Add project files.

This commit is contained in:
2025-04-15 16:26:12 +02:00
parent adef1fb47e
commit cfcd9b7bb9
22 changed files with 1546 additions and 0 deletions
+139
View File
@@ -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
+38
View File
@@ -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);
}
}
+96
View File
@@ -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";
}
+31
View File
@@ -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;
}
+9
View File
@@ -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;
}
+11
View File
@@ -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;
}
+7
View File
@@ -0,0 +1,7 @@
using System.Collections.Immutable;
namespace Squealify.Contexts;
public readonly struct TableUniqueContext(ImmutableArray<string> columnNames)
{
public readonly ImmutableArray<string> ColumnNames = columnNames;
}
+73
View File
@@ -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[]" }
};
}
+18
View File
@@ -0,0 +1,18 @@
namespace Squealify;
public enum DataTypes
{
SMALLINT,
INTEGER,
BIGINT,
REAL,
DOUBLE_PRECISION,
DECIMAL,
CHAR,
VARCHAR,
TEXT,
DATE,
TIME,
TIMESTAMP,
BOOLEAN,
BLOB
}
+41
View File
@@ -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);
}
}
+32
View File
@@ -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);
}
}
+34
View File
@@ -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>
+25
View File
@@ -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
+285
View File
@@ -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);
}
}
+264
View File
@@ -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;
}
}
+29
View File
@@ -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);
}
}
+21
View File
@@ -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;
}
}
+89
View File
@@ -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());
}
}
+41
View File
@@ -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);
}
}
+171
View File
@@ -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);
}
}
+50
View File
@@ -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);
}
}
+42
View File
@@ -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);
}
}