mirror of
https://github.com/AlexMacocian/Squealify.git
synced 2026-07-15 14:59:32 +00:00
Add project files.
This commit is contained in:
+139
@@ -0,0 +1,139 @@
|
||||
[*.cs]
|
||||
|
||||
# CA2254: Template should be a static expression
|
||||
dotnet_diagnostic.CA2254.severity = none
|
||||
csharp_indent_labels = one_less_than_current
|
||||
csharp_using_directive_placement = outside_namespace:error
|
||||
csharp_prefer_simple_using_statement = true:error
|
||||
csharp_prefer_braces = true:silent
|
||||
csharp_style_namespace_declarations = file_scoped:error
|
||||
csharp_style_prefer_method_group_conversion = true:suggestion
|
||||
csharp_style_expression_bodied_methods = false:silent
|
||||
csharp_style_expression_bodied_constructors = false:silent
|
||||
csharp_style_expression_bodied_operators = false:silent
|
||||
csharp_style_expression_bodied_properties = true:silent
|
||||
csharp_style_expression_bodied_indexers = true:silent
|
||||
csharp_style_expression_bodied_accessors = true:silent
|
||||
csharp_style_expression_bodied_lambdas = true:silent
|
||||
csharp_style_expression_bodied_local_functions = false:silent
|
||||
csharp_indent_braces = false
|
||||
insert_final_newline = true
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
csharp_style_throw_expression = true:suggestion
|
||||
csharp_style_prefer_null_check_over_type_check = true:suggestion
|
||||
csharp_prefer_simple_default_expression = true:suggestion
|
||||
csharp_style_prefer_local_over_anonymous_function = true:error
|
||||
csharp_style_prefer_index_operator = true:suggestion
|
||||
csharp_style_prefer_range_operator = true:suggestion
|
||||
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
|
||||
csharp_style_prefer_tuple_swap = true:suggestion
|
||||
csharp_style_inlined_variable_declaration = true:suggestion
|
||||
csharp_style_deconstructed_variable_declaration = true:error
|
||||
csharp_style_unused_value_assignment_preference = discard_variable:error
|
||||
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
|
||||
csharp_prefer_static_local_function = true:suggestion
|
||||
csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
|
||||
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent
|
||||
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:suggestion
|
||||
csharp_style_conditional_delegate_call = true:suggestion
|
||||
csharp_style_prefer_parameter_null_checking = true:suggestion
|
||||
csharp_style_prefer_switch_expression = true:suggestion
|
||||
csharp_style_prefer_pattern_matching = true:silent
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||
csharp_style_prefer_not_pattern = true:suggestion
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||
csharp_style_prefer_extended_property_pattern = true:suggestion
|
||||
csharp_style_var_for_built_in_types = false:silent
|
||||
csharp_style_var_when_type_is_apparent = false:silent
|
||||
csharp_style_var_elsewhere = false:silent
|
||||
csharp_style_prefer_top_level_statements = true:suggestion
|
||||
csharp_style_prefer_utf8_string_literals = true:suggestion
|
||||
csharp_style_prefer_readonly_struct = true:suggestion
|
||||
csharp_style_prefer_primary_constructors = true:suggestion
|
||||
csharp_prefer_system_threading_lock = true:suggestion
|
||||
|
||||
[*.{cs,vb}]
|
||||
#### Naming styles ####
|
||||
|
||||
# Naming rules
|
||||
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.severity = error
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
|
||||
|
||||
dotnet_naming_rule.types_should_be_pascal_case.severity = error
|
||||
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
|
||||
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = error
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
|
||||
|
||||
# Symbol specifications
|
||||
|
||||
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.interface.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
|
||||
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.types.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
||||
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.non_field_members.required_modifiers =
|
||||
|
||||
# Naming styles
|
||||
|
||||
dotnet_naming_style.begins_with_i.required_prefix = I
|
||||
dotnet_naming_style.begins_with_i.required_suffix =
|
||||
dotnet_naming_style.begins_with_i.word_separator =
|
||||
dotnet_naming_style.begins_with_i.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.pascal_case.required_prefix =
|
||||
dotnet_naming_style.pascal_case.required_suffix =
|
||||
dotnet_naming_style.pascal_case.word_separator =
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.pascal_case.required_prefix =
|
||||
dotnet_naming_style.pascal_case.required_suffix =
|
||||
dotnet_naming_style.pascal_case.word_separator =
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||
tab_width = 4
|
||||
indent_size = 4
|
||||
end_of_line = crlf
|
||||
dotnet_style_coalesce_expression = true:suggestion
|
||||
dotnet_style_null_propagation = true:suggestion
|
||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:error
|
||||
dotnet_style_prefer_auto_properties = true:error
|
||||
dotnet_style_object_initializer = true:suggestion
|
||||
dotnet_style_collection_initializer = true:suggestion
|
||||
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
|
||||
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
||||
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
||||
dotnet_style_explicit_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||
dotnet_style_prefer_compound_assignment = true:suggestion
|
||||
dotnet_style_prefer_simplified_interpolation = true:suggestion
|
||||
dotnet_style_namespace_match_folder = true:suggestion
|
||||
dotnet_style_readonly_field = true:suggestion
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
|
||||
dotnet_style_predefined_type_for_member_access = true:silent
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
|
||||
dotnet_style_allow_multiple_blank_lines_experimental = true:silent
|
||||
dotnet_style_allow_statement_immediately_after_block_experimental = false:suggestion
|
||||
dotnet_code_quality_unused_parameters = all:error
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion
|
||||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion
|
||||
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion
|
||||
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion
|
||||
dotnet_style_qualification_for_field = true:error
|
||||
dotnet_style_qualification_for_property = true:error
|
||||
dotnet_style_qualification_for_method = true:error
|
||||
dotnet_style_qualification_for_event = true:error
|
||||
dotnet_diagnostic.CA1000.severity = error
|
||||
|
||||
[*.vb]
|
||||
dotnet_diagnostic.CA1047.severity = error
|
||||
@@ -0,0 +1,38 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Sybil;
|
||||
using System;
|
||||
|
||||
namespace Squealify;
|
||||
|
||||
[Generator(LanguageNames.CSharp)]
|
||||
public sealed class ColumnNameAttributeGenerator : IIncrementalGenerator
|
||||
{
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
context.RegisterPostInitializationOutput(GenerateAttribute);
|
||||
}
|
||||
|
||||
private static void GenerateAttribute(IncrementalGeneratorPostInitializationContext context)
|
||||
{
|
||||
var builder = SyntaxBuilder.CreateCompilationUnit()
|
||||
.WithNamespace(SyntaxBuilder.CreateNamespace(Constants.Namespace)
|
||||
.WithClass(SyntaxBuilder.CreateClass(Constants.ColumnNameAttributeName)
|
||||
.WithBaseClass(nameof(Attribute))
|
||||
.WithModifiers($"{Constants.Public} {Constants.Sealed}")
|
||||
.WithConstructor(SyntaxBuilder.CreateConstructor(Constants.ColumnNameAttributeName)
|
||||
.WithModifier(Constants.Public)
|
||||
.WithParameter(Constants.StringType, Constants.ColumnNameArgumentName)
|
||||
.WithBody($"this.{Constants.ColumnNameProperty} = {Constants.ColumnNameArgumentName};"))
|
||||
.WithAttribute(SyntaxBuilder.CreateAttribute("AttributeUsage")
|
||||
.WithArgument(AttributeTargets.Property)
|
||||
.WithArgument("Inherited", false)
|
||||
.WithArgument("AllowMultiple", false))
|
||||
.WithProperty(SyntaxBuilder.CreateProperty(Constants.StringType, Constants.ColumnNameProperty)
|
||||
.WithModifier(Constants.Public)
|
||||
.WithAccessor(SyntaxBuilder.CreateGetter()))));
|
||||
|
||||
var syntax = builder.Build();
|
||||
var source = syntax.ToFullString();
|
||||
context.AddSource($"{Constants.ColumnNameAttributeName}.g", source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
namespace Squealify;
|
||||
public static class Constants
|
||||
{
|
||||
public const string Private = "private";
|
||||
public const string Protected = "protected";
|
||||
public const string Readonly = "readonly";
|
||||
public const string Public = "public";
|
||||
public const string Sealed = "sealed";
|
||||
public const string Partial = "partial";
|
||||
public const string Abstract = "abstract";
|
||||
public const string Async = "async";
|
||||
public const string Params = "params";
|
||||
|
||||
public const string StringType = "string";
|
||||
public const string ByteType = "byte";
|
||||
public const string ValueTaskType = "ValueTask";
|
||||
public const string CancellationTokenType = "CancellationToken";
|
||||
public const string StringArrayType = "string[]";
|
||||
public const string DbConnectionType = "DbConnection";
|
||||
public const string DbParameterType = "DbParameter";
|
||||
public const string DbCommandType = "DbCommand";
|
||||
public const string FuncType = "Func";
|
||||
public const string NullableObject = "object?";
|
||||
|
||||
public const string PrimaryKey = "PRIMARY KEY";
|
||||
public const string ForeignKey = "FOREIGN KEY";
|
||||
public const string References = "REFERENCES";
|
||||
public const string NotNull = "NOT NULL";
|
||||
public const string Unique = "UNIQUE";
|
||||
|
||||
public const string ArrayName = "Array";
|
||||
|
||||
public const string Namespace = "Squealify";
|
||||
|
||||
public const string UsingSystemDataCommon = "System.Data.Common";
|
||||
|
||||
public const string CancellationTokenArgument = "cancellationToken";
|
||||
|
||||
public const string DbSetName = "DbSet";
|
||||
public const string DbSetTypeName = "T";
|
||||
|
||||
public const string TableAttributeName = "TableAttribute";
|
||||
public const string TableAttributeShortName = "Table";
|
||||
public const string TableNameProperty = "Name";
|
||||
public const string TableNameArgumentName = "name";
|
||||
|
||||
public const string PrimaryKeyAttributeName = "PrimaryKeyAttribute";
|
||||
public const string PrimaryKeyAttributeShortName = "PrimaryKey";
|
||||
|
||||
public const string ForeignKeyAttributeName = "ForeignKeyAttribute";
|
||||
public const string ForeignKeyAttributeShortName = "ForeignKey";
|
||||
public const string ForeignKeyReferenceTableProperty = "ReferenceTable";
|
||||
public const string ForeignKeyReferenceFieldProperty = "ReferenceField";
|
||||
public const string ForeignKeyArgumentReferenceTableName = "referenceTable";
|
||||
public const string ForeignKeyArgumentReferenceFieldName = "field";
|
||||
|
||||
public const string ColumnNameAttributeName = "ColumnNameAttribute";
|
||||
public const string ColumnNameAttributeShortName = "ColumnName";
|
||||
public const string ColumnNameProperty = "Name";
|
||||
public const string ColumnNameArgumentName = "name";
|
||||
|
||||
public const string VarcharAttributeName = "VarcharAttribute";
|
||||
public const string VarcharAttributeShortName = "Varchar";
|
||||
public const string LengthProperty = "Length";
|
||||
public const string LengthArgumentName = "length";
|
||||
|
||||
public const string ColumnUniqueAttributeName = "ColumnUniqueAttribute";
|
||||
public const string ColumnUniqueAttributeShortName = "ColumnUnique";
|
||||
|
||||
public const string TableUniqueAttributeName = "TableUniqueAttribute";
|
||||
public const string TableUniqueAttributeShortName = "TableUnique";
|
||||
public const string TableUniqueProperty = "Uniques";
|
||||
public const string TableUniqueArgumentName = "uniqueFields";
|
||||
|
||||
public const string DbConnectionArgumentName = "connection";
|
||||
public const string DbConnectionPropertyName = "Connection";
|
||||
|
||||
public const string CreateTableStatement = "CREATE TABLE";
|
||||
public const string CreateTableIfNotExistsStatement = "CREATE TABLE IF NOT EXISTS";
|
||||
public const string CreateTableMethod = "CreateTable";
|
||||
public const string CreateTableIfNotExistsMethod = "CreateTableIfNotExists";
|
||||
|
||||
public const string CreateParameterMethod = "CreateParameter";
|
||||
public const string CreateParameterCommandArgument = "command";
|
||||
public const string CreateParameterNameArgument = "name";
|
||||
public const string CreateParameterValueArgument = "value";
|
||||
public const string ParameterTypeName = "TParameter";
|
||||
|
||||
public const string DboArgumentName = "dbo";
|
||||
public const string PrimaryKeyArgumentName = "primaryKey";
|
||||
public const string InsertMethodName = "Insert";
|
||||
public const string UpsertMethodName = "Upsert";
|
||||
public const string UpdateMethodName = "Update";
|
||||
public const string DeleteMethodName = "Delete";
|
||||
public const string FindMethodName = "Find";
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
namespace Squealify.Contexts;
|
||||
|
||||
public readonly struct FieldContext(
|
||||
string name,
|
||||
string propertyName,
|
||||
string propertyType,
|
||||
DataTypes type,
|
||||
bool isUnique,
|
||||
bool isPrimaryKey,
|
||||
bool isForeignKey,
|
||||
string? referenceTable,
|
||||
string? referenceField,
|
||||
byte? varcharLength,
|
||||
bool nullable,
|
||||
bool isEnum,
|
||||
bool requiresConversion)
|
||||
{
|
||||
public readonly string Name = name;
|
||||
public readonly string PropertyName = propertyName;
|
||||
public readonly string PropertyType = propertyType;
|
||||
public readonly DataTypes Type = type;
|
||||
public readonly bool IsUnique = isUnique;
|
||||
public readonly bool IsPrimaryKey = isPrimaryKey;
|
||||
public readonly bool IsForeignKey = isForeignKey;
|
||||
public readonly bool IsNullable = nullable;
|
||||
public readonly bool IsEnum = isEnum;
|
||||
public readonly string? ReferenceTable = referenceTable;
|
||||
public readonly string? ReferenceField = referenceField;
|
||||
public readonly byte? VarcharLength = varcharLength;
|
||||
public readonly bool RequiresConversion = requiresConversion;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Sybil;
|
||||
|
||||
namespace Squealify.Contexts;
|
||||
public readonly struct MethodWithSqlStatement(MethodBuilder methodBuilder, string placeholder, string commandText)
|
||||
{
|
||||
public readonly MethodBuilder MethodBuilder = methodBuilder;
|
||||
public readonly string Placeholder = placeholder;
|
||||
public readonly string CommandText = commandText;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Squealify.Contexts;
|
||||
public readonly struct TableContext(string tableName, string dboType, FieldContext primaryKey, ImmutableArray<FieldContext> createFieldContexts, ImmutableArray<TableUniqueContext> tableUniques)
|
||||
{
|
||||
public readonly string TableName = tableName;
|
||||
public readonly string DboType = dboType;
|
||||
public readonly FieldContext PrimaryKey = primaryKey;
|
||||
public readonly ImmutableArray<FieldContext> Fields = createFieldContexts;
|
||||
public readonly ImmutableArray<TableUniqueContext> TableUniques = tableUniques;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Squealify.Contexts;
|
||||
public readonly struct TableUniqueContext(ImmutableArray<string> columnNames)
|
||||
{
|
||||
public readonly ImmutableArray<string> ColumnNames = columnNames;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Squealify;
|
||||
public static class DataTypeMappings
|
||||
{
|
||||
public static readonly Dictionary<string, DataTypes> StringToDataType = new() {
|
||||
{ "int", DataTypes.INTEGER },
|
||||
{ "system.int32", DataTypes.INTEGER },
|
||||
{ "uint", DataTypes.INTEGER },
|
||||
{ "system.uint32", DataTypes.INTEGER },
|
||||
|
||||
{ "short", DataTypes.SMALLINT },
|
||||
{ "system.int16", DataTypes.SMALLINT },
|
||||
{ "ushort", DataTypes.SMALLINT },
|
||||
{ "system.uint16", DataTypes.SMALLINT },
|
||||
|
||||
{ "long", DataTypes.BIGINT },
|
||||
{ "system.int64", DataTypes.BIGINT },
|
||||
{ "ulong", DataTypes.BIGINT },
|
||||
{ "system.uint64", DataTypes.BIGINT },
|
||||
|
||||
{ "float", DataTypes.REAL },
|
||||
{ "system.single", DataTypes.REAL },
|
||||
|
||||
{ "double", DataTypes.DOUBLE_PRECISION },
|
||||
{ "system.double", DataTypes.DOUBLE_PRECISION },
|
||||
|
||||
{ "decimal", DataTypes.DECIMAL },
|
||||
{ "system.decimal", DataTypes.DECIMAL },
|
||||
|
||||
{ "string", DataTypes.TEXT },
|
||||
{ "system.string", DataTypes.TEXT },
|
||||
|
||||
{ "char", DataTypes.CHAR },
|
||||
{ "system.char", DataTypes.CHAR },
|
||||
|
||||
{ "bool", DataTypes.BOOLEAN },
|
||||
{ "system.boolean", DataTypes.BOOLEAN },
|
||||
{ "boolean", DataTypes.BOOLEAN },
|
||||
|
||||
{ "datetime", DataTypes.TIMESTAMP },
|
||||
{ "system.datetime", DataTypes.TIMESTAMP },
|
||||
{ "datetimeoffset", DataTypes.TIMESTAMP },
|
||||
{ "system.datetimeoffset", DataTypes.TIMESTAMP },
|
||||
|
||||
{ "dateonly", DataTypes.DATE },
|
||||
{ "system.dateonly", DataTypes.DATE },
|
||||
|
||||
{ "timeonly", DataTypes.TIME },
|
||||
{ "system.timeonly", DataTypes.TIME },
|
||||
|
||||
{ "byte[]", DataTypes.BLOB },
|
||||
{ "system.byte[]", DataTypes.BLOB },
|
||||
};
|
||||
|
||||
public static readonly Dictionary<DataTypes, string> DataTypeToString = new()
|
||||
{
|
||||
{ DataTypes.SMALLINT, "short" },
|
||||
{ DataTypes.INTEGER, "int" },
|
||||
{ DataTypes.BIGINT, "long" },
|
||||
{ DataTypes.REAL, "float" },
|
||||
{ DataTypes.DOUBLE_PRECISION, "double" },
|
||||
{ DataTypes.DECIMAL, "decimal" },
|
||||
{ DataTypes.CHAR, "char" },
|
||||
{ DataTypes.VARCHAR, "string" },
|
||||
{ DataTypes.TEXT, "string" },
|
||||
{ DataTypes.DATE, "DateOnly" },
|
||||
{ DataTypes.TIME, "TimeOnly" },
|
||||
{ DataTypes.TIMESTAMP, "DateTimeOffset" },
|
||||
{ DataTypes.BOOLEAN, "bool" },
|
||||
{ DataTypes.BLOB, "byte[]" }
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace Squealify;
|
||||
public enum DataTypes
|
||||
{
|
||||
SMALLINT,
|
||||
INTEGER,
|
||||
BIGINT,
|
||||
REAL,
|
||||
DOUBLE_PRECISION,
|
||||
DECIMAL,
|
||||
CHAR,
|
||||
VARCHAR,
|
||||
TEXT,
|
||||
DATE,
|
||||
TIME,
|
||||
TIMESTAMP,
|
||||
BOOLEAN,
|
||||
BLOB
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Sybil;
|
||||
using System;
|
||||
|
||||
namespace Squealify;
|
||||
|
||||
[Generator(LanguageNames.CSharp)]
|
||||
public sealed class ForeignKeyAttributeGenerator : IIncrementalGenerator
|
||||
{
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
context.RegisterPostInitializationOutput(GenerateAttribute);
|
||||
}
|
||||
|
||||
private static void GenerateAttribute(IncrementalGeneratorPostInitializationContext context)
|
||||
{
|
||||
var builder = SyntaxBuilder.CreateCompilationUnit()
|
||||
.WithNamespace(SyntaxBuilder.CreateNamespace(Constants.Namespace)
|
||||
.WithClass(SyntaxBuilder.CreateClass(Constants.ForeignKeyAttributeName)
|
||||
.WithBaseClass(nameof(Attribute))
|
||||
.WithModifiers($"{Constants.Public} {Constants.Sealed}")
|
||||
.WithConstructor(SyntaxBuilder.CreateConstructor(Constants.ForeignKeyAttributeName)
|
||||
.WithModifier(Constants.Public)
|
||||
.WithParameter(Constants.StringType, Constants.ForeignKeyArgumentReferenceTableName)
|
||||
.WithParameter(Constants.StringType, Constants.ForeignKeyArgumentReferenceFieldName)
|
||||
.WithBody($"this.{Constants.ForeignKeyReferenceTableProperty} = {Constants.ForeignKeyArgumentReferenceTableName}; this.{Constants.ForeignKeyReferenceFieldProperty} = {Constants.ForeignKeyArgumentReferenceFieldName};"))
|
||||
.WithAttribute(SyntaxBuilder.CreateAttribute("AttributeUsage")
|
||||
.WithArgument(AttributeTargets.Property)
|
||||
.WithArgument("Inherited", false)
|
||||
.WithArgument("AllowMultiple", false))
|
||||
.WithProperty(SyntaxBuilder.CreateProperty(Constants.StringType, Constants.ForeignKeyReferenceTableProperty)
|
||||
.WithModifier(Constants.Public)
|
||||
.WithAccessor(SyntaxBuilder.CreateGetter()))
|
||||
.WithProperty(SyntaxBuilder.CreateProperty(Constants.StringType, Constants.ForeignKeyReferenceFieldProperty)
|
||||
.WithModifier(Constants.Public)
|
||||
.WithAccessor(SyntaxBuilder.CreateGetter()))));
|
||||
var syntax = builder.Build();
|
||||
var source = syntax.ToFullString();
|
||||
context.AddSource($"{Constants.ForeignKeyAttributeName}.g.cs", source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Sybil;
|
||||
using System;
|
||||
|
||||
namespace Squealify;
|
||||
|
||||
[Generator(LanguageNames.CSharp)]
|
||||
public sealed class PrimaryKeyAttributeGenerator : IIncrementalGenerator
|
||||
{
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
context.RegisterPostInitializationOutput(GenerateAttribute);
|
||||
}
|
||||
|
||||
private static void GenerateAttribute(IncrementalGeneratorPostInitializationContext context)
|
||||
{
|
||||
var builder = SyntaxBuilder.CreateCompilationUnit()
|
||||
.WithNamespace(SyntaxBuilder.CreateNamespace(Constants.Namespace)
|
||||
.WithClass(SyntaxBuilder.CreateClass(Constants.PrimaryKeyAttributeName)
|
||||
.WithBaseClass(nameof(Attribute))
|
||||
.WithModifiers($"{Constants.Public} {Constants.Sealed}")
|
||||
.WithConstructor(SyntaxBuilder.CreateConstructor(Constants.PrimaryKeyAttributeName)
|
||||
.WithModifier(Constants.Public))
|
||||
.WithAttribute(SyntaxBuilder.CreateAttribute("AttributeUsage")
|
||||
.WithArgument(AttributeTargets.Property)
|
||||
.WithArgument("Inherited", false)
|
||||
.WithArgument("AllowMultiple", false))));
|
||||
var syntax = builder.Build();
|
||||
var source = syntax.ToFullString();
|
||||
context.AddSource($"{Constants.PrimaryKeyAttributeName}.g.cs", source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<Version>0.1.75</Version>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Authors>Alexandru Macocian</Authors>
|
||||
<Description>Source generators for ADO.Net that generate simple sql statements from classes.</Description>
|
||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
<IsRoslynComponent>true</IsRoslynComponent>
|
||||
<IsRoslynAnalyzer>true</IsRoslynAnalyzer>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<Nullable>Enable</Nullable>
|
||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Sybil" Version="0.8.4" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Package the generator in the analyzer directory of the nuget package -->
|
||||
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||
<None Include="$(OutputPath)\Sybil.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.13.35919.96
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Squealify", "Squealify.csproj", "{E0610C08-63C3-4577-9B66-9B64200C57E3}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{E0610C08-63C3-4577-9B66-9B64200C57E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E0610C08-63C3-4577-9B66-9B64200C57E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E0610C08-63C3-4577-9B66-9B64200C57E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E0610C08-63C3-4577-9B66-9B64200C57E3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {6013BB17-9CF4-4C52-8B8B-090413AA87AB}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,285 @@
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Squealify.Contexts;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Squealify;
|
||||
public static class SyntaxParsers
|
||||
{
|
||||
public static (string ConvertToName, string ConvertFromName) GetConversionNames(string propertyType, string convertedType)
|
||||
{
|
||||
var convertedTypeSafeNameBuilder = new StringBuilder(convertedType);
|
||||
convertedTypeSafeNameBuilder[0] = char.ToUpper(convertedTypeSafeNameBuilder[0]);
|
||||
if (convertedType.Contains("[]"))
|
||||
{
|
||||
convertedTypeSafeNameBuilder.Replace("[]", Constants.ArrayName);
|
||||
}
|
||||
|
||||
convertedTypeSafeNameBuilder.Replace("?", "");
|
||||
var convertedTypeSafeName = convertedTypeSafeNameBuilder.ToString();
|
||||
var propertyTypeSafeName = propertyType.Replace("?", "").Replace("[]", Constants.ArrayName);
|
||||
|
||||
var convertToName = $"{propertyTypeSafeName}To{convertedTypeSafeName}Converter";
|
||||
var convertFromName = $"{convertedTypeSafeName}To{propertyTypeSafeName}Converter";
|
||||
return (convertToName, convertFromName);
|
||||
}
|
||||
|
||||
public static string GetTableName(ClassDeclarationSyntax classDeclarationSyntax)
|
||||
{
|
||||
var tableAttribute = classDeclarationSyntax.AttributeLists
|
||||
.SelectMany(n => n.Attributes)
|
||||
.FirstOrDefault(a => a.Name.ToString() == Constants.TableAttributeName ||
|
||||
a.Name.ToString() == Constants.TableAttributeShortName);
|
||||
|
||||
if (tableAttribute is null)
|
||||
{
|
||||
return classDeclarationSyntax.Identifier.ValueText;
|
||||
}
|
||||
|
||||
if (tableAttribute.ArgumentList is not null && tableAttribute.ArgumentList.Arguments.Count > 0)
|
||||
{
|
||||
foreach (var arg in tableAttribute.ArgumentList.Arguments)
|
||||
{
|
||||
if (arg.NameEquals != null && arg.NameEquals.Name.Identifier.ValueText == Constants.TableNameArgumentName)
|
||||
{
|
||||
return arg.Expression.ToString().Trim('"');
|
||||
}
|
||||
}
|
||||
|
||||
return tableAttribute.ArgumentList.Arguments[0].Expression.ToString().Trim('"');
|
||||
}
|
||||
|
||||
return classDeclarationSyntax.Identifier.ValueText;
|
||||
}
|
||||
|
||||
public static ImmutableArray<TableUniqueContext> GetTableUniques(ClassDeclarationSyntax classDeclarationSyntax)
|
||||
{
|
||||
var uniquesBuilder = ImmutableArray.CreateBuilder<TableUniqueContext>();
|
||||
var classAttributes = classDeclarationSyntax.AttributeLists.SelectMany(a => a.Attributes);
|
||||
foreach (var attribute in classAttributes)
|
||||
{
|
||||
var name = attribute.Name.ToString();
|
||||
if (name == Constants.TableUniqueAttributeName || name == Constants.TableUniqueAttributeShortName)
|
||||
{
|
||||
if (attribute.ArgumentList is not null)
|
||||
{
|
||||
var columnNamesBuilder = ImmutableArray.CreateBuilder<string>();
|
||||
foreach (var arg in attribute.ArgumentList.Arguments)
|
||||
{
|
||||
if (arg.Expression is InvocationExpressionSyntax invocation &&
|
||||
invocation.Expression.ToString() == "nameof")
|
||||
{
|
||||
// e.g. nameof(PropertyName)
|
||||
// The argument's expression typically is an IdentifierNameSyntax
|
||||
if (invocation.ArgumentList.Arguments.Count == 1)
|
||||
{
|
||||
var nameofArg = invocation.ArgumentList.Arguments[0].Expression.ToString();
|
||||
columnNamesBuilder.Add(nameofArg);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var text = arg.Expression.ToString().Trim('"');
|
||||
columnNamesBuilder.Add(text);
|
||||
}
|
||||
}
|
||||
|
||||
uniquesBuilder.Add(new TableUniqueContext(columnNamesBuilder.ToImmutable()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return uniquesBuilder.ToImmutable();
|
||||
}
|
||||
|
||||
public static ImmutableArray<FieldContext> GetFieldsFromClass(ClassDeclarationSyntax classDeclarationSyntax, Compilation compilation)
|
||||
{
|
||||
var fieldsBuilder = ImmutableArray.CreateBuilder<FieldContext>();
|
||||
foreach (var member in classDeclarationSyntax.Members)
|
||||
{
|
||||
if (member is PropertyDeclarationSyntax propertyDeclaration)
|
||||
{
|
||||
// Ensure the property is public.
|
||||
if (!propertyDeclaration.Modifiers.Any(SyntaxKind.PublicKeyword))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check that the property has an accessor list containing a getter and a setter or init.
|
||||
if (propertyDeclaration.AccessorList is not { } accessorList)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bool hasGetter = false;
|
||||
bool hasSetOrInit = false;
|
||||
foreach (var accessor in accessorList.Accessors)
|
||||
{
|
||||
if (accessor.IsKind(SyntaxKind.GetAccessorDeclaration))
|
||||
{
|
||||
hasGetter = true;
|
||||
}
|
||||
else if (accessor.IsKind(SyntaxKind.SetAccessorDeclaration) || accessor.IsKind(SyntaxKind.InitAccessorDeclaration))
|
||||
{
|
||||
hasSetOrInit = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasGetter || !hasSetOrInit)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Default field name to property name.
|
||||
var fieldName = propertyDeclaration.Identifier.ValueText;
|
||||
var originalName = fieldName;
|
||||
var isUnique = false;
|
||||
var isPrimaryKey = false;
|
||||
var isForeignKey = false;
|
||||
string? referenceTable = null;
|
||||
string? referenceField = null;
|
||||
byte? varCharSize = null;
|
||||
|
||||
// Process attributes.
|
||||
foreach (var attributeList in propertyDeclaration.AttributeLists)
|
||||
{
|
||||
foreach (var attribute in attributeList.Attributes)
|
||||
{
|
||||
var attrName = attribute.Name.ToString();
|
||||
// Check for Unique attribute.
|
||||
if (attrName == Constants.ColumnUniqueAttributeName || attrName == Constants.ColumnUniqueAttributeShortName)
|
||||
{
|
||||
isUnique = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for PrimaryKey attribute.
|
||||
if (attrName == Constants.PrimaryKeyAttributeName || attrName == Constants.PrimaryKeyAttributeShortName)
|
||||
{
|
||||
isPrimaryKey = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for Varchar attribute.
|
||||
if (attrName == Constants.VarcharAttributeName || attrName == Constants.VarcharAttributeShortName)
|
||||
{
|
||||
if (attribute.ArgumentList is not null && attribute.ArgumentList.Arguments.Count >= 1)
|
||||
{
|
||||
// Extract first two arguments as referenceTable and referenceField.
|
||||
varCharSize = byte.Parse(attribute.ArgumentList.Arguments[0].ToString().Trim('"'));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for ForeignKey attribute.
|
||||
if (attrName == Constants.ForeignKeyAttributeName || attrName == Constants.ForeignKeyAttributeShortName)
|
||||
{
|
||||
isForeignKey = true;
|
||||
if (attribute.ArgumentList is not null && attribute.ArgumentList.Arguments.Count >= 2)
|
||||
{
|
||||
// Extract first two arguments as referenceTable and referenceField.
|
||||
referenceTable = attribute.ArgumentList.Arguments[0].ToString().Trim('"');
|
||||
referenceField = attribute.ArgumentList.Arguments[1].ToString().Trim('"');
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for Name attribute.
|
||||
if (attrName == Constants.ColumnNameAttributeName || attrName == Constants.ColumnNameAttributeShortName)
|
||||
{
|
||||
if (attribute.ArgumentList is not null && attribute.ArgumentList.Arguments.Count >= 1)
|
||||
{
|
||||
fieldName = attribute.ArgumentList.Arguments[0].ToString().Trim('"');
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var semanticModel = compilation.GetSemanticModel(propertyDeclaration.SyntaxTree);
|
||||
var typeSymbol = semanticModel.GetTypeInfo(propertyDeclaration.Type).Type;
|
||||
var propertyTypeString = propertyDeclaration.Type.ToString();
|
||||
var nullable = propertyTypeString.EndsWith("?");
|
||||
(var isEnum, var enumUnderlyingType) = IsEnumType(typeSymbol);
|
||||
(var propertyMapping, var requiresConversion) = MapPropertyType(propertyTypeString);
|
||||
requiresConversion = !isEnum && requiresConversion;
|
||||
var dataType = isEnum
|
||||
? enumUnderlyingType ?? DataTypes.INTEGER
|
||||
: varCharSize is null
|
||||
? propertyMapping
|
||||
: DataTypes.VARCHAR;
|
||||
fieldsBuilder.Add(new FieldContext(
|
||||
fieldName,
|
||||
originalName,
|
||||
propertyTypeString,
|
||||
dataType,
|
||||
isUnique,
|
||||
isPrimaryKey,
|
||||
isForeignKey,
|
||||
referenceTable,
|
||||
referenceField,
|
||||
varCharSize,
|
||||
nullable,
|
||||
isEnum,
|
||||
requiresConversion));
|
||||
}
|
||||
}
|
||||
|
||||
return fieldsBuilder.ToImmutable();
|
||||
}
|
||||
|
||||
private static (bool, DataTypes? UnderlyingType) IsEnumType(ITypeSymbol? typeSymbol)
|
||||
{
|
||||
if (typeSymbol is null)
|
||||
{
|
||||
return (false, default);
|
||||
}
|
||||
|
||||
if (typeSymbol.TypeKind == TypeKind.Enum && typeSymbol is INamedTypeSymbol enumType)
|
||||
{
|
||||
return (true, enumType.EnumUnderlyingType?.ToString().ToLowerInvariant() switch
|
||||
{
|
||||
"byte" or "system.byte" => DataTypes.SMALLINT,
|
||||
"int" or "system.int32" or "uint" or "system.uint32" => DataTypes.INTEGER,
|
||||
"short" or "system.int16" or "ushort" or "system.uint16" => DataTypes.SMALLINT,
|
||||
"long" or "system.int64" or "ulong" or "system.uint64" => DataTypes.BIGINT,
|
||||
_ => DataTypes.INTEGER
|
||||
});
|
||||
}
|
||||
|
||||
if (typeSymbol is INamedTypeSymbol namedType &&
|
||||
namedType.IsGenericType &&
|
||||
namedType.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T &&
|
||||
namedType.TypeArguments.Length == 1 &&
|
||||
namedType.TypeArguments[0].TypeKind == TypeKind.Enum)
|
||||
{
|
||||
var enumTypeSymbol = (INamedTypeSymbol)namedType.TypeArguments[0];
|
||||
return (true, enumTypeSymbol.EnumUnderlyingType?.ToString().ToLowerInvariant() switch
|
||||
{
|
||||
"byte" or "system.byte" => DataTypes.SMALLINT,
|
||||
"int" or "system.int32" or "uint" or "system.uint32" => DataTypes.INTEGER,
|
||||
"short" or "system.int16" or "ushort" or "system.uint16" => DataTypes.SMALLINT,
|
||||
"long" or "system.int64" or "ulong" or "system.uint64" => DataTypes.BIGINT,
|
||||
_ => DataTypes.INTEGER
|
||||
});
|
||||
}
|
||||
|
||||
return (false, default);
|
||||
}
|
||||
|
||||
private static (DataTypes Type, bool RequiresConversion) MapPropertyType(string propertyType)
|
||||
{
|
||||
// Normalize the type name for matching.
|
||||
var typeName = propertyType.Trim();
|
||||
var normalizedName = typeName.ToLowerInvariant().TrimEnd('?');
|
||||
if (DataTypeMappings.StringToDataType.TryGetValue(normalizedName, out var dataType))
|
||||
{
|
||||
return (dataType, false);
|
||||
}
|
||||
|
||||
return (DataTypes.TEXT, true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
using Squealify.Contexts;
|
||||
using Sybil;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Squealify.Table;
|
||||
public static class BasicQueryMethodGenerator
|
||||
{
|
||||
public static MethodWithSqlStatement CreateInsertStatement(TableContext context)
|
||||
{
|
||||
var placeHolder = $"Insert{context.DboType}";
|
||||
var sql = new StringBuilder();
|
||||
sql.AppendLine()
|
||||
.AppendLine($"\t\t\tINSERT INTO {context.TableName}")
|
||||
.AppendLine($"\t\t\t\t({string.Join(", ", context.Fields.Select(f => f.Name))})")
|
||||
.AppendLine("\t\t\tVALUES")
|
||||
.AppendLine($"\t\t\t\t({string.Join(", ", context.Fields.Select(GetPlaceholderName))})");
|
||||
|
||||
var methodBuilder = SyntaxBuilder.CreateMethod(Constants.ValueTaskType, Constants.InsertMethodName)
|
||||
.WithModifiers($"{Constants.Public} {Constants.Async}")
|
||||
.WithParameter(context.DboType, Constants.DboArgumentName)
|
||||
.WithParameter(Constants.CancellationTokenType, Constants.CancellationTokenArgument)
|
||||
.WithBody(GenerateFullBody(placeHolder, context));
|
||||
|
||||
return new MethodWithSqlStatement(methodBuilder, placeHolder, sql.ToString());
|
||||
}
|
||||
|
||||
public static MethodWithSqlStatement CreateUpsertStatement(TableContext context)
|
||||
{
|
||||
var placeHolder = $"Upsert{context.DboType}";
|
||||
var sql = new StringBuilder();
|
||||
sql.AppendLine()
|
||||
.AppendLine($"\t\t\tINSERT INTO {context.TableName}")
|
||||
.AppendLine($"\t\t\t\t({string.Join(", ", context.Fields.Select(f => f.Name))})")
|
||||
.AppendLine("\t\t\tVALUES")
|
||||
.AppendLine($"\t\t\t\t({string.Join(", ", context.Fields.Select(GetPlaceholderName))})")
|
||||
.AppendLine($"\t\t\tON CONFLICT({context.PrimaryKey.Name}) DO UPDATE SET");
|
||||
|
||||
for(var i = 0; i < context.Fields.Length; i++)
|
||||
{
|
||||
var fieldContext = context.Fields[i];
|
||||
if (fieldContext.Name == context.PrimaryKey.Name)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
sql.Append($"\t\t\t\t{fieldContext.Name} = excluded.{fieldContext.Name}");
|
||||
if (i < context.Fields.Length - 1)
|
||||
{
|
||||
sql.AppendLine(",");
|
||||
}
|
||||
}
|
||||
|
||||
sql.AppendLine(";");
|
||||
var methodBuilder = SyntaxBuilder.CreateMethod(Constants.ValueTaskType, Constants.UpsertMethodName)
|
||||
.WithModifiers($"{Constants.Public} {Constants.Async}")
|
||||
.WithParameter(context.DboType, Constants.DboArgumentName)
|
||||
.WithParameter(Constants.CancellationTokenType, Constants.CancellationTokenArgument)
|
||||
.WithBody(GenerateFullBody(placeHolder, context));
|
||||
|
||||
return new MethodWithSqlStatement(methodBuilder, placeHolder, sql.ToString());
|
||||
}
|
||||
|
||||
public static MethodWithSqlStatement CreateUpdateStatement(TableContext context)
|
||||
{
|
||||
var placeHolder = $"Update{context.DboType}";
|
||||
var sql = new StringBuilder();
|
||||
sql.AppendLine()
|
||||
.AppendLine($"\t\t\tUPDATE {context.TableName}")
|
||||
.AppendLine("\t\t\tSET");
|
||||
|
||||
for (var i = 0; i < context.Fields.Length; i++)
|
||||
{
|
||||
var fieldContext = context.Fields[i];
|
||||
if (fieldContext.Name == context.PrimaryKey.Name)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
sql.Append($"\t\t\t\t{fieldContext.Name} = {GetPlaceholderName(fieldContext)}");
|
||||
if (i < context.Fields.Length - 1)
|
||||
{
|
||||
sql.AppendLine(",");
|
||||
}
|
||||
}
|
||||
|
||||
sql.AppendLine()
|
||||
.AppendLine("\t\t\tWHERE")
|
||||
.AppendLine($"\t\t\t\t{context.PrimaryKey.Name} = {GetPlaceholderName(context.PrimaryKey)};");
|
||||
|
||||
var methodBuilder = SyntaxBuilder.CreateMethod(Constants.ValueTaskType, Constants.UpdateMethodName)
|
||||
.WithModifiers($"{Constants.Public} {Constants.Async}")
|
||||
.WithParameter(context.DboType, Constants.DboArgumentName)
|
||||
.WithParameter(Constants.CancellationTokenType, Constants.CancellationTokenArgument)
|
||||
.WithBody(GenerateFullBody(placeHolder, context));
|
||||
|
||||
return new MethodWithSqlStatement(methodBuilder, placeHolder, sql.ToString());
|
||||
}
|
||||
|
||||
public static MethodWithSqlStatement CreateDeleteStatement(TableContext context)
|
||||
{
|
||||
var placeHolder = $"Delete{context.DboType}";
|
||||
var sql = new StringBuilder();
|
||||
sql.AppendLine()
|
||||
.AppendLine($"\t\t\tDELETE FROM {context.TableName}")
|
||||
.AppendLine($"\t\t\tWHERE {context.PrimaryKey.Name} = {GetPlaceholderName(context.PrimaryKey)};");
|
||||
|
||||
var bodyBuilder = new StringBuilder(@$"
|
||||
using var command = this.{Constants.DbConnectionPropertyName}.CreateCommand();
|
||||
command.CommandText = @""{placeHolder}"";
|
||||
");
|
||||
|
||||
if (!DataTypeMappings.DataTypeToString.TryGetValue(context.PrimaryKey.Type, out var convertedType))
|
||||
{
|
||||
throw new InvalidOperationException($"Unable to find conversion for {context.PrimaryKey.Type}");
|
||||
}
|
||||
|
||||
(var convertToName, _) = SyntaxParsers.GetConversionNames(context.PrimaryKey.PropertyType, convertedType);
|
||||
var valueProvider = context.PrimaryKey.RequiresConversion
|
||||
? $"this.{convertToName}({Constants.PrimaryKeyArgumentName})"
|
||||
: $"{Constants.PrimaryKeyArgumentName}";
|
||||
|
||||
bodyBuilder.AppendLine($"command.Parameters.Add(this.{Constants.CreateParameterMethod}(command, \"{GetPlaceholderName(context.PrimaryKey)}\", {valueProvider}));");
|
||||
bodyBuilder.AppendLine("await command.ExecuteNonQueryAsync(cancellationToken);");
|
||||
|
||||
var methodBuilder = SyntaxBuilder.CreateMethod(Constants.ValueTaskType, Constants.DeleteMethodName)
|
||||
.WithModifiers($"{Constants.Public} {Constants.Async}")
|
||||
.WithParameter(context.PrimaryKey.PropertyType, Constants.PrimaryKeyArgumentName)
|
||||
.WithParameter(Constants.CancellationTokenType, Constants.CancellationTokenArgument)
|
||||
.WithBody(bodyBuilder.ToString());
|
||||
|
||||
return new MethodWithSqlStatement(methodBuilder, placeHolder, sql.ToString());
|
||||
}
|
||||
|
||||
public static MethodWithSqlStatement CreateFindStatement(TableContext context)
|
||||
{
|
||||
var placeHolder = $"Find{context.DboType}";
|
||||
var sql = new StringBuilder();
|
||||
sql.AppendLine()
|
||||
.AppendLine($"\t\t\tSELECT * FROM {context.TableName}")
|
||||
.AppendLine($"\t\t\tWHERE {context.PrimaryKey.Name} = {GetPlaceholderName(context.PrimaryKey)};");
|
||||
|
||||
var bodyBuilder = new StringBuilder(@$"
|
||||
using var command = this.{Constants.DbConnectionPropertyName}.CreateCommand();
|
||||
command.CommandText = @""{placeHolder}"";
|
||||
");
|
||||
|
||||
if (!DataTypeMappings.DataTypeToString.TryGetValue(context.PrimaryKey.Type, out var convertedType))
|
||||
{
|
||||
throw new InvalidOperationException($"Unable to find conversion for {context.PrimaryKey.Type}");
|
||||
}
|
||||
|
||||
(var convertToName, _) = SyntaxParsers.GetConversionNames(context.PrimaryKey.PropertyType, convertedType);
|
||||
var valueProvider = context.PrimaryKey.RequiresConversion
|
||||
? $"this.{convertToName}({Constants.PrimaryKeyArgumentName})"
|
||||
: $"{Constants.PrimaryKeyArgumentName}";
|
||||
|
||||
bodyBuilder.AppendLine($"command.Parameters.Add(this.{Constants.CreateParameterMethod}(command, \"{GetPlaceholderName(context.PrimaryKey)}\", {valueProvider}));");
|
||||
bodyBuilder.AppendLine($"using var reader = await command.ExecuteReaderAsync({Constants.CancellationTokenArgument});");
|
||||
bodyBuilder.AppendLine($"if (!await reader.ReadAsync({Constants.CancellationTokenArgument})) return default;");
|
||||
bodyBuilder.AppendLine($"return new {context.DboType}");
|
||||
bodyBuilder.AppendLine("{");
|
||||
foreach((var index, var fieldContext) in context.Fields.Select((f, i) => (i, f)))
|
||||
{
|
||||
if (!DataTypeMappings.DataTypeToString.TryGetValue(fieldContext.Type, out var convertedFieldType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
(_, var convertFromName) = SyntaxParsers.GetConversionNames(fieldContext.PropertyType, convertedFieldType);
|
||||
|
||||
var fieldValueProvider = fieldContext.RequiresConversion
|
||||
? $"this.{convertFromName}({GetReaderMethod("reader", fieldContext)}({index}))"
|
||||
: $"{GetReaderMethod("reader", fieldContext)}({index})";
|
||||
|
||||
var nullCheckGuardedProvider = fieldContext.IsNullable
|
||||
? $"await reader.IsDBNullAsync({index}, {Constants.CancellationTokenArgument}) ? default : {fieldValueProvider}"
|
||||
: fieldValueProvider;
|
||||
|
||||
bodyBuilder.Append($"{fieldContext.PropertyName} = {nullCheckGuardedProvider}");
|
||||
if (index < context.Fields.Length - 1)
|
||||
{
|
||||
bodyBuilder.AppendLine(",");
|
||||
}
|
||||
}
|
||||
bodyBuilder
|
||||
.AppendLine()
|
||||
.AppendLine("};");
|
||||
|
||||
var methodBuilder = SyntaxBuilder.CreateMethod($"{Constants.ValueTaskType}<{context.DboType}>", Constants.FindMethodName)
|
||||
.WithModifiers($"{Constants.Public} {Constants.Async}")
|
||||
.WithParameter(context.PrimaryKey.PropertyType, Constants.PrimaryKeyArgumentName)
|
||||
.WithParameter(Constants.CancellationTokenType, Constants.CancellationTokenArgument)
|
||||
.WithBody(bodyBuilder.ToString());
|
||||
|
||||
return new MethodWithSqlStatement(methodBuilder, placeHolder, sql.ToString());
|
||||
}
|
||||
|
||||
private static string GenerateFullBody(string placeHolder, TableContext context)
|
||||
{
|
||||
var bodyBuilder = new StringBuilder(@$"
|
||||
using var command = this.{Constants.DbConnectionPropertyName}.CreateCommand();
|
||||
command.CommandText = @""{placeHolder}"";
|
||||
");
|
||||
|
||||
foreach (var fieldContext in context.Fields)
|
||||
{
|
||||
var propertyType = fieldContext.PropertyType;
|
||||
var dataType = fieldContext.Type;
|
||||
if (!DataTypeMappings.DataTypeToString.TryGetValue(dataType, out var convertedType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
(var convertToName, _) = SyntaxParsers.GetConversionNames(propertyType, convertedType);
|
||||
var valueProvider = fieldContext.RequiresConversion
|
||||
? $"this.{convertToName}({Constants.DboArgumentName}.{fieldContext.PropertyName})"
|
||||
: $"{Constants.DboArgumentName}.{fieldContext.PropertyName}";
|
||||
|
||||
bodyBuilder.AppendLine($"command.Parameters.Add(this.{Constants.CreateParameterMethod}(command, \"{GetPlaceholderName(fieldContext)}\", {valueProvider}));");
|
||||
}
|
||||
|
||||
bodyBuilder.AppendLine("await command.ExecuteNonQueryAsync(cancellationToken);");
|
||||
return bodyBuilder.ToString();
|
||||
}
|
||||
|
||||
private static string GetPlaceholderName(FieldContext context) => $"@{context.Name}";
|
||||
|
||||
private static string GetReaderMethod(string readerVariableName, FieldContext context)
|
||||
{
|
||||
var readMethod = context.Type switch
|
||||
{
|
||||
DataTypes.SMALLINT => "GetInt16",
|
||||
DataTypes.INTEGER => "GetInt32",
|
||||
DataTypes.BIGINT => "GetInt64",
|
||||
DataTypes.REAL => "GetFloat",
|
||||
DataTypes.DOUBLE_PRECISION => "GetDouble",
|
||||
DataTypes.DECIMAL => "GetDecimal",
|
||||
DataTypes.CHAR => "GetChar",
|
||||
DataTypes.VARCHAR => "GetString",
|
||||
DataTypes.TEXT => "GetString",
|
||||
DataTypes.DATE => "GetDateTime",
|
||||
DataTypes.TIME => "GetDateTime",
|
||||
DataTypes.TIMESTAMP => "GetDateTime",
|
||||
DataTypes.BOOLEAN => "GetBoolean",
|
||||
DataTypes.BLOB => "GetBytes",
|
||||
_ => throw new NotSupportedException($"Data type {context.Type} is not supported.")
|
||||
};
|
||||
|
||||
var readerMethod = context.IsEnum
|
||||
? $"({context.PropertyType}){readerVariableName}.{readMethod}"
|
||||
: $"{readerVariableName}.{readMethod}";
|
||||
|
||||
if(context.PropertyType.ToLower().TrimEnd('?') is "datetimeoffset" or "system.datetimeoffset")
|
||||
{
|
||||
return context.IsNullable
|
||||
? $"(DateTimeOffset?){readerMethod}"
|
||||
: $"(DateTimeOffset){readerMethod}";
|
||||
}
|
||||
|
||||
return readerMethod;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Squealify.Contexts;
|
||||
using Sybil;
|
||||
|
||||
namespace Squealify.Table;
|
||||
public static class ConversionPropertyGenerator
|
||||
{
|
||||
public static (PropertyBuilder ConvertTo, PropertyBuilder ConvertFrom)? GenerateConversionProperties(FieldContext fieldContext)
|
||||
{
|
||||
var propertyType = fieldContext.PropertyType;
|
||||
var dataType = fieldContext.Type;
|
||||
if (!DataTypeMappings.DataTypeToString.TryGetValue(dataType, out var convertedType))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
(var convertToName, var convertFromName) = SyntaxParsers.GetConversionNames(propertyType, convertedType);
|
||||
var convertTo = SyntaxBuilder.CreateProperty($"{Constants.FuncType}<{propertyType}, {convertedType}>", convertToName)
|
||||
.WithModifier(Constants.Protected)
|
||||
.WithModifier(Constants.Abstract)
|
||||
.WithAccessor(SyntaxBuilder.CreateGetter());
|
||||
|
||||
var convertFrom = SyntaxBuilder.CreateProperty($"{Constants.FuncType}<{convertedType}, {propertyType}>", convertFromName)
|
||||
.WithModifier(Constants.Protected)
|
||||
.WithModifier(Constants.Abstract)
|
||||
.WithAccessor(SyntaxBuilder.CreateGetter());
|
||||
|
||||
return (convertTo, convertFrom);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Sybil;
|
||||
|
||||
namespace Squealify.Table;
|
||||
public static class CreateParameterMethodGenerator
|
||||
{
|
||||
public static MethodBuilder CreateParameterMethod()
|
||||
{
|
||||
var methodBuilder = SyntaxBuilder.CreateMethod(Constants.DbParameterType, Constants.CreateParameterMethod)
|
||||
.WithModifier(Constants.Protected)
|
||||
.WithParameter(Constants.DbCommandType, Constants.CreateParameterCommandArgument)
|
||||
.WithParameter(Constants.StringType, Constants.CreateParameterNameArgument)
|
||||
.WithParameter(Constants.NullableObject, Constants.CreateParameterValueArgument)
|
||||
.WithBody($@"
|
||||
var parameter = {Constants.CreateParameterCommandArgument}.CreateParameter();
|
||||
parameter.ParameterName = {Constants.CreateParameterNameArgument};
|
||||
parameter.Value = {Constants.CreateParameterValueArgument} ?? DBNull.Value;
|
||||
return parameter;");
|
||||
|
||||
return methodBuilder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
using Squealify.Contexts;
|
||||
using Sybil;
|
||||
using System.Text;
|
||||
|
||||
namespace Squealify.Table;
|
||||
public static class TableCreateMethodGenerator
|
||||
{
|
||||
public static (MethodWithSqlStatement Create, MethodWithSqlStatement CreateIfNotExists) GenerateCreateMethods(TableContext ctx)
|
||||
{
|
||||
(var createMethod, var createMethodPlaceholder, var createMethodBody) =
|
||||
GenerateCreate(Constants.CreateTableStatement, Constants.CreateTableMethod, ctx);
|
||||
(var createIfNotExistsMethod, var createIfNotExistsPlacholder, var createIfNotExistsBody) =
|
||||
GenerateCreate(Constants.CreateTableIfNotExistsStatement, Constants.CreateTableIfNotExistsMethod, ctx);
|
||||
|
||||
return (
|
||||
new MethodWithSqlStatement(createMethod, createMethodPlaceholder, createMethodBody),
|
||||
new MethodWithSqlStatement(createIfNotExistsMethod, createIfNotExistsPlacholder, createIfNotExistsBody));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates the create method. Generates the CommandText body but puts it inside a placeholder, to avoid auto-formatting by Roslyn.
|
||||
/// </summary>
|
||||
private static (MethodBuilder Method, string Placeholder, string CommandText) GenerateCreate(string createStatement, string createName, TableContext ctx)
|
||||
{
|
||||
var commandPlaceholder = $"__COMMAND_TEXT_{createName}__";
|
||||
var commandTextBuilder = new StringBuilder();
|
||||
commandTextBuilder.AppendLine().AppendLine($"\t\t\t{createStatement} {ctx.TableName} (");
|
||||
|
||||
//Generate normal, primary and unique fields
|
||||
for (var i = 0; i < ctx.Fields.Length; i++)
|
||||
{
|
||||
var field = ctx.Fields[i];
|
||||
commandTextBuilder.Append($"\t\t\t\t{field.Name} {field.Type.ToString().Replace('_', ' ')}");
|
||||
if (field.IsPrimaryKey)
|
||||
{
|
||||
commandTextBuilder.Append(' ').Append(Constants.PrimaryKey);
|
||||
}
|
||||
|
||||
if (field.IsUnique)
|
||||
{
|
||||
commandTextBuilder.Append(' ').Append(Constants.Unique);
|
||||
}
|
||||
|
||||
if (!field.IsPrimaryKey &&
|
||||
!field.IsNullable)
|
||||
{
|
||||
commandTextBuilder.Append(' ').Append(Constants.NotNull);
|
||||
}
|
||||
|
||||
if (field.IsForeignKey &&
|
||||
field.ReferenceTable is string referenceTable &&
|
||||
field.ReferenceField is string referenceField)
|
||||
{
|
||||
commandTextBuilder.Append(' ').Append($"{Constants.References} {referenceTable}({referenceField})");
|
||||
}
|
||||
|
||||
if (i < ctx.Fields.Length - 1)
|
||||
{
|
||||
commandTextBuilder.AppendLine(",");
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.TableUniques.Length > 0)
|
||||
{
|
||||
commandTextBuilder.Append(',').AppendLine();
|
||||
}
|
||||
|
||||
for (var i = 0; i < ctx.TableUniques.Length; i++)
|
||||
{
|
||||
var tableUnique = ctx.TableUniques[i];
|
||||
commandTextBuilder.Append($"\t\t\t\t{Constants.Unique} ({string.Join(", ", tableUnique.ColumnNames)})");
|
||||
if (i < ctx.TableUniques.Length - 1)
|
||||
{
|
||||
commandTextBuilder.AppendLine(",");
|
||||
}
|
||||
}
|
||||
|
||||
commandTextBuilder.AppendLine(");");
|
||||
var methodBuilder = SyntaxBuilder.CreateMethod(Constants.ValueTaskType, createName)
|
||||
.WithModifier(Constants.Public)
|
||||
.WithModifier(Constants.Async)
|
||||
.WithParameter(Constants.CancellationTokenType, Constants.CancellationTokenArgument)
|
||||
.WithBody($@"
|
||||
using var command = this.{Constants.DbConnectionPropertyName}.CreateCommand();
|
||||
command.CommandText = @""{commandPlaceholder}"";
|
||||
await command.ExecuteNonQueryAsync({Constants.CancellationTokenArgument});");
|
||||
return (methodBuilder, commandPlaceholder, commandTextBuilder.ToString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Sybil;
|
||||
using System;
|
||||
|
||||
namespace Squealify;
|
||||
|
||||
[Generator(LanguageNames.CSharp)]
|
||||
public sealed class TableAttributeGenerator : IIncrementalGenerator
|
||||
{
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
context.RegisterPostInitializationOutput(GenerateAttribute);
|
||||
}
|
||||
|
||||
private static void GenerateAttribute(IncrementalGeneratorPostInitializationContext context)
|
||||
{
|
||||
var builder = SyntaxBuilder.CreateCompilationUnit()
|
||||
.WithNamespace(SyntaxBuilder.CreateNamespace(Constants.Namespace)
|
||||
.WithClass(SyntaxBuilder.CreateClass(Constants.TableAttributeName)
|
||||
.WithBaseClass(nameof(Attribute))
|
||||
.WithModifiers($"{Constants.Public} {Constants.Sealed}")
|
||||
.WithConstructor(SyntaxBuilder.CreateConstructor(Constants.TableAttributeName)
|
||||
.WithModifier(Constants.Public)
|
||||
.WithBody($"this.{Constants.TableNameProperty} = string.Empty;"))
|
||||
.WithConstructor(SyntaxBuilder.CreateConstructor(Constants.TableAttributeName)
|
||||
.WithModifier(Constants.Public)
|
||||
.WithParameter(Constants.StringType, Constants.TableNameArgumentName)
|
||||
.WithBody($"this.{Constants.TableNameProperty} = {Constants.TableNameArgumentName};"))
|
||||
.WithAttribute(SyntaxBuilder.CreateAttribute("AttributeUsage")
|
||||
.WithArgument(AttributeTargets.Class)
|
||||
.WithArgument("Inherited", false)
|
||||
.WithArgument("AllowMultiple", false))
|
||||
.WithProperty(SyntaxBuilder.CreateProperty(Constants.StringType, Constants.TableNameProperty)
|
||||
.WithModifier(Constants.Public)
|
||||
.WithAccessor(SyntaxBuilder.CreateGetter()))));
|
||||
|
||||
var syntax = builder.Build();
|
||||
var source = syntax.ToFullString();
|
||||
context.AddSource($"{Constants.TableAttributeName}.g.cs", source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Squealify.Contexts;
|
||||
using Squealify.Table;
|
||||
using Sybil;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
namespace Squealify;
|
||||
|
||||
[Generator(LanguageNames.CSharp)]
|
||||
public sealed class TableContextGenerator : IIncrementalGenerator
|
||||
{
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
var classDeclarations = context.SyntaxProvider.CreateSyntaxProvider(
|
||||
predicate: static (s, _) => s is ClassDeclarationSyntax,
|
||||
transform: static (ctx, _) => GetFilteredClassDeclarationSyntax(ctx)).Where(static c => c is not null);
|
||||
var compilationAndClasses = context.CompilationProvider.Combine(classDeclarations.Collect());
|
||||
context.RegisterSourceOutput(compilationAndClasses, (sourceProductionContext, tuple) => Execute(tuple.Left, tuple.Right, sourceProductionContext));
|
||||
}
|
||||
|
||||
private static ClassDeclarationSyntax? GetFilteredClassDeclarationSyntax(GeneratorSyntaxContext context)
|
||||
{
|
||||
var classDeclarationSyntax = (ClassDeclarationSyntax)context.Node;
|
||||
if (classDeclarationSyntax.AttributeLists
|
||||
.SelectMany(l => l.Attributes)
|
||||
.OfType<AttributeSyntax>()
|
||||
.Any(s => s.Name.ToString() is Constants.TableAttributeName or Constants.TableAttributeShortName))
|
||||
{
|
||||
return classDeclarationSyntax;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private static void Execute(Compilation compilation, ImmutableArray<ClassDeclarationSyntax?> classDeclarationSyntaxes, SourceProductionContext sourceProductionContext)
|
||||
{
|
||||
if (classDeclarationSyntaxes.IsDefaultOrEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var maybeLanguageVersion = (compilation.SyntaxTrees.FirstOrDefault()?.Options as CSharpParseOptions)?.LanguageVersion;
|
||||
if (!maybeLanguageVersion.HasValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var languageVersion = maybeLanguageVersion.Value;
|
||||
foreach(var classDeclarationSyntax in classDeclarationSyntaxes)
|
||||
{
|
||||
if (classDeclarationSyntax is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ExecuteClass(classDeclarationSyntax, compilation, sourceProductionContext, languageVersion);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ExecuteClass(ClassDeclarationSyntax classDeclarationSyntax, Compilation compilation, SourceProductionContext sourceProductionContext, LanguageVersion languageVersion)
|
||||
{
|
||||
var contextClassName = $"{classDeclarationSyntax.Identifier.ValueText}TableContextBase";
|
||||
var usingsSet = new HashSet<string>
|
||||
{
|
||||
Constants.UsingSystemDataCommon
|
||||
};
|
||||
|
||||
// Add the namespace of the class declaration (if any)
|
||||
var namespaceNode = classDeclarationSyntax.Ancestors().FirstOrDefault(n => n is NamespaceDeclarationSyntax or FileScopedNamespaceDeclarationSyntax);
|
||||
if (namespaceNode is BaseNamespaceDeclarationSyntax baseNamespace)
|
||||
{
|
||||
var namespaceName = baseNamespace.Name.ToString();
|
||||
if (!string.IsNullOrEmpty(namespaceName))
|
||||
{
|
||||
usingsSet.Add(namespaceName);
|
||||
}
|
||||
}
|
||||
|
||||
// Collect all "top-level" using directives from the same file
|
||||
var root = classDeclarationSyntax.SyntaxTree.GetRoot() as CompilationUnitSyntax;
|
||||
if (root is not null)
|
||||
{
|
||||
foreach (var usingDirective in root.Usings)
|
||||
{
|
||||
var nameText = usingDirective.Name?.ToString();
|
||||
if (nameText is not null)
|
||||
{
|
||||
usingsSet.Add(nameText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var builder = SyntaxBuilder.CreateCompilationUnit();
|
||||
foreach(var u in usingsSet)
|
||||
{
|
||||
builder.WithUsing(u);
|
||||
}
|
||||
|
||||
var namespaceBuilder = languageVersion >= LanguageVersion.CSharp10 ? SyntaxBuilder.CreateFileScopedNamespace(Constants.Namespace) : SyntaxBuilder.CreateNamespace(Constants.Namespace);
|
||||
builder.WithNamespace(namespaceBuilder);
|
||||
var classBuilder = SyntaxBuilder.CreateClass(contextClassName)
|
||||
.WithModifiers($"{Constants.Public} {Constants.Abstract}")
|
||||
.WithProperty(SyntaxBuilder.CreateProperty(Constants.DbConnectionType, Constants.DbConnectionPropertyName)
|
||||
.WithModifier(Constants.Protected)
|
||||
.WithAccessor(SyntaxBuilder.CreateGetter()))
|
||||
.WithConstructor(SyntaxBuilder.CreateConstructor(contextClassName)
|
||||
.WithParameter(Constants.DbConnectionType, Constants.DbConnectionArgumentName)
|
||||
.WithModifier(Constants.Public)
|
||||
.WithBody($"this.{Constants.DbConnectionPropertyName} = {Constants.DbConnectionArgumentName};"));
|
||||
namespaceBuilder.WithClass(classBuilder);
|
||||
|
||||
var ctx = GetTableContext(classDeclarationSyntax, compilation);
|
||||
|
||||
// Generate create methods
|
||||
(var createMethod, var createIfNotExistsMethod) = TableCreateMethodGenerator.GenerateCreateMethods(ctx);
|
||||
var insertMethod = BasicQueryMethodGenerator.CreateInsertStatement(ctx);
|
||||
var upsertMethod = BasicQueryMethodGenerator.CreateUpsertStatement(ctx);
|
||||
var updateMethod = BasicQueryMethodGenerator.CreateUpdateStatement(ctx);
|
||||
var deleteMethod = BasicQueryMethodGenerator.CreateDeleteStatement(ctx);
|
||||
var findMethod = BasicQueryMethodGenerator.CreateFindStatement(ctx);
|
||||
classBuilder
|
||||
.WithMethod(createMethod.MethodBuilder)
|
||||
.WithMethod(createIfNotExistsMethod.MethodBuilder)
|
||||
.WithMethod(insertMethod.MethodBuilder)
|
||||
.WithMethod(upsertMethod.MethodBuilder)
|
||||
.WithMethod(updateMethod.MethodBuilder)
|
||||
.WithMethod(deleteMethod.MethodBuilder)
|
||||
.WithMethod(findMethod.MethodBuilder)
|
||||
.WithMethod(CreateParameterMethodGenerator.CreateParameterMethod());
|
||||
|
||||
// Generate conversion properties
|
||||
var conversionProperties = ctx.Fields
|
||||
.Where(f => f.RequiresConversion)
|
||||
.GroupBy(f => f.PropertyType)
|
||||
.Select(g => g.First())
|
||||
.Select(ConversionPropertyGenerator.GenerateConversionProperties)
|
||||
.OfType<(PropertyBuilder ConvertTo, PropertyBuilder ConvertFrom)>();
|
||||
|
||||
foreach((var convertTo, var convertFrom) in conversionProperties)
|
||||
{
|
||||
classBuilder.WithProperty(convertTo);
|
||||
classBuilder.WithProperty(convertFrom);
|
||||
}
|
||||
|
||||
// Replace placeholders with formatted command text
|
||||
var syntax = builder.Build();
|
||||
var source = syntax.ToFullString()
|
||||
.Replace(createMethod.Placeholder, createMethod.CommandText)
|
||||
.Replace(createIfNotExistsMethod.Placeholder, createIfNotExistsMethod.CommandText)
|
||||
.Replace(insertMethod.Placeholder, insertMethod.CommandText)
|
||||
.Replace(upsertMethod.Placeholder, upsertMethod.CommandText)
|
||||
.Replace(updateMethod.Placeholder, updateMethod.CommandText)
|
||||
.Replace(deleteMethod.Placeholder, deleteMethod.CommandText)
|
||||
.Replace(findMethod.Placeholder, findMethod.CommandText);
|
||||
|
||||
sourceProductionContext.AddSource($"{contextClassName}.g.cs", source);
|
||||
}
|
||||
|
||||
private static TableContext GetTableContext(ClassDeclarationSyntax classDeclarationSyntax, Compilation compilation)
|
||||
{
|
||||
var fields = SyntaxParsers.GetFieldsFromClass(classDeclarationSyntax, compilation);
|
||||
var primaryKeyField = fields.First(f => f.IsPrimaryKey);
|
||||
var tableName = SyntaxParsers.GetTableName(classDeclarationSyntax);
|
||||
var tableUnqiues = SyntaxParsers.GetTableUniques(classDeclarationSyntax);
|
||||
return new TableContext(tableName, classDeclarationSyntax.Identifier.ToString(), primaryKeyField, fields, tableUnqiues);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Sybil;
|
||||
using System;
|
||||
|
||||
namespace Squealify;
|
||||
|
||||
[Generator(LanguageNames.CSharp)]
|
||||
public sealed class UniqueAttributeGenerator : IIncrementalGenerator
|
||||
{
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
context.RegisterPostInitializationOutput(GenerateAttribute);
|
||||
}
|
||||
|
||||
private static void GenerateAttribute(IncrementalGeneratorPostInitializationContext context)
|
||||
{
|
||||
var columnUniqueBuilder = SyntaxBuilder.CreateCompilationUnit()
|
||||
.WithNamespace(SyntaxBuilder.CreateNamespace(Constants.Namespace)
|
||||
.WithClass(SyntaxBuilder.CreateClass(Constants.ColumnUniqueAttributeName)
|
||||
.WithBaseClass(nameof(Attribute))
|
||||
.WithModifiers($"{Constants.Public} {Constants.Sealed}")
|
||||
.WithConstructor(SyntaxBuilder.CreateConstructor(Constants.ColumnUniqueAttributeName)
|
||||
.WithModifier(Constants.Public))
|
||||
.WithAttribute(SyntaxBuilder.CreateAttribute("AttributeUsage")
|
||||
.WithArgument(AttributeTargets.Property)
|
||||
.WithArgument("Inherited", false)
|
||||
.WithArgument("AllowMultiple", false))));
|
||||
var columnUniqueSource = columnUniqueBuilder.Build().ToFullString();
|
||||
context.AddSource($"{Constants.ColumnUniqueAttributeName}.g.cs", columnUniqueSource);
|
||||
|
||||
var tableUniqueBuilder = SyntaxBuilder.CreateCompilationUnit()
|
||||
.WithNamespace(SyntaxBuilder.CreateNamespace(Constants.Namespace)
|
||||
.WithClass(SyntaxBuilder.CreateClass(Constants.TableUniqueAttributeName)
|
||||
.WithBaseClass(nameof(Attribute))
|
||||
.WithModifiers($"{Constants.Public} {Constants.Sealed}")
|
||||
.WithConstructor(SyntaxBuilder.CreateConstructor(Constants.TableUniqueAttributeName)
|
||||
.WithModifier(Constants.Public)
|
||||
.WithParameter($"{Constants.Params} {Constants.StringArrayType}", Constants.TableUniqueArgumentName)
|
||||
.WithBody($"this.{Constants.TableUniqueProperty} = {Constants.TableUniqueArgumentName};"))
|
||||
.WithAttribute(SyntaxBuilder.CreateAttribute("AttributeUsage")
|
||||
.WithArgument(AttributeTargets.Class)
|
||||
.WithArgument("Inherited", false)
|
||||
.WithArgument("AllowMultiple", true))
|
||||
.WithProperty(SyntaxBuilder.CreateProperty(Constants.StringArrayType, Constants.TableUniqueProperty)
|
||||
.WithModifiers(Constants.Public)
|
||||
.WithAccessor(SyntaxBuilder.CreateGetter()))));
|
||||
var tableUniqueSource = tableUniqueBuilder.Build().ToFullString();
|
||||
context.AddSource($"{Constants.TableUniqueAttributeName}.g.cs", tableUniqueSource);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Sybil;
|
||||
using System;
|
||||
|
||||
namespace Squealify;
|
||||
|
||||
[Generator(LanguageNames.CSharp)]
|
||||
public sealed class VarcharAttributeGenerator : IIncrementalGenerator
|
||||
{
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
context.RegisterPostInitializationOutput(GenerateAttribute);
|
||||
}
|
||||
|
||||
private static void GenerateAttribute(IncrementalGeneratorPostInitializationContext context)
|
||||
{
|
||||
var builder = SyntaxBuilder.CreateCompilationUnit()
|
||||
.WithNamespace(SyntaxBuilder.CreateNamespace(Constants.Namespace)
|
||||
.WithClass(SyntaxBuilder.CreateClass(Constants.VarcharAttributeName)
|
||||
.WithBaseClass(nameof(Attribute))
|
||||
.WithModifiers($"{Constants.Public} {Constants.Sealed}")
|
||||
.WithConstructor(SyntaxBuilder.CreateConstructor(Constants.VarcharAttributeName)
|
||||
.WithModifier(Constants.Public)
|
||||
.WithBody($"this.{Constants.LengthProperty} = 255;"))
|
||||
.WithConstructor(SyntaxBuilder.CreateConstructor(Constants.VarcharAttributeName)
|
||||
.WithModifier(Constants.Public)
|
||||
.WithParameter(Constants.ByteType, Constants.LengthArgumentName)
|
||||
.WithBody($"this.{Constants.LengthProperty} = {Constants.LengthArgumentName};"))
|
||||
.WithAttribute(SyntaxBuilder.CreateAttribute("AttributeUsage")
|
||||
.WithArgument(AttributeTargets.Property)
|
||||
.WithArgument("Inherited", false)
|
||||
.WithArgument("AllowMultiple", false))
|
||||
.WithProperty(SyntaxBuilder.CreateProperty(Constants.ByteType, Constants.LengthProperty)
|
||||
.WithModifier(Constants.Public)
|
||||
.WithAccessor(SyntaxBuilder.CreateGetter()))));
|
||||
|
||||
var syntax = builder.Build();
|
||||
var source = syntax.ToFullString();
|
||||
context.AddSource($"{Constants.VarcharAttributeName}.g.cs", source);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user