diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
new file mode 100644
index 0000000..6fecb38
--- /dev/null
+++ b/.github/workflows/dotnet.yml
@@ -0,0 +1,66 @@
+name: .NET
+
+on:
+ push:
+ branches: [ main, dev, feature/*, fix/*, release/* ]
+
+ pull_request:
+ branches: [ main ]
+
+ release:
+ types: [ published ]
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: |
+ 9.0.x
+
+ # Create Local NuGet Source
+
+ - name: Create Local NuGet Directory
+ run: mkdir ~/nuget
+
+ - name: Add Local Nuget Source
+ run: dotnet nuget add source ~/nuget
+
+ # CodeAnalysis.Extensions
+
+ - name: Restore CodeAnalysis.Extensions
+ run: dotnet restore ./src/*/CodeAnalysis.Extensions.csproj
+
+ - name: Build CodeAnalysis.Extensions
+ run: dotnet build ./src/*/CodeAnalysis.Extensions.csproj --no-restore -c Release
+
+ - name: Pack CodeAnalysis.Extensions
+ run: dotnet pack ./src/*/CodeAnalysis.Extensions.csproj --no-restore -o ~/nuget -c Release
+
+ # CodeAnalysis.SourceBuilder
+
+ - name: Restore CodeAnalysis.SourceBuilder
+ run: dotnet restore ./src/*/CodeAnalysis.SourceBuilder.csproj
+
+ - name: Build CodeAnalysis.SourceBuilder
+ run: dotnet build ./src/*/CodeAnalysis.SourceBuilder.csproj --no-restore -c Release
+
+ - name: Pack CodeAnalysis.SourceBuilder
+ run: dotnet pack ./src/*/CodeAnalysis.SourceBuilder.csproj --no-restore -o ~/nuget -c Release
+
+ # Push
+
+ - name: Push Packages
+ if: ${{ github.event_name == 'release' }}
+ run: >
+ dotnet nuget push "../../../nuget/*.nupkg"
+ -s https://api.nuget.org/v3/index.json
+ -k ${{ secrets.NuGetSourcePassword }}
+ --skip-duplicate
diff --git a/CodeAnalysis.sln b/CodeAnalysis.sln
new file mode 100644
index 0000000..8b290b0
--- /dev/null
+++ b/CodeAnalysis.sln
@@ -0,0 +1,28 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeAnalysis.Extensions", "src\CodeAnalysis.Extensions\CodeAnalysis.Extensions.csproj", "{B6C02DFB-D60E-4199-B880-F4CB6037B428}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeAnalysis.SourceBuilder", "src\CodeAnalysis.SourceBuilder\CodeAnalysis.SourceBuilder.csproj", "{AFF0B5A4-3339-4CA8-B875-0D66CB784005}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {B6C02DFB-D60E-4199-B880-F4CB6037B428}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B6C02DFB-D60E-4199-B880-F4CB6037B428}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B6C02DFB-D60E-4199-B880-F4CB6037B428}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B6C02DFB-D60E-4199-B880-F4CB6037B428}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AFF0B5A4-3339-4CA8-B875-0D66CB784005}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AFF0B5A4-3339-4CA8-B875-0D66CB784005}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AFF0B5A4-3339-4CA8-B875-0D66CB784005}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AFF0B5A4-3339-4CA8-B875-0D66CB784005}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/README.md b/README.md
index c4b7717..162aa22 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,2 @@
-# early-infra-codeanalysis
-EarlyFuncPack Infra.CodeAnalysis is an infrastructure library for .NET for use in building source generators
+# early-codeanalysis
+EarlyFuncPack CodeAnalysis is a library for .NET for use in building source generators
diff --git a/src/CodeAnalysis.Extensions/CodeAnalysis.Extensions.csproj b/src/CodeAnalysis.Extensions/CodeAnalysis.Extensions.csproj
new file mode 100644
index 0000000..e6811d4
--- /dev/null
+++ b/src/CodeAnalysis.Extensions/CodeAnalysis.Extensions.csproj
@@ -0,0 +1,45 @@
+
+
+
+ netstandard2.0
+ latest
+ true
+ disable
+ enable
+ true
+ true
+ $(NoWarn);IDE0130;IDE0290
+ true
+ LICENSE
+ README.md
+ https://github.com/pfpack/early-codeanalysis
+ https://github.com/pfpack/early-codeanalysis
+ pfpack
+ Andrei Sergeev, Pavel Moskovoy
+ Copyright © 2025 Andrei Sergeev, Pavel Moskovoy
+ EarlyFuncPack CodeAnalysis is a library for .NET for use in building source generators.
+ PrimeFuncPack
+ EarlyFuncPack.CodeAnalysis.Extensions
+ 0.0.1
+
+
+
+
+ True
+
+
+
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/CodeAnalysis.Extensions/Extensions/CodeAnalysisExtensions.cs b/src/CodeAnalysis.Extensions/Extensions/CodeAnalysisExtensions.cs
new file mode 100644
index 0000000..f19b4a4
--- /dev/null
+++ b/src/CodeAnalysis.Extensions/Extensions/CodeAnalysisExtensions.cs
@@ -0,0 +1,23 @@
+namespace PrimeFuncPack;
+
+public static partial class CodeAnalysisExtensions
+{
+ private const string SystemNamespace = "System";
+
+ private const string SystemTextJsonSerializationNamespace = "System.Text.Json.Serialization";
+
+ private static string InnerWithCamelCase(this string source)
+ {
+ if (string.IsNullOrEmpty(source))
+ {
+ return string.Empty;
+ }
+
+ if (source.Length is 1)
+ {
+ return source.ToLowerInvariant();
+ }
+
+ return source[0].ToString().ToLowerInvariant() + source.Substring(1);
+ }
+}
\ No newline at end of file
diff --git a/src/CodeAnalysis.Extensions/Extensions/Ext.AsStringSourceCodeOr.cs b/src/CodeAnalysis.Extensions/Extensions/Ext.AsStringSourceCodeOr.cs
new file mode 100644
index 0000000..bcff3b7
--- /dev/null
+++ b/src/CodeAnalysis.Extensions/Extensions/Ext.AsStringSourceCodeOr.cs
@@ -0,0 +1,18 @@
+namespace PrimeFuncPack;
+
+partial class CodeAnalysisExtensions
+{
+ public static string AsStringSourceCodeOr(this string? source, string defaultSourceCode = "\"\"")
+ =>
+ string.IsNullOrEmpty(source) ? defaultSourceCode : InnerWrapStringSourceCode(source!);
+
+ public static string AsStringSourceCodeOrStringEmpty(this string? source)
+ =>
+ string.IsNullOrEmpty(source) ? "string.Empty" : InnerWrapStringSourceCode(source!);
+
+ private static string InnerWrapStringSourceCode(string source)
+ {
+ var encodedString = source.Replace("\"", "\\\"");
+ return $"\"{encodedString}\"";
+ }
+}
\ No newline at end of file
diff --git a/src/CodeAnalysis.Extensions/Extensions/Ext.Attribute.GetConstructorArgumentValue.cs b/src/CodeAnalysis.Extensions/Extensions/Ext.Attribute.GetConstructorArgumentValue.cs
new file mode 100644
index 0000000..a558255
--- /dev/null
+++ b/src/CodeAnalysis.Extensions/Extensions/Ext.Attribute.GetConstructorArgumentValue.cs
@@ -0,0 +1,20 @@
+using Microsoft.CodeAnalysis;
+
+namespace PrimeFuncPack;
+
+partial class CodeAnalysisExtensions
+{
+ public static T? GetConstructorArgumentValue(this AttributeData attributeData, int constructorOrder)
+ =>
+ (T?)attributeData.InnerGetConstructorArgumentValue(constructorOrder);
+
+ private static object? InnerGetConstructorArgumentValue(this AttributeData? attributeData, int constructorOrder)
+ {
+ if (attributeData?.ConstructorArguments.Length <= constructorOrder)
+ {
+ return default;
+ }
+
+ return attributeData?.ConstructorArguments[constructorOrder].Value;
+ }
+}
\ No newline at end of file
diff --git a/src/CodeAnalysis.Extensions/Extensions/Ext.Attribute.GetNamedArgumentValue.cs b/src/CodeAnalysis.Extensions/Extensions/Ext.Attribute.GetNamedArgumentValue.cs
new file mode 100644
index 0000000..914561d
--- /dev/null
+++ b/src/CodeAnalysis.Extensions/Extensions/Ext.Attribute.GetNamedArgumentValue.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+
+namespace PrimeFuncPack;
+
+partial class CodeAnalysisExtensions
+{
+ public static T? GetNamedArgumentValue(this AttributeData attributeData, string propertyName)
+ =>
+ (T?)attributeData.InnerGetNamedArgumentValue(propertyName);
+
+ private static object? InnerGetNamedArgumentValue(this AttributeData? attributeData, string propertyName)
+ {
+ return attributeData?.NamedArguments.FirstOrDefault(IsNameMatched).Value.Value;
+
+ bool IsNameMatched(KeyValuePair pair)
+ =>
+ string.Equals(pair.Key, propertyName, StringComparison.InvariantCulture);
+ }
+}
\ No newline at end of file
diff --git a/src/CodeAnalysis.Extensions/Extensions/Ext.GetCollectionType.cs b/src/CodeAnalysis.Extensions/Extensions/Ext.GetCollectionType.cs
new file mode 100644
index 0000000..1f91144
--- /dev/null
+++ b/src/CodeAnalysis.Extensions/Extensions/Ext.GetCollectionType.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+
+namespace PrimeFuncPack;
+
+partial class CodeAnalysisExtensions
+{
+ public static ITypeSymbol? GetCollectionTypeOrDefault(this ITypeSymbol typeSymbol)
+ {
+ if (typeSymbol is IArrayTypeSymbol arrayTypeSymbol)
+ {
+ return arrayTypeSymbol.ElementType;
+ }
+
+ if (typeSymbol is not INamedTypeSymbol namedTypeSymbol)
+ {
+ return null;
+ }
+
+ var enumerableInterface = namedTypeSymbol.AllInterfaces.FirstOrDefault(IsGenericEnumerable);
+ if (enumerableInterface is not null)
+ {
+ return enumerableInterface.TypeArguments[0];
+ }
+
+ return typeSymbol.GetMembers().OfType().Where(IsEnumeratorMethod).Select(GetEnumeratorType).FirstOrDefault(NotNull);
+
+ static bool IsGenericEnumerable(INamedTypeSymbol symbol)
+ =>
+ InnerIsType(symbol, "System.Collections.Generic", "IEnumerable") && symbol.TypeArguments.Length is 1;
+
+ static bool IsEnumeratorMethod(IMethodSymbol methodSymbol)
+ =>
+ methodSymbol.IsGenericMethod is false &&
+ methodSymbol.Parameters.Length is 0 &&
+ string.Equals(methodSymbol.Name, "GetEnumerator", StringComparison.InvariantCulture);
+
+ static ITypeSymbol? GetEnumeratorType(IMethodSymbol methodSymbol)
+ =>
+ methodSymbol.ReturnType?.InnerGetEnumeratorTypeOrDefault();
+
+ static bool NotNull(ITypeSymbol? typeSymbol)
+ =>
+ typeSymbol is not null;
+ }
+
+ private static ITypeSymbol? InnerGetEnumeratorTypeOrDefault(this ITypeSymbol typeSymbol)
+ {
+ if (typeSymbol is not INamedTypeSymbol namedTypeSymbol)
+ {
+ return null;
+ }
+
+ var enumeratorInterface = namedTypeSymbol.AllInterfaces.FirstOrDefault(IsGenericEnumerator);
+ if (enumeratorInterface is not null)
+ {
+ return enumeratorInterface.TypeArguments[0];
+ }
+
+ if (namedTypeSymbol.GetMembers().OfType().Any(IsMoveNextMethod) is false)
+ {
+ return null;
+ }
+
+ return namedTypeSymbol.GetMembers().OfType().FirstOrDefault(IsCurrentProperty)?.Type;
+
+ static bool IsGenericEnumerator(INamedTypeSymbol symbol)
+ =>
+ InnerIsType(symbol, "System.Collections.Generic", "IEnumerator") && symbol.TypeArguments.Length is 1;
+
+ static bool IsMoveNextMethod(IMethodSymbol methodSymbol)
+ =>
+ methodSymbol.IsGenericMethod is false &&
+ methodSymbol.Parameters.Length is 0 &&
+ methodSymbol.ReturnType.InnerIsType(SystemNamespace, "Boolean") &&
+ string.Equals(methodSymbol.Name, "MoveNext", StringComparison.InvariantCulture);
+
+ static bool IsCurrentProperty(IPropertySymbol propertySymbol)
+ =>
+ string.Equals(propertySymbol.Name, "Current", StringComparison.InvariantCulture);
+ }
+}
\ No newline at end of file
diff --git a/src/CodeAnalysis.Extensions/Extensions/Ext.GetDisplayedData.cs b/src/CodeAnalysis.Extensions/Extensions/Ext.GetDisplayedData.cs
new file mode 100644
index 0000000..c6ab7a9
--- /dev/null
+++ b/src/CodeAnalysis.Extensions/Extensions/Ext.GetDisplayedData.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+
+namespace PrimeFuncPack;
+
+partial class CodeAnalysisExtensions
+{
+ public static DisplayedTypeData GetDisplayedData(this ITypeSymbol typeSymbol, bool withNullableSymbol = false)
+ =>
+ InnerGetDisplayedData(
+ typeSymbol: typeSymbol ?? throw new ArgumentNullException(nameof(typeSymbol)),
+ withNullableSymbol: withNullableSymbol);
+
+ private static DisplayedTypeData InnerGetDisplayedData(ITypeSymbol typeSymbol, bool withNullableSymbol)
+ {
+ var symbol = typeSymbol;
+ var nullableSymbol = string.Empty;
+
+ if (typeSymbol.InnerGetNullableBaseType() is ITypeSymbol baseTypeSymbol)
+ {
+ symbol = baseTypeSymbol;
+ if (withNullableSymbol)
+ {
+ nullableSymbol = "?";
+ }
+ }
+
+ if (symbol is IArrayTypeSymbol arrayTypeSymbol)
+ {
+ var elementTypeData = InnerGetChildrenDisplayedData(arrayTypeSymbol.ElementType);
+
+ List elementTypeNameParts = [elementTypeData.DisplayedTypeName];
+ elementTypeNameParts.AddRange(Enumerable.Repeat("[]", arrayTypeSymbol.Rank));
+
+ return new DisplayedTypeData(
+ allNamespaces: elementTypeData.AllNamespaces,
+ displayedTypeName: string.Concat(elementTypeNameParts) + nullableSymbol);
+ }
+
+ if (symbol is not INamedTypeSymbol namedTypeSymbol || namedTypeSymbol.TypeArguments.Length is not > 0)
+ {
+ var typeNamespace = symbol.ContainingNamespace?.ToString();
+ var typeNamespaces = new List(1);
+
+ if (string.IsNullOrEmpty(typeNamespace) is false)
+ {
+ typeNamespaces.Add(typeNamespace ?? string.Empty);
+ }
+
+ return new(
+ allNamespaces: typeNamespaces,
+ displayedTypeName: symbol.Name + nullableSymbol);
+ }
+
+ var argumentTypes = namedTypeSymbol.TypeArguments.Select(InnerGetChildrenDisplayedData);
+
+ return new(
+ allNamespaces: new List(argumentTypes.SelectMany(GetNamespaces))
+ {
+ symbol.ContainingNamespace.ToString()
+ },
+ displayedTypeName: $"{symbol.Name}<{string.Join(", ", argumentTypes.Select(GetName))}>{nullableSymbol}");
+
+ static DisplayedTypeData InnerGetChildrenDisplayedData(ITypeSymbol typeSymbol)
+ =>
+ InnerGetDisplayedData(typeSymbol, true);
+
+ static IEnumerable GetNamespaces(DisplayedTypeData typeData)
+ =>
+ typeData.AllNamespaces;
+
+ static string GetName(DisplayedTypeData typeData)
+ =>
+ typeData.DisplayedTypeName;
+ }
+
+ private static ITypeSymbol? InnerGetNullableBaseType(this ITypeSymbol typeSymbol)
+ {
+ if (typeSymbol is not INamedTypeSymbol namedTypeSymbol)
+ {
+ return null;
+ }
+
+ if (namedTypeSymbol.IsValueType is false)
+ {
+ return namedTypeSymbol.NullableAnnotation is NullableAnnotation.Annotated ? typeSymbol : null;
+ }
+
+ if (namedTypeSymbol.TypeArguments.Length is 1 && namedTypeSymbol.InnerIsType(SystemNamespace, "Nullable"))
+ {
+ return namedTypeSymbol.TypeArguments[0];
+ }
+
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/CodeAnalysis.Extensions/Extensions/Ext.GetEnumFields.cs b/src/CodeAnalysis.Extensions/Extensions/Ext.GetEnumFields.cs
new file mode 100644
index 0000000..edab470
--- /dev/null
+++ b/src/CodeAnalysis.Extensions/Extensions/Ext.GetEnumFields.cs
@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+
+namespace PrimeFuncPack;
+
+partial class CodeAnalysisExtensions
+{
+ public static IEnumerable GetEnumFields(this ITypeSymbol typeSymbol)
+ {
+ if (typeSymbol is null)
+ {
+ return [];
+ }
+
+ return typeSymbol.GetMembers().OfType().Where(IsPublic).Where(IsNameNotEmpty);
+
+ static bool IsPublic(IFieldSymbol fieldSymbol)
+ =>
+ fieldSymbol.DeclaredAccessibility is Accessibility.Public;
+
+ static bool IsNameNotEmpty(IFieldSymbol fieldSymbol)
+ =>
+ string.IsNullOrEmpty(fieldSymbol.Name) is false;
+ }
+}
\ No newline at end of file
diff --git a/src/CodeAnalysis.Extensions/Extensions/Ext.GetEnumUnderlyingType.cs b/src/CodeAnalysis.Extensions/Extensions/Ext.GetEnumUnderlyingType.cs
new file mode 100644
index 0000000..75de027
--- /dev/null
+++ b/src/CodeAnalysis.Extensions/Extensions/Ext.GetEnumUnderlyingType.cs
@@ -0,0 +1,10 @@
+using Microsoft.CodeAnalysis;
+
+namespace PrimeFuncPack;
+
+partial class CodeAnalysisExtensions
+{
+ public static INamedTypeSymbol? GetEnumUnderlyingType(this ITypeSymbol typeSymbol)
+ =>
+ typeSymbol is INamedTypeSymbol namedTypeSymbol ? namedTypeSymbol.EnumUnderlyingType : null;
+}
\ No newline at end of file
diff --git a/src/CodeAnalysis.Extensions/Extensions/Ext.GetJsonProperties.cs b/src/CodeAnalysis.Extensions/Extensions/Ext.GetJsonProperties.cs
new file mode 100644
index 0000000..77ce25e
--- /dev/null
+++ b/src/CodeAnalysis.Extensions/Extensions/Ext.GetJsonProperties.cs
@@ -0,0 +1,36 @@
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+
+namespace PrimeFuncPack;
+
+partial class CodeAnalysisExtensions
+{
+ public static IReadOnlyCollection GetJsonProperties(this ITypeSymbol typeSymbol)
+ {
+ if (typeSymbol is null)
+ {
+ return [];
+ }
+
+ return typeSymbol.GetMembers().OfType().Where(IsPublic).Where(IsNotIgnored).ToArray();
+
+ static bool IsPublic(IPropertySymbol propertySymbol)
+ =>
+ propertySymbol.DeclaredAccessibility is Accessibility.Public;
+
+ static bool IsNotIgnored(IPropertySymbol propertySymbol)
+ =>
+ propertySymbol.GetAttributes().Any(IsJsonIgnoreAttribute) is false;
+
+ static bool IsJsonIgnoreAttribute(AttributeData attributeData)
+ {
+ if (InnerIsType(attributeData?.AttributeClass, SystemTextJsonSerializationNamespace, "JsonIgnoreAttribute") is not true)
+ {
+ return false;
+ }
+
+ return attributeData.InnerGetNamedArgumentValue("Condition") is null or 1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CodeAnalysis.Extensions/Extensions/Ext.GetJsonPropertyName.cs b/src/CodeAnalysis.Extensions/Extensions/Ext.GetJsonPropertyName.cs
new file mode 100644
index 0000000..f3d9198
--- /dev/null
+++ b/src/CodeAnalysis.Extensions/Extensions/Ext.GetJsonPropertyName.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+
+namespace PrimeFuncPack;
+
+partial class CodeAnalysisExtensions
+{
+ public static string GetJsonPropertyName(this IPropertySymbol propertySymbol)
+ {
+ _ = propertySymbol ?? throw new ArgumentNullException(nameof(propertySymbol));
+
+ var jsonPropertyNameAttribute = propertySymbol.GetAttributes().FirstOrDefault(IsJsonPropertyNameAttribute);
+ if (jsonPropertyNameAttribute is not null)
+ {
+ var name = jsonPropertyNameAttribute.InnerGetConstructorArgumentValue(0)?.ToString();
+ if (string.IsNullOrEmpty(name) is false)
+ {
+ return name!;
+ }
+ }
+
+ return propertySymbol.Name.InnerWithCamelCase();
+
+ static bool IsJsonPropertyNameAttribute(AttributeData attributeData)
+ =>
+ InnerIsType(attributeData.AttributeClass, "System.Text.Json.Serialization", "JsonPropertyNameAttribute");
+ }
+}
\ No newline at end of file
diff --git a/src/CodeAnalysis.Extensions/Extensions/Ext.IsAnyType.cs b/src/CodeAnalysis.Extensions/Extensions/Ext.IsAnyType.cs
new file mode 100644
index 0000000..8cb7abd
--- /dev/null
+++ b/src/CodeAnalysis.Extensions/Extensions/Ext.IsAnyType.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+
+namespace PrimeFuncPack;
+
+partial class CodeAnalysisExtensions
+{
+ public static bool IsAnyType(this ITypeSymbol typeSymbol, string @namespace, params string[] types)
+ {
+ if (typeSymbol is null || types?.Length is not > 0)
+ {
+ return false;
+ }
+
+ if (string.Equals(typeSymbol.ContainingNamespace?.ToString(), @namespace, StringComparison.InvariantCulture) is false)
+ {
+ return false;
+ }
+
+ return types.Any(IsEqualToType);
+
+ bool IsEqualToType(string type)
+ =>
+ string.Equals(typeSymbol.Name, type, StringComparison.InvariantCulture);
+ }
+}
\ No newline at end of file
diff --git a/src/CodeAnalysis.Extensions/Extensions/Ext.IsType.cs b/src/CodeAnalysis.Extensions/Extensions/Ext.IsType.cs
new file mode 100644
index 0000000..867a211
--- /dev/null
+++ b/src/CodeAnalysis.Extensions/Extensions/Ext.IsType.cs
@@ -0,0 +1,17 @@
+using System;
+using Microsoft.CodeAnalysis;
+
+namespace PrimeFuncPack;
+
+partial class CodeAnalysisExtensions
+{
+ public static bool IsType(this ITypeSymbol typeSymbol, string @namespace, string typeName)
+ =>
+ InnerIsType(typeSymbol, @namespace, typeName);
+
+ private static bool InnerIsType(this ITypeSymbol? typeSymbol, string @namespace, string typeName)
+ =>
+ typeSymbol is not null &&
+ string.Equals(typeSymbol.ContainingNamespace?.ToString(), @namespace, StringComparison.InvariantCulture) &&
+ string.Equals(typeSymbol.Name, typeName, StringComparison.InvariantCulture);
+}
\ No newline at end of file
diff --git a/src/CodeAnalysis.Extensions/TypeData/DisplayedTypeData.cs b/src/CodeAnalysis.Extensions/TypeData/DisplayedTypeData.cs
new file mode 100644
index 0000000..2aa6b38
--- /dev/null
+++ b/src/CodeAnalysis.Extensions/TypeData/DisplayedTypeData.cs
@@ -0,0 +1,16 @@
+using System.Collections.Generic;
+
+namespace PrimeFuncPack;
+
+public sealed class DisplayedTypeData
+{
+ public DisplayedTypeData(IReadOnlyCollection allNamespaces, string displayedTypeName)
+ {
+ AllNamespaces = allNamespaces ?? [];
+ DisplayedTypeName = displayedTypeName ?? string.Empty;
+ }
+
+ public IReadOnlyCollection AllNamespaces { get; }
+
+ public string DisplayedTypeName { get; }
+}
\ No newline at end of file
diff --git a/src/CodeAnalysis.SourceBuilder/CodeAnalysis.SourceBuilder.csproj b/src/CodeAnalysis.SourceBuilder/CodeAnalysis.SourceBuilder.csproj
new file mode 100644
index 0000000..25c2362
--- /dev/null
+++ b/src/CodeAnalysis.SourceBuilder/CodeAnalysis.SourceBuilder.csproj
@@ -0,0 +1,41 @@
+
+
+
+ netstandard2.0
+ latest
+ true
+ disable
+ enable
+ true
+ true
+ $(NoWarn);IDE0130;IDE0290
+ true
+ LICENSE
+ README.md
+ https://github.com/pfpack/early-codeanalysis
+ https://github.com/pfpack/early-codeanalysis
+ pfpack
+ Andrei Sergeev, Pavel Moskovoy
+ Copyright © 2025 Andrei Sergeev, Pavel Moskovoy
+ EarlyFuncPack CodeAnalysis is a library for .NET for use in building source generators.
+ PrimeFuncPack
+ EarlyFuncPack.CodeAnalysis.SourceBuilder
+ 0.0.1
+
+
+
+
+ True
+
+
+
+ True
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.AddAlias.cs b/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.AddAlias.cs
new file mode 100644
index 0000000..bf41735
--- /dev/null
+++ b/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.AddAlias.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Linq;
+
+namespace PrimeFuncPack;
+
+partial class SourceBuilder
+{
+ public SourceBuilder AddAlias(string alias)
+ {
+ if (string.IsNullOrWhiteSpace(alias))
+ {
+ return this;
+ }
+
+ if (aliases.Any(AliasEquals))
+ {
+ return this;
+ }
+
+ aliases.Add(alias);
+ return this;
+
+ bool AliasEquals(string aliasValue)
+ =>
+ string.Equals(aliasValue, alias, StringComparison.InvariantCulture);
+ }
+}
\ No newline at end of file
diff --git a/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.AddUsing.cs b/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.AddUsing.cs
new file mode 100644
index 0000000..581e4fe
--- /dev/null
+++ b/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.AddUsing.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Linq;
+
+namespace PrimeFuncPack;
+
+partial class SourceBuilder
+{
+ public SourceBuilder AddUsing(params string[] usings)
+ {
+ if (usings?.Length is not > 0)
+ {
+ return this;
+ }
+
+ foreach (var @using in usings)
+ {
+ _ = InnerAddUsing(@using);
+ }
+
+ return this;
+ }
+
+ private SourceBuilder InnerAddUsing(string @using)
+ {
+ if (string.IsNullOrWhiteSpace(@using))
+ {
+ return this;
+ }
+
+ if (string.Equals(@using, @namespace, StringComparison.InvariantCulture))
+ {
+ return this;
+ }
+
+ if (@namespace.StartsWith(@using + ".", StringComparison.InvariantCulture))
+ {
+ return this;
+ }
+
+ if (usings.Any(UsingEquals))
+ {
+ return this;
+ }
+
+ usings.Add(@using);
+ return this;
+
+ bool UsingEquals(string usingValue)
+ =>
+ string.Equals(usingValue, @using, StringComparison.InvariantCulture);
+ }
+}
\ No newline at end of file
diff --git a/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.AppendCodeLine.cs b/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.AppendCodeLine.cs
new file mode 100644
index 0000000..2727003
--- /dev/null
+++ b/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.AppendCodeLine.cs
@@ -0,0 +1,21 @@
+namespace PrimeFuncPack;
+
+partial class SourceBuilder
+{
+ public SourceBuilder AppendCodeLine(params string[] codeLines)
+ {
+ if (codeLines?.Length is not > 0)
+ {
+ return this;
+ }
+
+ var builder = this;
+
+ foreach (var line in codeLines)
+ {
+ builder = InnerAppendLineWithTabulation(line);
+ }
+
+ return builder;
+ }
+}
\ No newline at end of file
diff --git a/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.AppendDirective.cs b/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.AppendDirective.cs
new file mode 100644
index 0000000..f0fe1e5
--- /dev/null
+++ b/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.AppendDirective.cs
@@ -0,0 +1,20 @@
+namespace PrimeFuncPack;
+
+partial class SourceBuilder
+{
+ public SourceBuilder AppendDirective(string preprocessorDirective)
+ {
+ if (string.IsNullOrWhiteSpace(preprocessorDirective))
+ {
+ return this;
+ }
+
+ if (codeBuilder.Length > 0)
+ {
+ _ = codeBuilder.AppendLine();
+ }
+
+ _ = codeBuilder.Append(preprocessorDirective);
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.AppendEmptyLine.cs b/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.AppendEmptyLine.cs
new file mode 100644
index 0000000..082f3c4
--- /dev/null
+++ b/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.AppendEmptyLine.cs
@@ -0,0 +1,10 @@
+namespace PrimeFuncPack;
+
+partial class SourceBuilder
+{
+ public SourceBuilder AppendEmptyLine()
+ {
+ _ = codeBuilder.AppendLine();
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.BeginArguments.cs b/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.BeginArguments.cs
new file mode 100644
index 0000000..ad3ffcb
--- /dev/null
+++ b/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.BeginArguments.cs
@@ -0,0 +1,10 @@
+namespace PrimeFuncPack;
+
+partial class SourceBuilder
+{
+ public SourceBuilder BeginArguments()
+ {
+ tabulationSize++;
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.BeginCodeBlock.cs b/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.BeginCodeBlock.cs
new file mode 100644
index 0000000..5fa07ad
--- /dev/null
+++ b/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.BeginCodeBlock.cs
@@ -0,0 +1,12 @@
+namespace PrimeFuncPack;
+
+partial class SourceBuilder
+{
+ public SourceBuilder BeginCodeBlock()
+ {
+ _ = InnerAppendLineWithTabulation("{");
+ tabulationSize++;
+
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.BeginCollectionExpression.cs b/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.BeginCollectionExpression.cs
new file mode 100644
index 0000000..58ff083
--- /dev/null
+++ b/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.BeginCollectionExpression.cs
@@ -0,0 +1,12 @@
+namespace PrimeFuncPack;
+
+partial class SourceBuilder
+{
+ public SourceBuilder BeginCollectionExpression()
+ {
+ _ = InnerAppendLineWithTabulation("[");
+ tabulationSize++;
+
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.BeginLambda.cs b/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.BeginLambda.cs
new file mode 100644
index 0000000..3ee022a
--- /dev/null
+++ b/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.BeginLambda.cs
@@ -0,0 +1,10 @@
+namespace PrimeFuncPack;
+
+partial class SourceBuilder
+{
+ public SourceBuilder BeginLambda()
+ {
+ tabulationSize++;
+ return InnerAppendLineWithTabulation("=>");
+ }
+}
\ No newline at end of file
diff --git a/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.Build.cs b/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.Build.cs
new file mode 100644
index 0000000..147a93a
--- /dev/null
+++ b/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.Build.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Linq;
+using System.Text;
+
+namespace PrimeFuncPack;
+
+partial class SourceBuilder
+{
+ public string Build()
+ {
+ var builder = new StringBuilder("// Auto-generated code by PrimeFuncPack").AppendLine().Append("#nullable enable");
+
+ if (usings.Count > 0)
+ {
+ builder = builder.AppendLine();
+ }
+
+ foreach (var @using in usings.OrderBy(GetNamespaceOrder))
+ {
+ builder = builder.AppendLine().Append("using").Append(' ').Append(@using).Append(';');
+ }
+
+ builder = builder.AppendLine().AppendLine().Append("namespace").Append(' ').Append(@namespace).Append(';');
+
+ if (aliases.Count > 0)
+ {
+ builder = builder.AppendLine();
+ }
+
+ foreach (var alias in aliases)
+ {
+ builder = builder.AppendLine().Append("using").Append(' ').Append(alias).Append(';');
+ }
+
+ if (codeBuilder.Length is not > 0)
+ {
+ return builder.ToString();
+ }
+
+ return builder.AppendLine().AppendLine().Append(codeBuilder).ToString();
+
+ static string GetNamespaceOrder(string @namespace)
+ =>
+ @namespace.StartsWith("System", StringComparison.InvariantCulture) ? "_" + @namespace : @namespace;
+ }
+}
\ No newline at end of file
diff --git a/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.EndArguments.cs b/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.EndArguments.cs
new file mode 100644
index 0000000..28afe41
--- /dev/null
+++ b/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.EndArguments.cs
@@ -0,0 +1,10 @@
+namespace PrimeFuncPack;
+
+partial class SourceBuilder
+{
+ public SourceBuilder EndArguments()
+ {
+ tabulationSize--;
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.EndCodeBlock.cs b/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.EndCodeBlock.cs
new file mode 100644
index 0000000..29e04a8
--- /dev/null
+++ b/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.EndCodeBlock.cs
@@ -0,0 +1,17 @@
+namespace PrimeFuncPack;
+
+partial class SourceBuilder
+{
+ public SourceBuilder EndCodeBlock(string? finalSymbol = default)
+ {
+ tabulationSize--;
+ _ = InnerAppendLineWithTabulation("}");
+
+ if (string.IsNullOrWhiteSpace(finalSymbol) is false)
+ {
+ _ = codeBuilder.Append(finalSymbol);
+ }
+
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.EndCollectionExpression.cs b/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.EndCollectionExpression.cs
new file mode 100644
index 0000000..2481025
--- /dev/null
+++ b/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.EndCollectionExpression.cs
@@ -0,0 +1,17 @@
+namespace PrimeFuncPack;
+
+partial class SourceBuilder
+{
+ public SourceBuilder EndCollectionExpression(string? finalSymbol = default)
+ {
+ tabulationSize--;
+ _ = InnerAppendLineWithTabulation("]");
+
+ if (string.IsNullOrWhiteSpace(finalSymbol) is false)
+ {
+ _ = codeBuilder.Append(finalSymbol);
+ }
+
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.EndLambda.cs b/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.EndLambda.cs
new file mode 100644
index 0000000..b318c2b
--- /dev/null
+++ b/src/CodeAnalysis.SourceBuilder/SourceBuilder/Builder.EndLambda.cs
@@ -0,0 +1,10 @@
+namespace PrimeFuncPack;
+
+partial class SourceBuilder
+{
+ public SourceBuilder EndLambda()
+ {
+ tabulationSize--;
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/src/CodeAnalysis.SourceBuilder/SourceBuilder/SourceBuilder.cs b/src/CodeAnalysis.SourceBuilder/SourceBuilder/SourceBuilder.cs
new file mode 100644
index 0000000..5c846e1
--- /dev/null
+++ b/src/CodeAnalysis.SourceBuilder/SourceBuilder/SourceBuilder.cs
@@ -0,0 +1,40 @@
+using System.Collections.Generic;
+using System.Text;
+
+namespace PrimeFuncPack;
+
+public sealed partial class SourceBuilder
+{
+ private const int TabulationLength = 4;
+
+ private readonly List usings = [];
+
+ private readonly string @namespace;
+
+ private readonly List aliases = [];
+
+ private readonly StringBuilder codeBuilder = new();
+
+ private int tabulationSize = 0;
+
+ public SourceBuilder(string? @namespace)
+ =>
+ this.@namespace = string.IsNullOrWhiteSpace(@namespace) ? "PrimeFuncPack" : @namespace!;
+
+ private SourceBuilder InnerAppendLineWithTabulation(string codeLine)
+ {
+ if (codeBuilder.Length > 0)
+ {
+ _ = codeBuilder.AppendLine();
+ }
+
+ if (tabulationSize > 0)
+ {
+ var tabulation = new string(' ', TabulationLength * tabulationSize);
+ _ = codeBuilder.Append(tabulation);
+ }
+
+ _ = codeBuilder.Append(codeLine);
+ return this;
+ }
+}
\ No newline at end of file