scan-modules.csx + scan-props.csx
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
#r "nuget: Microsoft.CodeAnalysis.CSharp, 4.11.0"
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
|
||||
if (Args.Count < 1)
|
||||
{
|
||||
Console.Error.WriteLine("Użycie: dotnet script scan-modules.csx -- <KatalogDll>");
|
||||
Console.Error.WriteLine("Przykład: dotnet script scan-modules.csx -- ./bin/Debug/net8.0");
|
||||
return 1;
|
||||
}
|
||||
|
||||
var dllDir = Path.GetFullPath(Args[0]);
|
||||
if (!Directory.Exists(dllDir))
|
||||
{
|
||||
Console.Error.WriteLine($"Katalog nie istnieje: {dllDir}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
var dllPaths = Directory.EnumerateFiles(dllDir, "*.dll", SearchOption.TopDirectoryOnly).ToList();
|
||||
if (dllPaths.Count == 0)
|
||||
{
|
||||
Console.Error.WriteLine($"Brak plików *.dll w katalogu: {dllDir}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
var refs = new List<MetadataReference>();
|
||||
var addedPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var p in dllPaths)
|
||||
{
|
||||
try
|
||||
{
|
||||
refs.Add(MetadataReference.CreateFromFile(p));
|
||||
addedPaths.Add(Path.GetFileName(p));
|
||||
}
|
||||
catch (Exception ex) { Console.Error.WriteLine($"# Pominięto {Path.GetFileName(p)}: {ex.Message}"); }
|
||||
}
|
||||
|
||||
var tpa = (AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") as string ?? "")
|
||||
.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var path in tpa)
|
||||
{
|
||||
var name = Path.GetFileName(path);
|
||||
if (addedPaths.Contains(name)) continue;
|
||||
try { refs.Add(MetadataReference.CreateFromFile(path)); addedPaths.Add(name); }
|
||||
catch { /* pomiń */ }
|
||||
}
|
||||
|
||||
var compilation = CSharpCompilation.Create("ScanModules")
|
||||
.WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
|
||||
.AddReferences(refs);
|
||||
|
||||
var modules = new List<INamedTypeSymbol>();
|
||||
foreach (var asmRef in compilation.References)
|
||||
{
|
||||
if (compilation.GetAssemblyOrModuleSymbol(asmRef) is not IAssemblySymbol asm) continue;
|
||||
foreach (var type in EnumerateAllTypes(asm.GlobalNamespace))
|
||||
{
|
||||
if (type.ContainingType != null) continue;
|
||||
if (type.TypeKind != TypeKind.Class) continue;
|
||||
if (!type.Name.EndsWith("Module")) continue;
|
||||
if (!InheritsFromModule(type)) continue;
|
||||
modules.Add(type);
|
||||
}
|
||||
}
|
||||
|
||||
modules = modules.OrderBy(m => m.ToDisplayString(), StringComparer.Ordinal).ToList();
|
||||
|
||||
Console.WriteLine("# Moduły i tabele (Soneta)");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"Znaleziono modułów: {modules.Count}");
|
||||
Console.WriteLine();
|
||||
|
||||
var totalRows = 0;
|
||||
foreach (var module in modules)
|
||||
{
|
||||
// Indeks zagnieżdżonych typów *Table — Row bez własnej klasy *Table nie jest realną tabelą
|
||||
// (jest subrow/abstrakcyjną klasą bazową), więc filtrujemy go z wyników.
|
||||
var tableClasses = module.GetTypeMembers()
|
||||
.Where(t => t.TypeKind == TypeKind.Class && t.Name.EndsWith("Table"))
|
||||
.ToDictionary(t => t.Name, StringComparer.Ordinal);
|
||||
|
||||
var rowClasses = module.GetTypeMembers()
|
||||
.Where(t => t.TypeKind == TypeKind.Class && t.Name.EndsWith("Row"))
|
||||
.Where(t => tableClasses.ContainsKey(
|
||||
t.Name.Substring(0, t.Name.Length - "Row".Length) + "Table"))
|
||||
.OrderBy(t => t.Name, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
|
||||
var moduleShortName = module.Name.EndsWith("Module")
|
||||
? module.Name.Substring(0, module.Name.Length - "Module".Length)
|
||||
: module.Name;
|
||||
Console.WriteLine($"## {moduleShortName}");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"- Klasa: `{module.ToDisplayString()}`");
|
||||
Console.WriteLine();
|
||||
var modCaption = GetAttributeFirstString(module, "CaptionAttribute");
|
||||
var modDescription = GetAttributeFirstString(module, "DescriptionAttribute");
|
||||
if (!string.IsNullOrEmpty(modCaption)) Console.WriteLine($"- Tytuł: {modCaption}");
|
||||
if (!string.IsNullOrEmpty(modDescription)) Console.WriteLine($"- Opis: {modDescription}");
|
||||
Console.WriteLine($"- Tabel: {rowClasses.Count}");
|
||||
Console.WriteLine();
|
||||
|
||||
if (rowClasses.Count == 0)
|
||||
{
|
||||
Console.WriteLine("_Brak klas `*Row` w tym module._");
|
||||
Console.WriteLine();
|
||||
continue;
|
||||
}
|
||||
|
||||
Console.WriteLine("| RowType | TableType | Guided | Tytuł | Opis |");
|
||||
Console.WriteLine("|---------|-----------|--------|-------|------|");
|
||||
foreach (var row in rowClasses)
|
||||
{
|
||||
var rowType = row.Name.EndsWith("Row")
|
||||
? row.Name.Substring(0, row.Name.Length - "Row".Length)
|
||||
: row.Name;
|
||||
|
||||
var tableProp = FindMemberInherited(row, "Table") as IPropertySymbol;
|
||||
var tableType = tableProp?.Type is INamedTypeSymbol nt
|
||||
? nt.Name
|
||||
: (tableProp?.Type?.ToDisplayString() ?? "");
|
||||
|
||||
// Atrybuty Caption/Description siedzą na klasie *Table zagnieżdżonej w module
|
||||
// (np. `HandelModule.DokumentHandlowyTable`). Klasa *Row jest fallbackiem na wypadek,
|
||||
// gdyby konwencja w przyszłości się zmieniła.
|
||||
tableClasses.TryGetValue(rowType + "Table", out var tableCls);
|
||||
var caption = GetAttributeFirstString(tableCls, "CaptionAttribute");
|
||||
if (string.IsNullOrEmpty(caption))
|
||||
caption = GetAttributeFirstString(row, "CaptionAttribute");
|
||||
var description = GetAttributeFirstString(tableCls, "DescriptionAttribute");
|
||||
if (string.IsNullOrEmpty(description))
|
||||
description = GetAttributeFirstString(row, "DescriptionAttribute");
|
||||
|
||||
var guided = tableCls != null && InheritsFromGuidedOrExportedTable(tableCls)
|
||||
? "tak"
|
||||
: "";
|
||||
|
||||
Console.WriteLine($"| {rowType} | {tableType} | {guided} | {EscapeCell(caption)} | {EscapeCell(description)} |");
|
||||
totalRows++;
|
||||
}
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
Console.WriteLine($"_Łącznie tabel: {totalRows}_");
|
||||
return 0;
|
||||
|
||||
static IEnumerable<INamedTypeSymbol> EnumerateAllTypes(INamespaceSymbol ns)
|
||||
{
|
||||
foreach (var t in ns.GetTypeMembers()) yield return t;
|
||||
foreach (var sub in ns.GetNamespaceMembers())
|
||||
foreach (var t in EnumerateAllTypes(sub)) yield return t;
|
||||
}
|
||||
|
||||
static bool InheritsFromGuidedOrExportedTable(INamedTypeSymbol type)
|
||||
{
|
||||
for (var t = type.BaseType; t != null && t.SpecialType != SpecialType.System_Object; t = t.BaseType)
|
||||
{
|
||||
if (t.Name == "GuidedTable" || t.Name == "ExportedTable") return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool InheritsFromModule(INamedTypeSymbol type)
|
||||
{
|
||||
for (var t = type.BaseType; t != null && t.SpecialType != SpecialType.System_Object; t = t.BaseType)
|
||||
{
|
||||
if (t.Name == "Module" && t.ContainingNamespace?.ToDisplayString() == "Soneta.Business")
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static ISymbol FindMemberInherited(INamedTypeSymbol type, string name)
|
||||
{
|
||||
for (var t = type; t != null && t.SpecialType != SpecialType.System_Object; t = t.BaseType)
|
||||
{
|
||||
var m = t.GetMembers(name).FirstOrDefault();
|
||||
if (m != null) return m;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static string GetAttributeFirstString(ISymbol symbol, string attributeTypeName)
|
||||
{
|
||||
if (symbol == null) return "";
|
||||
var shortName = attributeTypeName.EndsWith("Attribute")
|
||||
? attributeTypeName.Substring(0, attributeTypeName.Length - "Attribute".Length)
|
||||
: attributeTypeName;
|
||||
var longName = shortName + "Attribute";
|
||||
foreach (var a in symbol.GetAttributes())
|
||||
{
|
||||
if (a.AttributeClass == null) continue;
|
||||
var n = a.AttributeClass.Name;
|
||||
if (!string.Equals(n, shortName, StringComparison.Ordinal)
|
||||
&& !string.Equals(n, longName, StringComparison.Ordinal))
|
||||
continue;
|
||||
foreach (var arg in a.ConstructorArguments)
|
||||
{
|
||||
if (arg.Kind == TypedConstantKind.Primitive && arg.Value is string s)
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static string EscapeCell(string s)
|
||||
{
|
||||
if (string.IsNullOrEmpty(s)) return "";
|
||||
return s.Replace("\\", "\\\\").Replace("|", "\\|").Replace("\r", " ").Replace("\n", " ");
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
#r "nuget: Microsoft.CodeAnalysis.CSharp, 4.11.0"
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
|
||||
if (Args.Count < 2)
|
||||
{
|
||||
Console.Error.WriteLine("Użycie: dotnet script scan-props.csx -- <NazwaRekordu> <KatalogDll>");
|
||||
Console.Error.WriteLine("Przykład: dotnet script scan-props.csx -- DokumentHandlowy ./bin/Debug/net8.0");
|
||||
return 1;
|
||||
}
|
||||
|
||||
var recordBaseName = Args[0];
|
||||
var dllDir = Path.GetFullPath(Args[1]);
|
||||
var nestedTypeName = recordBaseName + "Record";
|
||||
|
||||
if (!Directory.Exists(dllDir))
|
||||
{
|
||||
Console.Error.WriteLine($"Katalog nie istnieje: {dllDir}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
var dllPaths = Directory.EnumerateFiles(dllDir, "*.dll", SearchOption.TopDirectoryOnly).ToList();
|
||||
if (dllPaths.Count == 0)
|
||||
{
|
||||
Console.Error.WriteLine($"Brak plików *.dll w katalogu: {dllDir}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
var refs = new List<MetadataReference>();
|
||||
var addedPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var p in dllPaths)
|
||||
{
|
||||
try
|
||||
{
|
||||
refs.Add(MetadataReference.CreateFromFile(p));
|
||||
addedPaths.Add(Path.GetFileName(p));
|
||||
}
|
||||
catch (Exception ex) { Console.Error.WriteLine($"# Pominięto {Path.GetFileName(p)}: {ex.Message}"); }
|
||||
}
|
||||
|
||||
// Dodaj referencje do bibliotek runtime'u .NET (TPA — Trusted Platform Assemblies),
|
||||
// żeby Roslyn potrafił rozwiązać atrybuty typu System.ComponentModel.DescriptionAttribute
|
||||
// i zdekodować ich argumenty konstruktora.
|
||||
var tpa = (AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") as string ?? "")
|
||||
.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var path in tpa)
|
||||
{
|
||||
var name = Path.GetFileName(path);
|
||||
if (addedPaths.Contains(name)) continue;
|
||||
try
|
||||
{
|
||||
refs.Add(MetadataReference.CreateFromFile(path));
|
||||
addedPaths.Add(name);
|
||||
}
|
||||
catch { /* pomiń */ }
|
||||
}
|
||||
|
||||
var compilation = CSharpCompilation.Create("Scan")
|
||||
.WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
|
||||
.AddReferences(refs);
|
||||
|
||||
INamedTypeSymbol foundRecord = null;
|
||||
INamedTypeSymbol enclosing = null;
|
||||
|
||||
// Indeks publicznych klas najwyższego poziomu po nazwie (do wyszukiwania
|
||||
// klas biznesowych — głównej oraz dla subrowów).
|
||||
var topLevelClasses = new Dictionary<string, INamedTypeSymbol>(StringComparer.Ordinal);
|
||||
|
||||
foreach (var asmRef in compilation.References)
|
||||
{
|
||||
if (compilation.GetAssemblyOrModuleSymbol(asmRef) is not IAssemblySymbol asm) continue;
|
||||
foreach (var type in EnumerateAllTypes(asm.GlobalNamespace))
|
||||
{
|
||||
if (foundRecord == null && type.Name.EndsWith("Module"))
|
||||
{
|
||||
var nested = type.GetTypeMembers(nestedTypeName).FirstOrDefault();
|
||||
if (nested != null)
|
||||
{
|
||||
foundRecord = nested;
|
||||
enclosing = type;
|
||||
}
|
||||
}
|
||||
if (type.ContainingType == null
|
||||
&& type.TypeKind == TypeKind.Class
|
||||
&& type.DeclaredAccessibility == Accessibility.Public)
|
||||
{
|
||||
topLevelClasses.TryAdd(type.Name, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (foundRecord == null)
|
||||
{
|
||||
Console.Error.WriteLine($"Nie znaleziono typu *Module+{nestedTypeName} w {dllDir}");
|
||||
return 2;
|
||||
}
|
||||
|
||||
INamedTypeSymbol mainBusinessClass = null;
|
||||
topLevelClasses.TryGetValue(recordBaseName, out mainBusinessClass);
|
||||
|
||||
// Nazwa tabeli wyciągana z typu zwracanego przez property `Table` w klasie XxxxRow.
|
||||
string tableTypeName = null;
|
||||
var rowClass = enclosing?.GetTypeMembers(recordBaseName + "Row").FirstOrDefault();
|
||||
if (rowClass != null)
|
||||
{
|
||||
for (var t = rowClass; t != null && t.SpecialType != SpecialType.System_Object; t = t.BaseType)
|
||||
{
|
||||
var tableProp = t.GetMembers("Table").OfType<IPropertySymbol>().FirstOrDefault();
|
||||
if (tableProp != null)
|
||||
{
|
||||
tableTypeName = tableProp.Type.Name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Klucz: nazwa pola z notacją kropkową dla subrowów; Wartość: (typ, czyBazodanowe, tytuł, opis)
|
||||
var merged = new SortedDictionary<string, (string Type, bool IsDb, string Caption, string Description)>(StringComparer.Ordinal);
|
||||
|
||||
var visited = new HashSet<INamedTypeSymbol>(SymbolEqualityComparer.Default);
|
||||
ScanRecord(foundRecord, "", visited, merged, topLevelClasses);
|
||||
|
||||
if (mainBusinessClass != null)
|
||||
{
|
||||
Console.WriteLine($"# Pola i właściwości klasy biznesowej: `{mainBusinessClass.ToDisplayString()}`");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"# Pola i właściwości `{enclosing.ToDisplayString()}+{nestedTypeName}`");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"Nie znaleziono klasy biznesowej `{recordBaseName}` — pokazano tylko pola bazodanowe.");
|
||||
}
|
||||
if (!string.IsNullOrEmpty(tableTypeName))
|
||||
{
|
||||
Console.WriteLine($"Nazwa tabeli: `{tableTypeName}`");
|
||||
}
|
||||
Console.WriteLine();
|
||||
var dbCount = merged.Values.Count(v => v.IsDb);
|
||||
var calcCount = merged.Count - dbCount;
|
||||
Console.WriteLine($"- pola bazodanowe: {dbCount}");
|
||||
Console.WriteLine($"- pola kalkulowane (z klas biznesowych): {calcCount}");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("| Pole | Typ | Rodzaj | Tytuł | Opis |");
|
||||
Console.WriteLine("|------|-----|--------|-------|------|");
|
||||
foreach (var kv in merged)
|
||||
{
|
||||
var rodzaj = kv.Value.IsDb ? "bazodanowe" : "";
|
||||
Console.WriteLine($"| {kv.Key} | `{kv.Value.Type}` | {rodzaj} | {EscapeCell(kv.Value.Caption)} | {EscapeCell(kv.Value.Description)} |");
|
||||
}
|
||||
return 0;
|
||||
|
||||
static void ScanRecord(
|
||||
INamedTypeSymbol record,
|
||||
string prefix,
|
||||
HashSet<INamedTypeSymbol> visited,
|
||||
SortedDictionary<string, (string Type, bool IsDb, string Caption, string Description)> merged,
|
||||
Dictionary<string, INamedTypeSymbol> topLevelClasses)
|
||||
{
|
||||
if (record == null) return;
|
||||
if (!visited.Add(record)) return;
|
||||
|
||||
var fields = record.GetMembers()
|
||||
.OfType<IFieldSymbol>()
|
||||
.Where(f => f.DeclaredAccessibility == Accessibility.Public)
|
||||
.ToList();
|
||||
|
||||
var encMod = record.ContainingType;
|
||||
var baseName = record.Name.EndsWith("Record")
|
||||
? record.Name.Substring(0, record.Name.Length - "Record".Length)
|
||||
: record.Name;
|
||||
|
||||
INamedTypeSymbol bizCls = null;
|
||||
topLevelClasses.TryGetValue(baseName, out bizCls);
|
||||
var rowFallback = encMod?.GetTypeMembers(baseName + "Row").FirstOrDefault();
|
||||
|
||||
// 1. Pola rekordu → bazodanowe.
|
||||
foreach (var f in fields)
|
||||
{
|
||||
var key = prefix + f.Name;
|
||||
merged[key] = (
|
||||
f.Type.ToDisplayString(),
|
||||
true,
|
||||
GetAttributeFirstString(f, "CaptionAttribute"),
|
||||
GetAttributeFirstString(f, "DescriptionAttribute"));
|
||||
}
|
||||
|
||||
// 2. Właściwości klasy biznesowej (z dziedziczeniem) → kalkulowane lub nadpisanie pola bazodanowego.
|
||||
if (bizCls != null)
|
||||
{
|
||||
var seen = new HashSet<string>(StringComparer.Ordinal);
|
||||
foreach (var p in EnumerateInheritedProperties(bizCls))
|
||||
{
|
||||
if (p.DeclaredAccessibility != Accessibility.Public || p.IsStatic || p.IsIndexer || p.GetMethod == null)
|
||||
continue;
|
||||
if (!seen.Add(p.Name)) continue;
|
||||
var key = prefix + p.Name;
|
||||
var typeStr = p.Type.ToDisplayString();
|
||||
var caption = GetAttributeFirstString(p, "CaptionAttribute");
|
||||
var description = GetAttributeFirstString(p, "DescriptionAttribute");
|
||||
if (merged.TryGetValue(key, out var existing))
|
||||
{
|
||||
merged[key] = (
|
||||
typeStr,
|
||||
existing.IsDb,
|
||||
!string.IsNullOrEmpty(caption) ? caption : existing.Caption,
|
||||
!string.IsNullOrEmpty(description) ? description : existing.Description);
|
||||
}
|
||||
else
|
||||
{
|
||||
merged[key] = (typeStr, false, caption, description);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Fallback — atrybuty Caption/Description z typu zagnieżdżonego *Row (wraz z dziedziczeniem).
|
||||
if (rowFallback != null)
|
||||
{
|
||||
var prefixLen = prefix.Length;
|
||||
foreach (var key in merged.Keys.ToList())
|
||||
{
|
||||
if (!key.StartsWith(prefix, StringComparison.Ordinal)) continue;
|
||||
var local = key.Substring(prefixLen);
|
||||
if (local.Length == 0 || local.Contains('.')) continue;
|
||||
var entry = merged[key];
|
||||
if (!string.IsNullOrEmpty(entry.Caption) && !string.IsNullOrEmpty(entry.Description))
|
||||
continue;
|
||||
var member = FindMemberInherited(rowFallback, local);
|
||||
if (member == null) continue;
|
||||
var caption = !string.IsNullOrEmpty(entry.Caption)
|
||||
? entry.Caption
|
||||
: GetAttributeFirstString(member, "CaptionAttribute");
|
||||
var description = !string.IsNullOrEmpty(entry.Description)
|
||||
? entry.Description
|
||||
: GetAttributeFirstString(member, "DescriptionAttribute");
|
||||
merged[key] = (entry.Type, entry.IsDb, caption, description);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Rekurencja po polach typu subrow (typ kończący się na "Record").
|
||||
foreach (var f in fields)
|
||||
{
|
||||
if (f.Type is INamedTypeSymbol nested && nested.TypeKind == TypeKind.Class && nested.Name.EndsWith("Record"))
|
||||
{
|
||||
ScanRecord(nested, prefix + f.Name + ".", visited, merged, topLevelClasses);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static IEnumerable<INamedTypeSymbol> EnumerateAllTypes(INamespaceSymbol ns)
|
||||
{
|
||||
foreach (var t in ns.GetTypeMembers()) yield return t;
|
||||
foreach (var sub in ns.GetNamespaceMembers())
|
||||
foreach (var t in EnumerateAllTypes(sub)) yield return t;
|
||||
}
|
||||
|
||||
static IEnumerable<IPropertySymbol> EnumerateInheritedProperties(INamedTypeSymbol type)
|
||||
{
|
||||
for (var t = type; t != null && t.SpecialType != SpecialType.System_Object; t = t.BaseType)
|
||||
{
|
||||
foreach (var p in t.GetMembers().OfType<IPropertySymbol>())
|
||||
yield return p;
|
||||
}
|
||||
}
|
||||
|
||||
static string GetAttributeFirstString(ISymbol symbol, string attributeTypeName)
|
||||
{
|
||||
if (symbol == null) return "";
|
||||
var shortName = attributeTypeName.EndsWith("Attribute")
|
||||
? attributeTypeName.Substring(0, attributeTypeName.Length - "Attribute".Length)
|
||||
: attributeTypeName;
|
||||
var longName = shortName + "Attribute";
|
||||
foreach (var a in symbol.GetAttributes())
|
||||
{
|
||||
if (a.AttributeClass == null) continue;
|
||||
var n = a.AttributeClass.Name;
|
||||
if (!string.Equals(n, shortName, StringComparison.Ordinal)
|
||||
&& !string.Equals(n, longName, StringComparison.Ordinal))
|
||||
continue;
|
||||
foreach (var arg in a.ConstructorArguments)
|
||||
{
|
||||
if (arg.Kind == TypedConstantKind.Primitive && arg.Value is string s)
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static ISymbol FindMemberInherited(INamedTypeSymbol type, string name)
|
||||
{
|
||||
for (var t = type; t != null && t.SpecialType != SpecialType.System_Object; t = t.BaseType)
|
||||
{
|
||||
var m = t.GetMembers(name).FirstOrDefault();
|
||||
if (m != null) return m;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static string EscapeCell(string s)
|
||||
{
|
||||
if (string.IsNullOrEmpty(s)) return "";
|
||||
return s.Replace("\\", "\\\\").Replace("|", "\\|").Replace("\r", " ").Replace("\n", " ");
|
||||
}
|
||||
Reference in New Issue
Block a user