using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace PCL.Core.SourceGenerators; public static class SharedExtensions { public static string ToLiteral(this string str) => SymbolDisplay.FormatLiteral(str, true); public static string? ToPrimitive(this object? obj) => SymbolDisplay.FormatPrimitive(obj, true, false); public static int GetDeclarationOrder(this ISymbol symbol) { var loc = symbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax().GetLocation(); return loc?.SourceSpan.Start ?? int.MaxValue; } extension(INamedTypeSymbol type) { public bool IsPartial() { foreach (var decl in type.DeclaringSyntaxReferences) { if (decl.GetSyntax() is ClassDeclarationSyntax { Modifiers: { } modifiers } && modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword))) return true; } return false; } public bool IsNestedWithin(INamedTypeSymbol potentialContainer) { var t = type.ContainingType; while (t is not null) { if (SymbolEqualityComparer.Default.Equals(t, potentialContainer)) return true; t = t.ContainingType; } return false; } public bool IsAttribute() { var baseType = type.BaseType; while (baseType is not null) { if (baseType.ToDisplayString() == "System.Attribute") return true; baseType = baseType.BaseType; } return false; } public int GenerateTypeHeader(StringBuilder sb) { var ctnTypes = new Stack(); for (var ctnType = type.ContainingType; ctnType is not null; ctnType = ctnType.ContainingType) ctnTypes.Push(ctnType); // namespace var ns = type.ContainingNamespace?.ToDisplayString(); var indent = 0; if (!string.IsNullOrEmpty(ns)) { sb.Append("namespace ").Append(ns).AppendLine(); sb.AppendLine("{"); indent++; } // outer classes foreach (var containingType in ctnTypes) { sb.Append(' ', indent * 4).Append("partial class ").Append(containingType.Name).AppendLine(); sb.Append(' ', indent * 4).AppendLine("{"); indent++; } // class sb.Append(' ', indent * 4).Append("partial class ").Append(type.Name).AppendLine(); sb.Append(' ', indent * 4).AppendLine("{"); return indent + 1; } } public static string RenderDefaultValueCode(this SemanticModel sm, ExpressionSyntax expr) { if (expr is LiteralExpressionSyntax || expr.IsNegativeNumeric()) return expr.ToString(); if (expr is TypeOfExpressionSyntax toe) { var type = sm.GetTypeInfo(toe.Type).Type; if (type is not null) return "typeof(" + type.GetFullyQualifiedName() + ")"; return expr.ToString(); } if (expr is InvocationExpressionSyntax { Expression: IdentifierNameSyntax { Identifier.ValueText: "nameof" }, ArgumentList.Arguments.Count: 1 } inv) { var targetExpr = inv.ArgumentList.Arguments[0].Expression; var sym = sm.GetSymbolInfo(targetExpr).Symbol; if (sym is not null) { return "nameof(" + sym.GetQualifiedSymbolName() + ")"; } return expr.ToString(); } var s = sm.GetSymbolInfo(expr).Symbol; if (s is IFieldSymbol fs) { return fs.GetQualifiedSymbolName(); } return expr.ToString(); } public static bool IsNegativeNumeric(this ExpressionSyntax expr) { return expr is PrefixUnaryExpressionSyntax p && p.IsKind(SyntaxKind.UnaryMinusExpression) && p.Operand is LiteralExpressionSyntax l && l.IsKind(SyntaxKind.NumericLiteralExpression); } extension(ISymbol symbol) { public string GetQualifiedSymbolName() { if (symbol is ITypeSymbol ts) return ts.GetFullyQualifiedName(); var parts = new Stack(); parts.Push(symbol.Name); var t = symbol.ContainingType; while (t is not null) { parts.Push(t.Name); t = t.ContainingType; } var ns = symbol.ContainingNamespace?.ToDisplayString(); if (!string.IsNullOrEmpty(ns)) parts.Push(ns!); return string.Join(".", parts); } } private static readonly SymbolDisplayFormat _SimplifiedTypeNameFormat = new( globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.OmittedAsContaining, typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers | SymbolDisplayMiscellaneousOptions.CollapseTupleTypes | SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier | SymbolDisplayMiscellaneousOptions.UseSpecialTypes, genericsOptions: SymbolDisplayGenericsOptions.None ); private static readonly SymbolDisplayFormat _FullQualifiedNameFormat = new( typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters, miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers | SymbolDisplayMiscellaneousOptions.UseSpecialTypes ); extension(ITypeSymbol type) { public string GetSimplifiedTypeName() { return type.ToDisplayString(_SimplifiedTypeNameFormat); } public string GetFullyQualifiedName() { if (type is INamedTypeSymbol { OriginalDefinition.SpecialType: SpecialType.System_Nullable_T, TypeArguments.Length: 1 } nt) { var inner = nt.TypeArguments[0]; return inner.GetFullyQualifiedName() + "?"; } if (type.TryGetSpecialTypeKeyword(out var keyword)) return keyword; return type.ToDisplayString(_FullQualifiedNameFormat); } public bool TryGetSpecialTypeKeyword(out string keyword) { switch (type.SpecialType) { case SpecialType.System_Boolean: keyword = "bool"; return true; case SpecialType.System_Byte: keyword = "byte"; return true; case SpecialType.System_SByte: keyword = "sbyte"; return true; case SpecialType.System_Int16: keyword = "short"; return true; case SpecialType.System_UInt16: keyword = "ushort"; return true; case SpecialType.System_Int32: keyword = "int"; return true; case SpecialType.System_UInt32: keyword = "uint"; return true; case SpecialType.System_Int64: keyword = "long"; return true; case SpecialType.System_UInt64: keyword = "ulong"; return true; case SpecialType.System_IntPtr: keyword = "nint"; return true; case SpecialType.System_UIntPtr: keyword = "nuint"; return true; case SpecialType.System_Char: keyword = "char"; return true; case SpecialType.System_String: keyword = "string"; return true; case SpecialType.System_Object: keyword = "object"; return true; case SpecialType.System_Single: keyword = "float"; return true; case SpecialType.System_Double: keyword = "double"; return true; case SpecialType.System_Decimal: keyword = "decimal"; return true; default: keyword = ""; return false; } } } public static string GetQualifiedPropertyAccess(this IPropertySymbol prop) { var owner = prop.ContainingType.GetFullyQualifiedName(); return owner + "." + prop.Name; } public static bool IsAwaitable(this IMethodSymbol method) { // TODO this is a very naive implementation. return method.ReturnType.GetSimplifiedTypeName() == "System.Threading.Tasks.Task"; } public static string CorrectConfigTypeName(this string typeName, out string? fullTypeName) { var isArgConfig = typeName.StartsWith("PCL.Core.App.Configuration.ArgConfig<"); if (isArgConfig) { fullTypeName = typeName; typeName = typeName.Substring(37, typeName.Length - 38); } else fullTypeName = null; return typeName; } extension(string str) { public string SnakeIdToPascal() { var sb = new StringBuilder(); foreach (var part in str.Split('-')) { if (part.Length == 0) continue; sb.Append(char.ToUpper(part[0])).Append(part.Substring(1)); } return sb.ToString(); } } }