Rozbudowa scan-workers.csx
This commit is contained in:
@@ -105,13 +105,62 @@ uruchomień. Reguły rozpoznawania powiązań (po metadanych typu):
|
||||
| Klasa dziedzicząca z `Soneta.Business.Table` (np. `Pracownicy`, `DokHandlowe`) | Klasa rekordu (`Pracownik`, `DokumentHandlowy`) | indekser `this[int]` — typ zwracany |
|
||||
| Typ implementujący `IRowWithHistory` (np. `Pracownik`) | Typ rekordu historycznego (`PracHistoria`) | indekser `this[Soneta.Types.Date]` — typ zwracany |
|
||||
|
||||
Reguły działają łącznie — np. dla `Pracownik` (Row + IRowWithHistory) zebrane są workery z trzech
|
||||
typów naraz: `Pracownik`, `Pracownicy`, `PracHistoria`. Informacje o znalezionych typach
|
||||
powiązanych skrypt wypisuje na stderr (`# Typ podstawowy: …`, `# Typ powiązany: …`), żeby nie
|
||||
zaśmiecać JSON-a na stdout.
|
||||
Reguły działają **przechodnio** i **łącznie** — np. dla `Pracownik` (Row + IRowWithHistory)
|
||||
zestaw to: `Pracownik`, `Pracownicy`, `PracHistoria`, `PracHistorie` (tabela historii dochodzi,
|
||||
bo `PracHistoria` to też Row z własną `Table`).
|
||||
|
||||
Dodatkowo dla każdego znalezionego Row (oryginał + history-Row) skrypt **rozszerza zestaw o całą
|
||||
hierarchię**:
|
||||
- **klasy bazowe** (`baseClasses`) — chodzi w górę po `BaseType` aż do `object` wyłącznie
|
||||
(włącznie z generowanym `*Row` z `*Module`, frameworkowymi `Row` / `GuidedRow` / `RowBase`);
|
||||
- **klasy pochodne** (`derivedClasses`) — przeszukuje wszystkie referencje w poszukiwaniu klas
|
||||
mających dany Row w łańcuchu `BaseType` (np. `OsobaWspolpracujaca`, `PracownikFirmy`,
|
||||
`Wlasciciel` dla `Pracownik`).
|
||||
|
||||
Tabele tego rozszerzenia nie dostają — zbędne, intermediate `*Table` rzadko bywa celem rejestracji
|
||||
workera.
|
||||
|
||||
Informacje o znalezionych typach skrypt wypisuje **dwojako**:
|
||||
|
||||
1. **W JSON pod kluczem `scope`** (na stdout, sformatowane do parsowania):
|
||||
|
||||
```json
|
||||
{
|
||||
"description": "Workery przypięte do typu `Pracownik` (Soneta)",
|
||||
"scope": {
|
||||
"primary": "Soneta.Kadry.Pracownik",
|
||||
"related": [
|
||||
{ "type": "Soneta.Kadry.Pracownicy", "kind": "table" },
|
||||
{ "type": "Soneta.Kadry.PracHistoria", "kind": "history-row" },
|
||||
{ "type": "Soneta.Kadry.PracHistorie", "kind": "history-table" }
|
||||
],
|
||||
"baseClasses": [
|
||||
"Soneta.Kadry.KadryModule.PracownikRow",
|
||||
"Soneta.Business.GuidedRow",
|
||||
"Soneta.Business.Row",
|
||||
"Soneta.Kadry.KadryModule.PracHistoriaRow"
|
||||
],
|
||||
"derivedClasses": [
|
||||
"Soneta.Kadry.OsobaWspolpracujaca",
|
||||
"Soneta.Kadry.PracownikFirmy",
|
||||
"Soneta.Kadry.Wlasciciel"
|
||||
]
|
||||
},
|
||||
"Soneta.Kadry.Pracownik": [ /* … */ ],
|
||||
"Soneta.Kadry.Pracownicy": [ /* … */ ]
|
||||
}
|
||||
```
|
||||
|
||||
Pole `scope` pojawia się **wyłącznie** w trybie `--related` (przy zwykłym filtrze JSON nie ma
|
||||
tego klucza). Dozwolone wartości `scope.related[].kind`: `table`, `row`, `history-row`,
|
||||
`history-table`.
|
||||
|
||||
2. **W logu na stderr** (`# Typ podstawowy: …`, `# Typ powiązany (kind): …`, `# Klasa bazowa: …`,
|
||||
`# Klasa pochodna: …`) — wygodne do szybkiego podglądu w konsoli.
|
||||
|
||||
Gdy `--related` jest podany, ale typu z `<NazwaTypuDanych>` nie da się znaleźć w referencjach
|
||||
(np. literówka), skrypt loguje ostrzeżenie na stderr i wraca do prostego dopasowania po nazwie.
|
||||
(np. literówka), skrypt loguje ostrzeżenie na stderr i wraca do prostego dopasowania po nazwie
|
||||
(bez sekcji `scope`).
|
||||
|
||||
### Przykłady
|
||||
|
||||
|
||||
@@ -139,18 +139,52 @@ foreach (var asmRef in compilation.References)
|
||||
|
||||
var allowedDataTypes = new HashSet<INamedTypeSymbol>(SymbolEqualityComparer.Default);
|
||||
INamedTypeSymbol primaryFilterType = null;
|
||||
Dictionary<string, object> scopeJson = null;
|
||||
if (typeFilter != null && includeRelated)
|
||||
{
|
||||
primaryFilterType = FindTypeByName(compilation, typeFilter);
|
||||
if (primaryFilterType != null)
|
||||
{
|
||||
allowedDataTypes.Add(primaryFilterType);
|
||||
var related = ResolveRelatedTypes(primaryFilterType).ToList();
|
||||
foreach (var r in related) allowedDataTypes.Add(r);
|
||||
var related = ResolveRelatedTypes(primaryFilterType);
|
||||
foreach (var r in related) allowedDataTypes.Add(r.Type);
|
||||
|
||||
// Dla każdego Row z zestawu (podstawowy oraz historyczny zwrócony z this[Date])
|
||||
// dodaj klasy bazowe (do `Row` włącznie) oraz klasy pochodne. Dla tabel tego nie robimy —
|
||||
// intermediate `*Table` generowane i framework Table to inny obszar.
|
||||
var rowTypes = allowedDataTypes
|
||||
.Where(t => InheritsFromNamed(t, "Row", "Soneta.Business")
|
||||
|| (t.Name == "Row" && (t.ContainingNamespace?.ToDisplayString() ?? "") == "Soneta.Business"))
|
||||
.ToList();
|
||||
var baseAdded = new List<INamedTypeSymbol>();
|
||||
var derivedAdded = new List<INamedTypeSymbol>();
|
||||
foreach (var r in rowTypes)
|
||||
{
|
||||
foreach (var b in BaseTypes(r))
|
||||
if (allowedDataTypes.Add(b)) baseAdded.Add(b);
|
||||
foreach (var d in FindDerivedTypes(compilation, r))
|
||||
if (allowedDataTypes.Add(d)) derivedAdded.Add(d);
|
||||
}
|
||||
|
||||
Console.Error.WriteLine($"# Typ podstawowy: {primaryFilterType.ToDisplayString()}");
|
||||
foreach (var r in related)
|
||||
Console.Error.WriteLine($"# Typ powiązany: {r.ToDisplayString()}");
|
||||
Console.Error.WriteLine($"# Typ powiązany ({r.Kind}): {r.Type.ToDisplayString()}");
|
||||
foreach (var b in baseAdded)
|
||||
Console.Error.WriteLine($"# Klasa bazowa: {b.ToDisplayString()}");
|
||||
foreach (var d in derivedAdded)
|
||||
Console.Error.WriteLine($"# Klasa pochodna: {d.ToDisplayString()}");
|
||||
|
||||
scopeJson = new Dictionary<string, object>
|
||||
{
|
||||
["primary"] = primaryFilterType.ToDisplayString(),
|
||||
["related"] = related.Select(r => (object)new Dictionary<string, object>
|
||||
{
|
||||
["type"] = r.Type.ToDisplayString(),
|
||||
["kind"] = r.Kind,
|
||||
}).ToList(),
|
||||
["baseClasses"] = baseAdded.Select(b => (object)b.ToDisplayString()).ToList(),
|
||||
["derivedClasses"] = derivedAdded.Select(d => (object)d.ToDisplayString()).ToList(),
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -183,7 +217,7 @@ var extenders = typeFilter == null
|
||||
.ToList()
|
||||
: new List<WorkerRegistration>();
|
||||
|
||||
WriteJson(byData, extenders, typeFilter);
|
||||
WriteJson(byData, extenders, typeFilter, scopeJson);
|
||||
return 0;
|
||||
|
||||
static bool IsWorkerAttribute(INamedTypeSymbol attrClass)
|
||||
@@ -235,13 +269,15 @@ static string StripSuffix(string name, string suffix)
|
||||
static void WriteJson(
|
||||
List<IGrouping<ISymbol, WorkerRegistration>> byData,
|
||||
List<WorkerRegistration> extenders,
|
||||
string typeFilter)
|
||||
string typeFilter,
|
||||
Dictionary<string, object> scope)
|
||||
{
|
||||
// Dictionary z zachowaną kolejnością wstawiania — opis na początku, potem klucze typów.
|
||||
var root = new Dictionary<string, object>();
|
||||
root["description"] = typeFilter != null
|
||||
? $"Workery przypięte do typu `{typeFilter}` (Soneta)"
|
||||
: "Workery i extendery (Soneta)";
|
||||
if (scope != null) root["scope"] = scope;
|
||||
|
||||
foreach (var g in byData)
|
||||
{
|
||||
@@ -420,67 +456,95 @@ static IEnumerable<INamedTypeSymbol> EnumerateAllTypes(INamespaceSymbol ns)
|
||||
foreach (var t in EnumerateAllTypes(sub)) yield return t;
|
||||
}
|
||||
|
||||
// Zbiór typów powiązanych z `primary` (przechodnio):
|
||||
// - jeśli typ dziedziczy z `Soneta.Business.Row` → typ z property `Table` (klasa tabeli);
|
||||
// - jeśli typ dziedziczy z `Soneta.Business.Table` → typ zwracany przez indekser `this[int]`
|
||||
// (klasa rekordu);
|
||||
// - jeśli typ implementuje interfejs `IRowWithHistory` → typ zwracany przez indekser
|
||||
// przyjmujący `Soneta.Types.Date` (historyczny rekord).
|
||||
// Reguły aplikowane są w pętli — np. dla `Pracownik` (Row + IRowWithHistory) najpierw
|
||||
// dostajemy `Pracownicy` i `PracHistoria`, a `PracHistoria` (kolejny Row) dorzuca własną
|
||||
// tabelę `PracHistorie`. Pętle są zabezpieczone zbiorem już odwiedzonych typów.
|
||||
static IEnumerable<INamedTypeSymbol> ResolveRelatedTypes(INamedTypeSymbol primary)
|
||||
// Zbiór typów powiązanych z `primary` (przechodnio), wraz z rodzajem powiązania:
|
||||
// - "table" — tabela uzyskana z property `Table` na klasie Row;
|
||||
// - "row" — rekord uzyskany z `this[int]` na klasie Table;
|
||||
// - "history-row" — rekord historyczny z `this[Date]` (IRowWithHistory);
|
||||
// - "history-table" — tabela rekordu historycznego (Row→Table na history-row).
|
||||
// Pętle są zabezpieczone zbiorem już odwiedzonych typów.
|
||||
static List<RelatedType> ResolveRelatedTypes(INamedTypeSymbol primary)
|
||||
{
|
||||
var result = new HashSet<INamedTypeSymbol>(SymbolEqualityComparer.Default);
|
||||
var queue = new Queue<INamedTypeSymbol>();
|
||||
queue.Enqueue(primary);
|
||||
var results = new List<RelatedType>();
|
||||
var queue = new Queue<(INamedTypeSymbol Type, string ParentKind)>();
|
||||
queue.Enqueue((primary, "primary"));
|
||||
var visited = new HashSet<INamedTypeSymbol>(SymbolEqualityComparer.Default) { primary };
|
||||
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
var t = queue.Dequeue();
|
||||
foreach (var related in DirectRelatedTypes(t))
|
||||
var (t, parentKind) = queue.Dequeue();
|
||||
var historyBranch = parentKind == "history-row" || parentKind == "history-table";
|
||||
|
||||
if (InheritsFromNamed(t, "Row", "Soneta.Business"))
|
||||
{
|
||||
if (visited.Add(related))
|
||||
if (FindMemberInherited(t, m => m is IPropertySymbol p && !p.IsIndexer && p.Name == "Table")
|
||||
is IPropertySymbol tableProp
|
||||
&& tableProp.Type is INamedTypeSymbol tableType
|
||||
&& visited.Add(tableType))
|
||||
{
|
||||
result.Add(related);
|
||||
queue.Enqueue(related);
|
||||
var kind = historyBranch ? "history-table" : "table";
|
||||
results.Add(new RelatedType(tableType, kind));
|
||||
queue.Enqueue((tableType, kind));
|
||||
}
|
||||
}
|
||||
|
||||
if (InheritsFromNamed(t, "Table", "Soneta.Business"))
|
||||
{
|
||||
if (FindMemberInherited(t, m => m is IPropertySymbol p
|
||||
&& p.IsIndexer && p.Parameters.Length == 1
|
||||
&& p.Parameters[0].Type.SpecialType == SpecialType.System_Int32)
|
||||
is IPropertySymbol rowIndexer
|
||||
&& rowIndexer.Type is INamedTypeSymbol rowType
|
||||
&& visited.Add(rowType))
|
||||
{
|
||||
var kind = historyBranch ? "history-row" : "row";
|
||||
results.Add(new RelatedType(rowType, kind));
|
||||
queue.Enqueue((rowType, kind));
|
||||
}
|
||||
}
|
||||
|
||||
if (ImplementsInterface(t, "IRowWithHistory"))
|
||||
{
|
||||
if (FindMemberInherited(t, m => m is IPropertySymbol p
|
||||
&& p.IsIndexer && p.Parameters.Length == 1
|
||||
&& p.Parameters[0].Type is INamedTypeSymbol pt
|
||||
&& pt.Name == "Date"
|
||||
&& (pt.ContainingNamespace?.ToDisplayString() ?? "").StartsWith("Soneta", StringComparison.Ordinal))
|
||||
is IPropertySymbol dateIndexer
|
||||
&& dateIndexer.Type is INamedTypeSymbol histType
|
||||
&& visited.Add(histType))
|
||||
{
|
||||
results.Add(new RelatedType(histType, "history-row"));
|
||||
queue.Enqueue((histType, "history-row"));
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return results;
|
||||
}
|
||||
|
||||
static IEnumerable<INamedTypeSymbol> DirectRelatedTypes(INamedTypeSymbol t)
|
||||
record RelatedType(INamedTypeSymbol Type, string Kind);
|
||||
|
||||
// Wszystkie klasy bazowe (bez `object`).
|
||||
static IEnumerable<INamedTypeSymbol> BaseTypes(INamedTypeSymbol type)
|
||||
{
|
||||
if (InheritsFromNamed(t, "Row", "Soneta.Business"))
|
||||
{
|
||||
if (FindMemberInherited(t, m => m is IPropertySymbol p && !p.IsIndexer && p.Name == "Table")
|
||||
is IPropertySymbol tableProp
|
||||
&& tableProp.Type is INamedTypeSymbol tableType)
|
||||
yield return tableType;
|
||||
}
|
||||
for (var t = type.BaseType; t != null && t.SpecialType != SpecialType.System_Object; t = t.BaseType)
|
||||
yield return t;
|
||||
}
|
||||
|
||||
if (InheritsFromNamed(t, "Table", "Soneta.Business"))
|
||||
// Wszystkie klasy pochodne (publiczne, w referencjach), które mają `baseType` w łańcuchu BaseType.
|
||||
static IEnumerable<INamedTypeSymbol> FindDerivedTypes(CSharpCompilation compilation, INamedTypeSymbol baseType)
|
||||
{
|
||||
foreach (var asmRef in compilation.References)
|
||||
{
|
||||
if (FindMemberInherited(t, m => m is IPropertySymbol p
|
||||
&& p.IsIndexer && p.Parameters.Length == 1
|
||||
&& p.Parameters[0].Type.SpecialType == SpecialType.System_Int32)
|
||||
is IPropertySymbol rowIndexer
|
||||
&& rowIndexer.Type is INamedTypeSymbol rowType)
|
||||
yield return rowType;
|
||||
}
|
||||
|
||||
if (ImplementsInterface(t, "IRowWithHistory"))
|
||||
{
|
||||
if (FindMemberInherited(t, m => m is IPropertySymbol p
|
||||
&& p.IsIndexer && p.Parameters.Length == 1
|
||||
&& p.Parameters[0].Type is INamedTypeSymbol pt
|
||||
&& pt.Name == "Date"
|
||||
&& (pt.ContainingNamespace?.ToDisplayString() ?? "").StartsWith("Soneta", StringComparison.Ordinal))
|
||||
is IPropertySymbol dateIndexer
|
||||
&& dateIndexer.Type is INamedTypeSymbol histType)
|
||||
yield return histType;
|
||||
if (compilation.GetAssemblyOrModuleSymbol(asmRef) is not IAssemblySymbol asm) continue;
|
||||
foreach (var t in EnumerateAllTypes(asm.GlobalNamespace))
|
||||
{
|
||||
if (t.TypeKind != TypeKind.Class) continue;
|
||||
if (SymbolEqualityComparer.Default.Equals(t, baseType)) continue;
|
||||
for (var b = t.BaseType; b != null && b.SpecialType != SpecialType.System_Object; b = b.BaseType)
|
||||
{
|
||||
if (SymbolEqualityComparer.Default.Equals(b, baseType)) { yield return t; break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user