@phuc1403/musketeer 0.12.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALLATION.md +1 -1
- package/README.md +2 -2
- package/manifest.json +4 -3
- package/package.json +1 -1
- package/template/.claude/hooks/inject-ubiquitous-language.cjs +2 -2
- package/template/.claude/skills/domain-expert/SKILL.md +76 -0
- package/template/.claude/skills/logical-components/SKILL.md +165 -94
- package/template/.claude/skills/logical-components/assets/component-diagram-template.html +104 -0
- package/template/.claude/skills/logical-components/references/component-analyst-prompt.md +88 -0
- package/template/.claude/skills/logical-components/references/component-level-detection.md +95 -0
- package/template/.claude/skills/logical-components/references/logical-component-concepts.md +92 -0
- package/template/.claude/skills/logical-components/scripts/build-component-model.cjs +120 -0
- package/template/.claude/skills/logical-components/scripts/index-declared-symbols.cjs +106 -0
- package/template/.claude/skills/logical-components/scripts/lib/coupling-metrics.cjs +48 -0
- package/template/.claude/skills/logical-components/scripts/lib/declared-symbol-patterns.cjs +57 -0
- package/template/.claude/skills/logical-components/scripts/lib/html-sections.cjs +91 -0
- package/template/.claude/skills/logical-components/scripts/lib/mermaid-graph.cjs +55 -0
- package/template/.claude/skills/logical-components/scripts/lib/read-json-input.cjs +109 -0
- package/template/.claude/skills/logical-components/scripts/lib/repo-walk.cjs +61 -0
- package/template/.claude/skills/logical-components/scripts/lib/verify-claims.cjs +159 -0
- package/template/.claude/skills/logical-components/scripts/render-component-diagram-html.cjs +112 -0
- package/template/.claude/skills/logical-components/scripts/scan-folder-tree.cjs +88 -0
- package/template/.claude/skills/logical-components/scripts/select-stale-components.cjs +129 -0
- package/template/.claude/skills/logical-components/.gitattributes +0 -2
- package/template/.claude/skills/logical-components/references/component-classifier-prompt.md +0 -53
- package/template/.claude/skills/logical-components/references/responsibility-agent-prompt.md +0 -67
- package/template/.claude/skills/logical-components/references/responsibility-verifier-prompt.md +0 -39
- package/template/.claude/skills/logical-components/scripts/Directory.Build.props +0 -7
- package/template/.claude/skills/logical-components/scripts/Directory.Build.rsp +0 -1
- package/template/.claude/skills/logical-components/scripts/Directory.Build.targets +0 -6
- package/template/.claude/skills/logical-components/scripts/Directory.Packages.props +0 -8
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/BatchPlanning.cs +0 -70
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/BatchesCommand.cs +0 -47
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/CandidatesCommand.cs +0 -98
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/CitableSource.cs +0 -54
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/Citation.cs +0 -37
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/ClassifyBatchesCommand.cs +0 -86
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/ClassifyMergeCommand.cs +0 -219
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/ComponentDiscovery.cs +0 -180
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/ComponentRule.cs +0 -130
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/ComponentsFile.cs +0 -101
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/CouplingResolver.cs +0 -251
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/DisplayName.cs +0 -14
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/EdgeExtraction.cs +0 -111
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/ExtractCommand.cs +0 -87
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/ExtractConfig.cs +0 -79
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/LogicalComponents.csproj +0 -15
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/MarkdownCodeSpans.cs +0 -72
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/MarkdownRenderer.cs +0 -78
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/MemberUseResolution.cs +0 -272
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/MemberUses.cs +0 -231
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/PlainText.cs +0 -55
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/PortResolution.cs +0 -75
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/Program.cs +0 -27
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/ProjectFilters.cs +0 -77
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/PruneCommand.cs +0 -164
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/RenderCommand.cs +0 -234
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/ResponsibilityBatches.cs +0 -84
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/ResponsibilityValidation.cs +0 -197
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/SeamDispatch.cs +0 -115
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/SourceCodeLines.cs +0 -79
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/SymbolNames.cs +0 -53
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/TypeMap.cs +0 -106
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/WalkRoots.cs +0 -63
- package/template/.claude/skills/logical-components/scripts/LogicalComponents/WorkspaceLoader.cs +0 -297
- package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/AssignIdsTests.cs +0 -41
- package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/BatchPlanningTests.cs +0 -90
- package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/BatchesCommandTests.cs +0 -37
- package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/CandidatesCommandTests.cs +0 -59
- package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/CitationTests.cs +0 -42
- package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/ClassifyCommandsTests.cs +0 -249
- package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/ComponentDiscoveryTests.cs +0 -253
- package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/CouplingAppExtractionTests.cs +0 -188
- package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/CouplingAppWorkspace.cs +0 -30
- package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/CouplingExtractionTests.cs +0 -174
- package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/DisplayNameTests.cs +0 -17
- package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/ExtractConfigTests.cs +0 -72
- package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/FixturePaths.cs +0 -36
- package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/LogicalComponents.Tests.csproj +0 -23
- package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/MarkdownRendererTests.cs +0 -93
- package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/ProjectFiltersTests.cs +0 -69
- package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/PruneCommandTests.cs +0 -149
- package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/RenderCommandTests.cs +0 -225
- package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/RenderGoldenTests.cs +0 -34
- package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/RenderTestData.cs +0 -55
- package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/ResponsibilityValidationTests.cs +0 -315
- package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/SampleAppWorkspace.cs +0 -27
- package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/WorkspaceLoaderTests.cs +0 -266
- package/template/.claude/skills/logical-components/scripts/NuGet.config +0 -17
- package/template/.claude/skills/logical-components/scripts/fixtures/BrokenApp/src/Broken.Application/Broken.Application.csproj +0 -7
- package/template/.claude/skills/logical-components/scripts/fixtures/BrokenApp/src/Broken.Application/Broken.cs +0 -7
- package/template/.claude/skills/logical-components/scripts/fixtures/CompositionRootApp/CompositionRootApp.slnx +0 -5
- package/template/.claude/skills/logical-components/scripts/fixtures/CompositionRootApp/src/Root.Application/HostBuilderExtensions.cs +0 -15
- package/template/.claude/skills/logical-components/scripts/fixtures/CompositionRootApp/src/Root.Application/HostBuilderShims.cs +0 -25
- package/template/.claude/skills/logical-components/scripts/fixtures/CompositionRootApp/src/Root.Application/NativeInterop.cs +0 -17
- package/template/.claude/skills/logical-components/scripts/fixtures/CompositionRootApp/src/Root.Application/Root.Application.csproj +0 -7
- package/template/.claude/skills/logical-components/scripts/fixtures/CompositionRootApp/src/Root.Application/RootAutofacModule.cs +0 -11
- package/template/.claude/skills/logical-components/scripts/fixtures/CompositionRootApp/src/Root.Application/RootGreeter.cs +0 -7
- package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/logical-components.json +0 -48
- package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/src/CouplingApp.Application/AbstractMemberCases.cs +0 -27
- package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/src/CouplingApp.Application/AttributeCases.cs +0 -23
- package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/src/CouplingApp.Application/CouplingApp.Application.csproj +0 -7
- package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/src/CouplingApp.Application/GenericBaseCases.cs +0 -14
- package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/src/CouplingApp.Application/GenericSeamBindingCases.cs +0 -59
- package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/src/CouplingApp.Application/ImplicitCallCases.cs +0 -230
- package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/src/CouplingApp.Application/PartialInheritCases.First.cs +0 -11
- package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/src/CouplingApp.Application/PartialInheritCases.Second.cs +0 -14
- package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/src/CouplingApp.Application/StackOverflowCases.cs +0 -14
- package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/src/CouplingApp.Application/ViaDisambiguationCases.cs +0 -36
- package/template/.claude/skills/logical-components/scripts/fixtures/MultiTargetApp/MultiTargetApp.slnx +0 -7
- package/template/.claude/skills/logical-components/scripts/fixtures/MultiTargetApp/src/Legacy.FSharp/Legacy.FSharp.fsproj +0 -8
- package/template/.claude/skills/logical-components/scripts/fixtures/MultiTargetApp/src/Legacy.FSharp/Library.fs +0 -5
- package/template/.claude/skills/logical-components/scripts/fixtures/MultiTargetApp/src/Multi.Application/Multi.Application.csproj +0 -7
- package/template/.claude/skills/logical-components/scripts/fixtures/MultiTargetApp/src/Multi.Application/Scheduling.cs +0 -14
- package/template/.claude/skills/logical-components/scripts/fixtures/MultiTargetApp/src/Multi.Infrastructure/Multi.Infrastructure.csproj +0 -12
- package/template/.claude/skills/logical-components/scripts/fixtures/MultiTargetApp/src/Multi.Infrastructure/SchedulingClient.cs +0 -12
- package/template/.claude/skills/logical-components/scripts/fixtures/NoSolutionApp/src/Solo.Application/Solo.Application.csproj +0 -7
- package/template/.claude/skills/logical-components/scripts/fixtures/NoSolutionApp/src/Solo.Application/SoloGreeting.cs +0 -7
- package/template/.claude/skills/logical-components/scripts/fixtures/NoSolutionApp/src/Solo.Application.Tests/FakeGreeting.cs +0 -6
- package/template/.claude/skills/logical-components/scripts/fixtures/NoSolutionApp/src/Solo.Application.Tests/Solo.Application.Tests.csproj +0 -11
- package/template/.claude/skills/logical-components/scripts/fixtures/ObjOutputApp/ObjOutputApp.slnx +0 -5
- package/template/.claude/skills/logical-components/scripts/fixtures/ObjOutputApp/src/Obj.Application/Greeting.cs +0 -7
- package/template/.claude/skills/logical-components/scripts/fixtures/ObjOutputApp/src/Obj.Application/Obj.Application.csproj +0 -14
- package/template/.claude/skills/logical-components/scripts/fixtures/RenderGolden/components.json +0 -67
- package/template/.claude/skills/logical-components/scripts/fixtures/RenderGolden/expected.md +0 -28
- package/template/.claude/skills/logical-components/scripts/fixtures/RenderGolden/repo/src/App/OrderAcceptance.cs +0 -16
- package/template/.claude/skills/logical-components/scripts/fixtures/RenderGolden/repo/src/Infra/OrderQueue.cs +0 -10
- package/template/.claude/skills/logical-components/scripts/fixtures/RenderGolden/resp/batch-01.json +0 -18
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/SampleApp.slnx +0 -10
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/logical-components.json +0 -78
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/CandidateCases.cs +0 -37
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/CouplingCases.cs +0 -33
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/DependencyInjection.cs +0 -18
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/EdgeCases.cs +0 -63
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/LoudNotifying.Base.cs +0 -5
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/LoudNotifying.cs +0 -7
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/OrderAcceptance.cs +0 -17
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/OrderOptions.cs +0 -9
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/OrderRejectedException.cs +0 -7
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/OrderRequest.cs +0 -4
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/OrderRunning.cs +0 -18
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/OrderValidation.cs +0 -6
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/Ports.cs +0 -19
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/PriceRules.cs +0 -7
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/ReceiptPrinting.Totals.cs +0 -6
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/ReceiptPrinting.cs +0 -7
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/ReviewCases.cs +0 -107
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/ReviewFixCases.cs +0 -143
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/RunClock.cs +0 -12
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/Sample.Application.csproj +0 -10
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/SeeThroughCases.cs +0 -89
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Billing.Application/OrderRunning.cs +0 -7
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Billing.Application/Sample.Billing.Application.csproj +0 -11
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/ForwardedNotifier.cs +0 -8
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/InfrastructureDependencyInjection.cs +0 -16
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/Mailer.cs +0 -6
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/OrderQueue.cs +0 -11
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/QueueEntry.cs +0 -4
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/QueueStore.cs +0 -8
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/ReviewCases.cs +0 -35
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/ReviewFixCases.cs +0 -13
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/Sample.Infrastructure.csproj +0 -13
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/SeeThroughCases.cs +0 -18
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/UnreachedPlumbing.cs +0 -7
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/tests/Sample.Tests/FakeOrderQueue.cs +0 -11
- package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/tests/Sample.Tests/Sample.Tests.csproj +0 -11
- package/template/.claude/skills/logical-components/scripts/fixtures/SharedOutsideRepo/SharedClock.cs +0 -7
- package/template/.claude/skills/logical-components/scripts/global.json +0 -5
- package/template/.claude/skills/logical-components/scripts/smoke-sample.sh +0 -26
package/template/.claude/skills/logical-components/scripts/LogicalComponents/ExtractCommand.cs
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
namespace LogicalComponents;
|
|
2
|
-
|
|
3
|
-
/// <summary>
|
|
4
|
-
/// <c>extract <repoPath> [--out <components.json>] [--solution <file>]</c>: loads the repo and writes the
|
|
5
|
-
/// deterministic extraction, including a hash of every cited file's content (see <see cref="FileHashes"/>) so
|
|
6
|
-
/// <c>render</c> can tell whether the repo changed since. <c>--solution</c> picks the solution file as in
|
|
7
|
-
/// <c>candidates</c>. Progress and warnings go to <c>errors</c> (stderr); the artifact goes to the --out file.
|
|
8
|
-
/// Exit codes: 0 written, 1 usage, 2 the target repo or its config could not be loaded, 4 candidates not classified
|
|
9
|
-
/// in the repo config yet (one <c>unclassified <type></c> line each; nothing is written).
|
|
10
|
-
/// </summary>
|
|
11
|
-
public static class ExtractCommand
|
|
12
|
-
{
|
|
13
|
-
public const string Usage =
|
|
14
|
-
"usage: LogicalComponents extract <repoPath> [--out <components.json>] [--solution <file>]";
|
|
15
|
-
|
|
16
|
-
public static async Task<int> RunAsync(string[] args, TextWriter errors)
|
|
17
|
-
{
|
|
18
|
-
if (args is not [var repoPath, ..]
|
|
19
|
-
|| !CandidatesCommand.TryParseOptions(args[1..], "components.json", out var outPath, out var solution))
|
|
20
|
-
{
|
|
21
|
-
await errors.WriteLineAsync(Usage);
|
|
22
|
-
return 1;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
try
|
|
26
|
-
{
|
|
27
|
-
using var loaded = await WorkspaceLoader.LoadAsync(repoPath, solution: solution, log: errors);
|
|
28
|
-
await errors.WriteLineAsync($"Loaded {loaded.ProjectNames.Count} projects from {loaded.Source}.");
|
|
29
|
-
|
|
30
|
-
var extracted = await CouplingResolver.ExtractAsync(loaded, ExtractConfig.Load(loaded.RepoRoot));
|
|
31
|
-
var result = extracted with { FileHashes = FileHashes(loaded.RepoRoot, extracted) };
|
|
32
|
-
foreach (var warning in result.Warnings)
|
|
33
|
-
{
|
|
34
|
-
await errors.WriteLineAsync($"warning: {warning}");
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(outPath))!);
|
|
38
|
-
await File.WriteAllBytesAsync(outPath, ComponentsFile.Serialize(result));
|
|
39
|
-
await errors.WriteLineAsync(
|
|
40
|
-
$"{result.Components.Count} components, {result.ExternalNodes.Count} ports, {result.Edges.Count} edges, "
|
|
41
|
-
+ $"{result.Warnings.Count} warnings -> {Path.GetFullPath(outPath)}");
|
|
42
|
-
return 0;
|
|
43
|
-
}
|
|
44
|
-
catch (UnclassifiedCandidatesException e)
|
|
45
|
-
{
|
|
46
|
-
foreach (var type in e.Types)
|
|
47
|
-
{
|
|
48
|
-
await errors.WriteLineAsync($"unclassified {type}");
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
await errors.WriteLineAsync($"{e.Message}: run the classify step, review it, then extract again.");
|
|
52
|
-
return 4;
|
|
53
|
-
}
|
|
54
|
-
catch (Exception e) when (e is DirectoryNotFoundException or WorkspaceLoadException or ExtractConfigException)
|
|
55
|
-
{
|
|
56
|
-
await errors.WriteLineAsync(e.Message);
|
|
57
|
-
return 2;
|
|
58
|
-
}
|
|
59
|
-
catch (Exception e)
|
|
60
|
-
{
|
|
61
|
-
// Anything else (I/O, access, build host) is still a load failure: report it, don't crash with a stack trace.
|
|
62
|
-
await errors.WriteLineAsync($"Unexpected error while extracting {repoPath}: {e.Message}");
|
|
63
|
-
return 2;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/// <summary>
|
|
68
|
-
/// A repo-relative-file → content-hash map (see <see cref="ComponentsFile.HashFile"/>) for every file any component's
|
|
69
|
-
/// spans, reaches or outgoing edge citations point into, so <c>render</c> can later tell whether the repo changed.
|
|
70
|
-
/// </summary>
|
|
71
|
-
private static IReadOnlyDictionary<string, string> FileHashes(string repoRoot, ExtractResult result)
|
|
72
|
-
{
|
|
73
|
-
var files = result.Components
|
|
74
|
-
.SelectMany(c => c.Spans.Concat(c.Reaches ?? []).Select(s => s.File))
|
|
75
|
-
.Concat(result.Edges.Select(e => Citation.Parse(e.At)?.File).OfType<string>())
|
|
76
|
-
.Distinct(StringComparer.Ordinal)
|
|
77
|
-
.Order(StringComparer.Ordinal);
|
|
78
|
-
|
|
79
|
-
var hashes = new SortedDictionary<string, string>(StringComparer.Ordinal);
|
|
80
|
-
foreach (var file in files)
|
|
81
|
-
{
|
|
82
|
-
hashes[file] = ComponentsFile.HashFile(Path.Combine(repoRoot, file));
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return hashes;
|
|
86
|
-
}
|
|
87
|
-
}
|
package/template/.claude/skills/logical-components/scripts/LogicalComponents/ExtractConfig.cs
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
using System.Text.Json;
|
|
2
|
-
using System.Text.Json.Serialization;
|
|
3
|
-
|
|
4
|
-
namespace LogicalComponents;
|
|
5
|
-
|
|
6
|
-
/// <summary>A config file in the target repo that could not be read, or contradicts itself.</summary>
|
|
7
|
-
public sealed class ExtractConfigException(string message) : Exception(message);
|
|
8
|
-
|
|
9
|
-
/// <summary>Candidates the repo config does not classify yet: extract refuses to guess.</summary>
|
|
10
|
-
public sealed class UnclassifiedCandidatesException(IReadOnlyList<string> types)
|
|
11
|
-
: Exception($"{types.Count} candidate(s) not classified in {ExtractConfig.FileName}")
|
|
12
|
-
{
|
|
13
|
-
public IReadOnlyList<string> Types { get; } = types;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/// <summary>
|
|
17
|
-
/// The frozen classification in <c>logical-components.json</c> at the repo root: every candidate class is listed, by
|
|
18
|
-
/// full metadata name, either as a component or as a helper (with the reason it is not a component). An LLM proposes
|
|
19
|
-
/// the entries once and a person reviews them; extract only ever reads this file, so the same code and the same file
|
|
20
|
-
/// always give the same components.
|
|
21
|
-
/// </summary>
|
|
22
|
-
public sealed record ExtractConfig(IReadOnlyList<string> Components, IReadOnlyDictionary<string, string> Helpers)
|
|
23
|
-
{
|
|
24
|
-
public const string FileName = "logical-components.json";
|
|
25
|
-
|
|
26
|
-
public static ExtractConfig Empty { get; } = new([], new Dictionary<string, string>());
|
|
27
|
-
|
|
28
|
-
private static readonly JsonSerializerOptions Options = new()
|
|
29
|
-
{
|
|
30
|
-
PropertyNameCaseInsensitive = true,
|
|
31
|
-
ReadCommentHandling = JsonCommentHandling.Skip,
|
|
32
|
-
AllowTrailingCommas = true,
|
|
33
|
-
// A misspelt key ("helper") must fail rather than silently classify nothing.
|
|
34
|
-
UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow,
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
public bool IsClassified(string fullName) => Helpers.ContainsKey(fullName) || Components.Contains(fullName);
|
|
38
|
-
|
|
39
|
-
public static ExtractConfig Load(string repoRoot)
|
|
40
|
-
{
|
|
41
|
-
var path = Path.Combine(repoRoot, FileName);
|
|
42
|
-
if (!File.Exists(path))
|
|
43
|
-
{
|
|
44
|
-
return Empty;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
ConfigFile? file;
|
|
48
|
-
try
|
|
49
|
-
{
|
|
50
|
-
file = JsonSerializer.Deserialize<ConfigFile>(File.ReadAllText(path), Options);
|
|
51
|
-
}
|
|
52
|
-
catch (JsonException e)
|
|
53
|
-
{
|
|
54
|
-
throw new ExtractConfigException($"Invalid {FileName}: {e.Message}");
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (file?.Exclude is not null || file?.Include is not null)
|
|
58
|
-
{
|
|
59
|
-
throw new ExtractConfigException(
|
|
60
|
-
$"Invalid {FileName}: 'exclude'/'include' were replaced by 'components' and 'helpers' "
|
|
61
|
-
+ "(full type names); re-run the classify step to regenerate it.");
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
var components = file?.Components ?? [];
|
|
65
|
-
var helpers = file?.Helpers ?? [];
|
|
66
|
-
var both = components.Where(helpers.ContainsKey).Distinct().Order(StringComparer.Ordinal).ToList();
|
|
67
|
-
if (both.Count > 0)
|
|
68
|
-
{
|
|
69
|
-
throw new ExtractConfigException(
|
|
70
|
-
$"Invalid {FileName}: {string.Join(", ", both.Select(n => $"'{n}'"))} is in both components and helpers.");
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return new ExtractConfig(components, helpers);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Exclude and Include are read only to reject a file written for the old format with a useful message.
|
|
77
|
-
private sealed record ConfigFile(
|
|
78
|
-
string[]? Components, Dictionary<string, string>? Helpers, string[]? Exclude, string[]? Include);
|
|
79
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
<Project Sdk="Microsoft.NET.Sdk">
|
|
2
|
-
|
|
3
|
-
<PropertyGroup>
|
|
4
|
-
<TargetFramework>net10.0</TargetFramework>
|
|
5
|
-
<OutputType>Exe</OutputType>
|
|
6
|
-
<Nullable>enable</Nullable>
|
|
7
|
-
<ImplicitUsings>enable</ImplicitUsings>
|
|
8
|
-
</PropertyGroup>
|
|
9
|
-
|
|
10
|
-
<ItemGroup>
|
|
11
|
-
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="5.9.0" />
|
|
12
|
-
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="5.9.0" />
|
|
13
|
-
</ItemGroup>
|
|
14
|
-
|
|
15
|
-
</Project>
|
package/template/.claude/skills/logical-components/scripts/LogicalComponents/MarkdownCodeSpans.cs
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
using System.Text;
|
|
2
|
-
|
|
3
|
-
namespace LogicalComponents;
|
|
4
|
-
|
|
5
|
-
/// <summary>
|
|
6
|
-
/// CommonMark code-span parsing for <see cref="ResponsibilityValidation"/>'s plain-text check: a backtick run of
|
|
7
|
-
/// length n opens a code span, which closes at the next run of exactly length n (a run of a different length inside is
|
|
8
|
-
/// literal content; a run with no matching close is an unclosed delimiter, not a span). A naive `` `[^`]*` `` match
|
|
9
|
-
/// gets this wrong for an escaped or mismatched backtick, which could let HTML or a link hide inside what looks like,
|
|
10
|
-
/// but is not, a real code span.
|
|
11
|
-
/// </summary>
|
|
12
|
-
public static class MarkdownCodeSpans
|
|
13
|
-
{
|
|
14
|
-
/// <summary>The text with every real code span removed, and whether any backtick run was left unmatched.</summary>
|
|
15
|
-
public static (string Outside, bool Unclosed) Strip(string text)
|
|
16
|
-
{
|
|
17
|
-
var outside = new StringBuilder();
|
|
18
|
-
var unclosed = false;
|
|
19
|
-
var i = 0;
|
|
20
|
-
while (i < text.Length)
|
|
21
|
-
{
|
|
22
|
-
if (text[i] != '`')
|
|
23
|
-
{
|
|
24
|
-
outside.Append(text[i]);
|
|
25
|
-
i++;
|
|
26
|
-
continue;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
var openStart = i;
|
|
30
|
-
while (i < text.Length && text[i] == '`')
|
|
31
|
-
{
|
|
32
|
-
i++;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
var openLength = i - openStart;
|
|
36
|
-
var closeStart = -1;
|
|
37
|
-
var j = i;
|
|
38
|
-
while (j < text.Length)
|
|
39
|
-
{
|
|
40
|
-
if (text[j] != '`')
|
|
41
|
-
{
|
|
42
|
-
j++;
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
var runStart = j;
|
|
47
|
-
while (j < text.Length && text[j] == '`')
|
|
48
|
-
{
|
|
49
|
-
j++;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (j - runStart == openLength)
|
|
53
|
-
{
|
|
54
|
-
closeStart = runStart;
|
|
55
|
-
break;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (closeStart >= 0)
|
|
60
|
-
{
|
|
61
|
-
i = closeStart + openLength;
|
|
62
|
-
}
|
|
63
|
-
else
|
|
64
|
-
{
|
|
65
|
-
unclosed = true;
|
|
66
|
-
outside.Append('`', openLength);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return (outside.ToString(), unclosed);
|
|
71
|
-
}
|
|
72
|
-
}
|
package/template/.claude/skills/logical-components/scripts/LogicalComponents/MarkdownRenderer.cs
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
using System.Text;
|
|
2
|
-
|
|
3
|
-
namespace LogicalComponents;
|
|
4
|
-
|
|
5
|
-
/// <summary>
|
|
6
|
-
/// Writes the final document: <c># Logical Components</c> (one section per component, sorted by display name, each
|
|
7
|
-
/// responsibility with its citation) and <c># Coupling</c> (a legend, then a Mermaid flowchart of the extracted edges).
|
|
8
|
-
/// Everything but the responsibility text comes from the deterministic extraction; the output has LF line endings and no
|
|
9
|
-
/// timestamp.
|
|
10
|
-
/// Call only with responsibilities that passed <see cref="ResponsibilityValidation"/>.
|
|
11
|
-
/// </summary>
|
|
12
|
-
public static class MarkdownRenderer
|
|
13
|
-
{
|
|
14
|
-
/// <summary>
|
|
15
|
-
/// How to read the diagram, so incoming and outgoing arrows are not read backwards, and what it leaves out, so an
|
|
16
|
-
/// entry point with no incoming arrow is not mistaken for dead code.
|
|
17
|
-
/// </summary>
|
|
18
|
-
private const string Legend =
|
|
19
|
-
"`A --> B` means A depends on B: it calls, creates, holds or inherits from B. A component's incoming arrows are its "
|
|
20
|
-
+ "afferent coupling, its outgoing arrows its efferent coupling. Only components in `*.Application` and "
|
|
21
|
-
+ "`*.Infrastructure` projects are drawn; callers in other projects, such as the host, are not shown.";
|
|
22
|
-
|
|
23
|
-
public static string Render(
|
|
24
|
-
ExtractResult extraction, IReadOnlyDictionary<string, IReadOnlyList<Responsibility>> responsibilities)
|
|
25
|
-
{
|
|
26
|
-
var md = new StringBuilder();
|
|
27
|
-
md.Append("# Logical Components\n\n");
|
|
28
|
-
foreach (var component in extraction.Components
|
|
29
|
-
.OrderBy(c => c.Name, StringComparer.Ordinal)
|
|
30
|
-
.ThenBy(c => c.Id, StringComparer.Ordinal))
|
|
31
|
-
{
|
|
32
|
-
md.Append($"## {component.Name}\n\n");
|
|
33
|
-
md.Append($"`{SymbolNames.ReadableFullName(component.Type)}`\n\n");
|
|
34
|
-
foreach (var responsibility in responsibilities[component.Id])
|
|
35
|
-
{
|
|
36
|
-
md.Append($"- {responsibility.Text.Trim()} (`{Citation.Parse(responsibility.Cite)}`)\n");
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
md.Append('\n');
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// ELK lays out a large flowchart far more readably than Mermaid's default dagre.
|
|
43
|
-
md.Append("# Coupling\n\n").Append(Legend).Append("\n\n```mermaid\n%%{init: {\"layout\": \"elk\"}}%%\nflowchart LR\n");
|
|
44
|
-
var nodes = extraction.Components
|
|
45
|
-
.Select(c => (c.Id, c.Name))
|
|
46
|
-
.Concat(extraction.ExternalNodes.Select(n => (n.Id, n.Name)))
|
|
47
|
-
.OrderBy(n => n.Id, StringComparer.Ordinal);
|
|
48
|
-
foreach (var (id, name) in nodes)
|
|
49
|
-
{
|
|
50
|
-
md.Append($" {NodeId(id)}[\"{Escape(name)}\"]\n");
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// A --> B: A depends on B (efferent for A, afferent for B). How A reaches B (di, new, static, inherits) is not
|
|
54
|
-
// drawn; it stays in components.json.
|
|
55
|
-
foreach (var edge in extraction.Edges)
|
|
56
|
-
{
|
|
57
|
-
md.Append($" {NodeId(edge.From)} --> {NodeId(edge.To)}\n");
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
md.Append("```\n");
|
|
61
|
-
return md.ToString();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Mermaid node labels are double-quoted; a literal quote is written as an entity. Angle brackets are escaped the
|
|
65
|
-
// same way: some renderers treat a label as HTML, and an unescaped `<` in a type or module name could otherwise
|
|
66
|
-
// inject markup into the diagram.
|
|
67
|
-
private static string Escape(string label) => label.Replace("\"", "#quot;").Replace("<", "#lt;").Replace(">", "#gt;");
|
|
68
|
-
|
|
69
|
-
/// <summary>
|
|
70
|
-
/// A Mermaid-safe node id: prefixed so a component called <c>end</c>, <c>graph</c> or <c>class</c> is never read as
|
|
71
|
-
/// a keyword, and every character that is not an ASCII letter or digit - including a literal underscore, so the
|
|
72
|
-
/// escape below cannot collide with one already in the id - is replaced by <c>_x{hex}_</c>. That makes the mapping
|
|
73
|
-
/// injective (two different ids always get two different node ids), unlike collapsing every such character to a
|
|
74
|
-
/// single <c>_</c>, which let ids as different as "My Module_Queue" and "My-Module.Queue" collide.
|
|
75
|
-
/// </summary>
|
|
76
|
-
private static string NodeId(string id) => "n_" + string.Concat(
|
|
77
|
-
id.Select(c => char.IsAsciiLetterOrDigit(c) ? c.ToString() : $"_x{(int)c:x}_"));
|
|
78
|
-
}
|
package/template/.claude/skills/logical-components/scripts/LogicalComponents/MemberUseResolution.cs
DELETED
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
using System.Collections.Immutable;
|
|
2
|
-
using Microsoft.CodeAnalysis;
|
|
3
|
-
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
4
|
-
|
|
5
|
-
namespace LogicalComponents;
|
|
6
|
-
|
|
7
|
-
/// <summary>
|
|
8
|
-
/// Follows what a component's code uses until it lands on another component. A use of a member of:
|
|
9
|
-
/// <list type="bullet">
|
|
10
|
-
/// <item>a component is an edge to it;</item>
|
|
11
|
-
/// <item>an interface or abstract class (a seam) goes on to the member that runs in each implementation of the
|
|
12
|
-
/// construction used (see <see cref="SeamDispatch"/>);</item>
|
|
13
|
-
/// <item>any other type of the repo (a helper, a record, a non-candidate) is seen through: the code that runs for that
|
|
14
|
-
/// use is walked the same way, with the helper's type parameters bound to the use's type arguments (see
|
|
15
|
-
/// <see cref="TypeMap"/>). It is walked per member and per accessor, so reading one property never couples to what
|
|
16
|
-
/// another member or the setter calls, and compiler-written record members run nothing.</item>
|
|
17
|
-
/// </list>
|
|
18
|
-
/// Framework and library members end the walk. The kind of an edge is the kind of the last use; its citation is the
|
|
19
|
-
/// line in the component where the chain starts; <c>via</c> names the seams and seen-through types on the first path
|
|
20
|
-
/// found (members already walked for a component are not walked again). The helper code walked is the component's
|
|
21
|
-
/// <c>reaches</c>.
|
|
22
|
-
/// </summary>
|
|
23
|
-
public sealed class MemberUseResolution(LoadedWorkspace loaded, IReadOnlySet<string> components, PortResolution ports)
|
|
24
|
-
{
|
|
25
|
-
// How many seen-through hops one chain may follow before it is cut off with a warning: real helper chains never
|
|
26
|
-
// run this deep, but a generic helper that instantiates itself one level deeper at every hop (Nest<T> calling
|
|
27
|
-
// Nest<List<T>>) never revisits the same (member, bindings) pair, so the cycle guard below never catches it and the
|
|
28
|
-
// walk would otherwise recurse until the process stack overflows.
|
|
29
|
-
private const int MaxWalkDepth = 200;
|
|
30
|
-
|
|
31
|
-
public sealed record RawEdge(string From, string To, string Kind, IReadOnlyList<string> Via, string File, int Line);
|
|
32
|
-
|
|
33
|
-
/// <summary>
|
|
34
|
-
/// The edges a component's code leads to, the helper code it runs on the way (sorted, distinct), and warnings about
|
|
35
|
-
/// coupling the walk could not fully resolve.
|
|
36
|
-
/// </summary>
|
|
37
|
-
public sealed record Resolution(List<RawEdge> Edges, List<SourceSpan> Reaches, List<string> Warnings);
|
|
38
|
-
|
|
39
|
-
// Shared by every component's walk: what a piece of syntax uses never changes within one loaded solution.
|
|
40
|
-
private readonly Dictionary<SyntaxNode, List<MemberUses.MemberUse>> _uses = [];
|
|
41
|
-
private readonly Dictionary<INamedTypeSymbol, bool> _repoSource = new(SymbolEqualityComparer.Default);
|
|
42
|
-
|
|
43
|
-
/// <summary>
|
|
44
|
-
/// The edges from <paramref name="from"/> reached by <paramref name="uses"/>. <paramref name="self"/> holds the
|
|
45
|
-
/// component and the base classes whose code counts as its own: uses of those are not coupling. <paramref name="map"/>
|
|
46
|
-
/// binds the type parameters of any base class among <paramref name="uses"/>' declaring types to what the component
|
|
47
|
-
/// supplies as its base class construction, so a generic base's own seam calls resolve to the right construction.
|
|
48
|
-
/// </summary>
|
|
49
|
-
public async Task<Resolution> ResolveAsync(
|
|
50
|
-
string from, IReadOnlySet<string> self, IEnumerable<MemberUses.MemberUse> uses, TypeMap map,
|
|
51
|
-
CancellationToken cancellationToken)
|
|
52
|
-
{
|
|
53
|
-
var walk = new Walk(from, self);
|
|
54
|
-
foreach (var use in uses)
|
|
55
|
-
{
|
|
56
|
-
await ResolveUseAsync(use.Member, use.Kind, [], use.File, use.Line, map, walk, cancellationToken);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return new Resolution(
|
|
60
|
-
walk.Edges,
|
|
61
|
-
walk.Reaches.OrderBy(s => s.File, StringComparer.Ordinal).ThenBy(s => s.Start).ThenBy(s => s.End).ToList(),
|
|
62
|
-
walk.Warnings.Order(StringComparer.Ordinal).ToList());
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// One component's walk: members already seen through (for the same type bindings) are not walked again, which also
|
|
66
|
-
// ends cycles. Depth guards the chains a cycle can't catch (see MaxWalkDepth).
|
|
67
|
-
private sealed class Walk(string from, IReadOnlySet<string> self)
|
|
68
|
-
{
|
|
69
|
-
public string From { get; } = from;
|
|
70
|
-
public IReadOnlySet<string> Self { get; } = self;
|
|
71
|
-
public HashSet<string> Visited { get; } = new(StringComparer.Ordinal);
|
|
72
|
-
public List<RawEdge> Edges { get; } = [];
|
|
73
|
-
public HashSet<SourceSpan> Reaches { get; } = [];
|
|
74
|
-
public HashSet<string> Warnings { get; } = new(StringComparer.Ordinal);
|
|
75
|
-
public int Depth { get; set; }
|
|
76
|
-
|
|
77
|
-
public void Warn(string message) => Warnings.Add(message);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
private async Task ResolveUseAsync(
|
|
81
|
-
ISymbol member,
|
|
82
|
-
string kind,
|
|
83
|
-
ImmutableList<string> via,
|
|
84
|
-
string file,
|
|
85
|
-
int line,
|
|
86
|
-
TypeMap map,
|
|
87
|
-
Walk walk,
|
|
88
|
-
CancellationToken cancellationToken)
|
|
89
|
-
{
|
|
90
|
-
// A C# 14 extension member belongs to the static class around its extension block.
|
|
91
|
-
var declaring = member.ContainingType is { IsExtension: true } block ? block.ContainingType : member.ContainingType;
|
|
92
|
-
if (declaring is null)
|
|
93
|
-
{
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (map.Substitute(declaring) is INamedTypeSymbol bound)
|
|
98
|
-
{
|
|
99
|
-
declaring = bound;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
var owner = EdgeExtraction.Normalize(declaring.OriginalDefinition);
|
|
103
|
-
if (!IsRepo(owner) || walk.Self.Contains(SymbolNames.FullName(owner)))
|
|
104
|
-
{
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (components.Contains(SymbolNames.FullName(owner)))
|
|
109
|
-
{
|
|
110
|
-
walk.Edges.Add(new RawEdge(walk.From, SymbolNames.FullName(owner), kind, via, file, line));
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (EdgeExtraction.IsSeam(declaring) && !member.IsStatic && kind != EdgeExtraction.New)
|
|
115
|
-
{
|
|
116
|
-
// A concrete, non-virtual member of an abstract class cannot be overridden: it runs as written for every
|
|
117
|
-
// subclass (or none), so it is this walk's own behaviour rather than a per-implementation dispatch.
|
|
118
|
-
if (declaring.TypeKind == TypeKind.Class && !IsOverridable(member))
|
|
119
|
-
{
|
|
120
|
-
await SeeThroughAsync(member, kind, via.Add(SymbolNames.FullName(declaring)), file, line, map, walk, cancellationToken);
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
var implementations = await ports.ImplementationsAsync(declaring, cancellationToken);
|
|
125
|
-
if (implementations.Count == 0 && declaring.TypeKind == TypeKind.Class)
|
|
126
|
-
{
|
|
127
|
-
walk.Warn($"{SymbolNames.FullName(declaring.OriginalDefinition)}.{member.Name} has no concrete subclass in the repo");
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
foreach (var implementation in implementations)
|
|
131
|
-
{
|
|
132
|
-
if (SeamDispatch.ImplementationOf(implementation, declaring, member) is not { } runs)
|
|
133
|
-
{
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// An inherited default body lives on the seam itself: walk it rather than resolve the seam again.
|
|
138
|
-
if (runs.ContainingType is { } runsIn && EdgeExtraction.IsSeam(runsIn))
|
|
139
|
-
{
|
|
140
|
-
await SeeThroughAsync(runs, kind, via.Add(SymbolNames.FullName(declaring)), file, line, map, walk, cancellationToken);
|
|
141
|
-
}
|
|
142
|
-
else
|
|
143
|
-
{
|
|
144
|
-
// Bind the implementation's own type parameters (an open-generic decorator) and the resolved
|
|
145
|
-
// method's own type parameters (a generic method declared on the seam) to what this use actually
|
|
146
|
-
// supplies, so a seam the implementation calls in turn resolves to the right construction too.
|
|
147
|
-
var binding = SeamDispatch.BindingFor(implementation, declaring, member, runs, map);
|
|
148
|
-
await ResolveUseAsync(runs, kind, via.Add(SymbolNames.FullName(declaring)), file, line, binding, walk, cancellationToken);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
await SeeThroughAsync(member, kind, via.Add(SymbolNames.FullName(owner)), file, line, map, walk, cancellationToken);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
private async Task SeeThroughAsync(
|
|
159
|
-
ISymbol member,
|
|
160
|
-
string kind,
|
|
161
|
-
ImmutableList<string> via,
|
|
162
|
-
string file,
|
|
163
|
-
int line,
|
|
164
|
-
TypeMap map,
|
|
165
|
-
Walk walk,
|
|
166
|
-
CancellationToken cancellationToken)
|
|
167
|
-
{
|
|
168
|
-
// Compiler-written members (a record's ToString/Equals/Deconstruct, an auto-property accessor) run nothing of the
|
|
169
|
-
// type's own code; their declaring syntax is the whole type, which must not be walked. Constructors still run
|
|
170
|
-
// initializers. Using an instance field runs no code: its initializer ran when the object was constructed.
|
|
171
|
-
if ((member.IsImplicitlyDeclared && kind != EdgeExtraction.New) || member is IFieldSymbol { IsStatic: false })
|
|
172
|
-
{
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
var inner = map.For(member);
|
|
177
|
-
var key = (kind == EdgeExtraction.New ? "new " : "") + SymbolNames.MemberId(member) + "|" + inner.Key;
|
|
178
|
-
if (!walk.Visited.Add(key))
|
|
179
|
-
{
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// A cycle that never repeats a (member, bindings) pair (a generic helper instantiating itself one level
|
|
184
|
-
// deeper at every hop) would otherwise recurse until the process stack overflows: cut it off instead.
|
|
185
|
-
if (walk.Depth >= MaxWalkDepth)
|
|
186
|
-
{
|
|
187
|
-
walk.Warn($"{walk.From}: coupling walk stopped at depth {MaxWalkDepth} following {member.Name}, possible unbounded generic recursion");
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
walk.Depth++;
|
|
192
|
-
try
|
|
193
|
-
{
|
|
194
|
-
var (roots, runsImplicitBase) = kind == EdgeExtraction.New && member is IMethodSymbol constructor
|
|
195
|
-
? WalkRoots.Construction(constructor)
|
|
196
|
-
: (WalkRoots.Body(member), false);
|
|
197
|
-
Record(roots, walk);
|
|
198
|
-
|
|
199
|
-
foreach (var root in roots)
|
|
200
|
-
{
|
|
201
|
-
foreach (var use in await UsesAsync(root, cancellationToken))
|
|
202
|
-
{
|
|
203
|
-
await ResolveUseAsync(use.Member, use.Kind, via, file, line, inner, walk, cancellationToken);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (runsImplicitBase
|
|
208
|
-
&& member.ContainingType.BaseType?.InstanceConstructors.FirstOrDefault(c => c.Parameters.Length == 0) is { } baseConstructor)
|
|
209
|
-
{
|
|
210
|
-
await ResolveUseAsync(baseConstructor, EdgeExtraction.New, via, file, line, inner, walk, cancellationToken);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// The static constructor runs once, lazily, before the type's first static member use or instantiation:
|
|
214
|
-
// consistent with a static field's own initializer, which is already walked wherever that field is read.
|
|
215
|
-
if ((kind == EdgeExtraction.Static || kind == EdgeExtraction.New)
|
|
216
|
-
&& member.ContainingType?.OriginalDefinition.StaticConstructors.FirstOrDefault(c => !c.IsImplicitlyDeclared)
|
|
217
|
-
is { } staticConstructor)
|
|
218
|
-
{
|
|
219
|
-
await ResolveUseAsync(staticConstructor, EdgeExtraction.Call, via, file, line, inner, walk, cancellationToken);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
finally
|
|
223
|
-
{
|
|
224
|
-
walk.Depth--;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/// <summary>A method, property or event that a subclass could give a different body: dispatch, not fixed code.</summary>
|
|
229
|
-
private static bool IsOverridable(ISymbol member) => member switch
|
|
230
|
-
{
|
|
231
|
-
IMethodSymbol method => method.IsVirtual || method.IsAbstract || method.IsOverride,
|
|
232
|
-
IPropertySymbol property => property.IsVirtual || property.IsAbstract || property.IsOverride,
|
|
233
|
-
IEventSymbol @event => @event.IsVirtual || @event.IsAbstract || @event.IsOverride,
|
|
234
|
-
_ => false,
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
// The walked helper code becomes citable for the component; a parameter declares a value and runs nothing.
|
|
238
|
-
private void Record(IEnumerable<SyntaxNode> roots, Walk walk)
|
|
239
|
-
{
|
|
240
|
-
foreach (var root in roots.Where(r => r is not (ParameterSyntax or ParameterListSyntax)))
|
|
241
|
-
{
|
|
242
|
-
var (sourceKind, relative) = CitableSource.Classify(loaded, root.SyntaxTree);
|
|
243
|
-
if (sourceKind == CitableSource.Kind.InRepo)
|
|
244
|
-
{
|
|
245
|
-
var lines = root.GetLocation().GetLineSpan();
|
|
246
|
-
walk.Reaches.Add(new SourceSpan(relative, lines.StartLinePosition.Line + 1, lines.EndLinePosition.Line + 1));
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
private async Task<List<MemberUses.MemberUse>> UsesAsync(SyntaxNode root, CancellationToken cancellationToken)
|
|
252
|
-
{
|
|
253
|
-
if (!_uses.TryGetValue(root, out var uses))
|
|
254
|
-
{
|
|
255
|
-
uses = await MemberUses.ReadAsync([root], loaded, cancellationToken);
|
|
256
|
-
_uses[root] = uses;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
return uses;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
private bool IsRepo(INamedTypeSymbol type)
|
|
263
|
-
{
|
|
264
|
-
if (!_repoSource.TryGetValue(type, out var isRepo))
|
|
265
|
-
{
|
|
266
|
-
isRepo = CitableSource.IsRepoSource(type, loaded);
|
|
267
|
-
_repoSource[type] = isRepo;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
return isRepo;
|
|
271
|
-
}
|
|
272
|
-
}
|