@phuc1403/musketeer 0.12.0 → 0.14.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 +3 -3
- package/package.json +1 -1
- 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/statusline.cjs +26 -22
- 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/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
|
-
}
|
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
using Microsoft.CodeAnalysis;
|
|
2
|
-
using Microsoft.CodeAnalysis.CSharp;
|
|
3
|
-
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
4
|
-
|
|
5
|
-
namespace LogicalComponents;
|
|
6
|
-
|
|
7
|
-
/// <summary>
|
|
8
|
-
/// Reads what a piece of code uses, as members: <c>new</c> (the constructor an object creation, a <c>: this(...)</c> /
|
|
9
|
-
/// <c>: base(...)</c> initializer or a primary-constructor base call runs), <c>static</c> (a static member, an extension
|
|
10
|
-
/// method or C# 14 extension member, a user-defined operator or conversion) and <c>call</c> (an instance member used on
|
|
11
|
-
/// any receiver, an indexer or conditional-access indexer, the enumerator, deconstruction and <c>MoveNext</c>/<c>Current</c>
|
|
12
|
-
/// a <c>foreach</c> runs, the <c>Add</c> a collection initializer element runs, a deconstruction assignment's
|
|
13
|
-
/// <c>Deconstruct</c>, and the <c>GetAwaiter</c>/<c>IsCompleted</c>/<c>GetResult</c> an <c>await</c> runs). A property or
|
|
14
|
-
/// indexer use is the accessor that actually runs: a read is the getter, an assignment the setter, a compound
|
|
15
|
-
/// assignment or increment both. Attribute names are not uses: an attribute is metadata the compiler reads, not code the
|
|
16
|
-
/// annotated member runs. Only citable source is read, so generator output never contributes. What a use couples to is
|
|
17
|
-
/// decided by <see cref="MemberUseResolution"/>.
|
|
18
|
-
/// </summary>
|
|
19
|
-
public static class MemberUses
|
|
20
|
-
{
|
|
21
|
-
/// <summary>One use of <see cref="Member"/>, at a repo-relative file and 1-based line.</summary>
|
|
22
|
-
public sealed record MemberUse(ISymbol Member, string Kind, string File, int Line);
|
|
23
|
-
|
|
24
|
-
/// <summary>
|
|
25
|
-
/// Every member use inside the given syntax nodes, in source order. Nodes outside the repo, and nodes inside an
|
|
26
|
-
/// attribute list, are skipped.
|
|
27
|
-
/// </summary>
|
|
28
|
-
public static async Task<List<MemberUse>> ReadAsync(
|
|
29
|
-
IEnumerable<SyntaxNode> roots, LoadedWorkspace loaded, CancellationToken cancellationToken)
|
|
30
|
-
{
|
|
31
|
-
var uses = new List<MemberUse>();
|
|
32
|
-
foreach (var root in roots)
|
|
33
|
-
{
|
|
34
|
-
var (kind, file) = CitableSource.Classify(loaded, root.SyntaxTree);
|
|
35
|
-
if (kind != CitableSource.Kind.InRepo || loaded.Solution.GetDocument(root.SyntaxTree) is not { } document)
|
|
36
|
-
{
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
var model = await document.GetSemanticModelAsync(cancellationToken)
|
|
41
|
-
?? throw new InvalidOperationException($"No semantic model for {file}");
|
|
42
|
-
|
|
43
|
-
// An attribute name resolves to its constructor, but applying an attribute is not running code: never
|
|
44
|
-
// descend into an attribute list at all, so nothing inside one is ever read as a use.
|
|
45
|
-
foreach (var node in root.DescendantNodesAndSelf(descendant => descendant is not AttributeListSyntax))
|
|
46
|
-
{
|
|
47
|
-
var line = node.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
|
|
48
|
-
uses.AddRange(Uses(node, model, cancellationToken).Select(u => new MemberUse(u.Member, u.Kind, file, line)));
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return uses;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
private static IEnumerable<(ISymbol Member, string Kind)> Uses(SyntaxNode node, SemanticModel model, CancellationToken token)
|
|
56
|
-
{
|
|
57
|
-
ISymbol? Symbol() => model.GetSymbolInfo(node, token).Symbol;
|
|
58
|
-
(ISymbol, string) AsUse(ISymbol member) => (member, member.IsStatic ? EdgeExtraction.Static : EdgeExtraction.Call);
|
|
59
|
-
|
|
60
|
-
switch (node)
|
|
61
|
-
{
|
|
62
|
-
case BaseObjectCreationExpressionSyntax or ConstructorInitializerSyntax or PrimaryConstructorBaseTypeSyntax:
|
|
63
|
-
if (Symbol() is IMethodSymbol constructor)
|
|
64
|
-
{
|
|
65
|
-
yield return (constructor, EdgeExtraction.New);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
break;
|
|
69
|
-
case SimpleNameSyntax name when !IsInsideNameOf(name, model):
|
|
70
|
-
foreach (var use in Classify(Symbol(), name))
|
|
71
|
-
{
|
|
72
|
-
yield return use;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
break;
|
|
76
|
-
case ElementAccessExpressionSyntax or ElementBindingExpressionSyntax when Symbol() is IPropertySymbol indexer:
|
|
77
|
-
foreach (var accessor in AccessorsThatRun(indexer, node))
|
|
78
|
-
{
|
|
79
|
-
yield return (accessor, EdgeExtraction.Call);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
break;
|
|
83
|
-
case BinaryExpressionSyntax or PrefixUnaryExpressionSyntax or PostfixUnaryExpressionSyntax
|
|
84
|
-
or CastExpressionSyntax or AssignmentExpressionSyntax
|
|
85
|
-
when Symbol() is IMethodSymbol { MethodKind: MethodKind.UserDefinedOperator or MethodKind.Conversion } op:
|
|
86
|
-
yield return (op, EdgeExtraction.Static);
|
|
87
|
-
break;
|
|
88
|
-
// A deconstruction assignment, `(a, b) = point;`: tried before the operator-assignment case above, but only
|
|
89
|
-
// claims the node when it is actually a deconstruction, so a plain or compound assignment still falls
|
|
90
|
-
// through to it.
|
|
91
|
-
case AssignmentExpressionSyntax assignment when DeconstructMethods(model.GetDeconstructionInfo(assignment)).ToList()
|
|
92
|
-
is { Count: > 0 } assignmentDeconstructs:
|
|
93
|
-
foreach (var method in assignmentDeconstructs)
|
|
94
|
-
{
|
|
95
|
-
yield return AsUse(method);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
break;
|
|
99
|
-
case CommonForEachStatementSyntax loop:
|
|
100
|
-
var info = model.GetForEachStatementInfo(loop);
|
|
101
|
-
foreach (var member in new ISymbol?[] { info.GetEnumeratorMethod, info.MoveNextMethod, info.CurrentProperty?.GetMethod })
|
|
102
|
-
{
|
|
103
|
-
if (member is not null)
|
|
104
|
-
{
|
|
105
|
-
yield return AsUse(member);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// foreach (var (a, b) in pairs): the per-element deconstruction, same as an explicit assignment's.
|
|
110
|
-
if (loop is ForEachVariableStatementSyntax deconstructingLoop)
|
|
111
|
-
{
|
|
112
|
-
foreach (var method in DeconstructMethods(model.GetDeconstructionInfo(deconstructingLoop)))
|
|
113
|
-
{
|
|
114
|
-
yield return AsUse(method);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
break;
|
|
119
|
-
case InitializerExpressionSyntax initializer when initializer.IsKind(SyntaxKind.CollectionInitializerExpression):
|
|
120
|
-
foreach (var element in initializer.Expressions)
|
|
121
|
-
{
|
|
122
|
-
if (model.GetCollectionInitializerSymbolInfo(element).Symbol is IMethodSymbol add)
|
|
123
|
-
{
|
|
124
|
-
yield return AsUse(add);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
break;
|
|
129
|
-
case AwaitExpressionSyntax awaitExpression:
|
|
130
|
-
var awaited = model.GetAwaitExpressionInfo(awaitExpression);
|
|
131
|
-
foreach (var member in new ISymbol?[] { awaited.GetAwaiterMethod, awaited.IsCompletedProperty?.GetMethod, awaited.GetResultMethod })
|
|
132
|
-
{
|
|
133
|
-
if (member is not null)
|
|
134
|
-
{
|
|
135
|
-
yield return AsUse(member);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
break;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/// <summary>The Deconstruct methods a deconstruction runs, including any nested tuple/pattern's own.</summary>
|
|
144
|
-
private static IEnumerable<IMethodSymbol> DeconstructMethods(DeconstructionInfo info)
|
|
145
|
-
{
|
|
146
|
-
if (info.Method is { } method)
|
|
147
|
-
{
|
|
148
|
-
yield return method;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
foreach (var nested in info.Nested)
|
|
152
|
-
{
|
|
153
|
-
foreach (var method2 in DeconstructMethods(nested))
|
|
154
|
-
{
|
|
155
|
-
yield return method2;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/// <summary>
|
|
161
|
-
/// A symbol a name refers to, as uses: static or extension members are <c>static</c> (a classic extension method is
|
|
162
|
-
/// seen as its unreduced static form), other members are <c>call</c>, a property is the accessors that run. Locals,
|
|
163
|
-
/// parameters, types, namespaces and consts (copied into the caller at compile time) are not uses.
|
|
164
|
-
/// </summary>
|
|
165
|
-
private static IEnumerable<(ISymbol, string)> Classify(ISymbol? symbol, SyntaxNode usage)
|
|
166
|
-
{
|
|
167
|
-
var kind = symbol is not null && (symbol.IsStatic || symbol.ContainingType is { IsExtension: true })
|
|
168
|
-
? EdgeExtraction.Static
|
|
169
|
-
: EdgeExtraction.Call;
|
|
170
|
-
switch (symbol)
|
|
171
|
-
{
|
|
172
|
-
case IFieldSymbol { IsConst: true }:
|
|
173
|
-
case IMethodSymbol { MethodKind: MethodKind.LocalFunction or MethodKind.AnonymousFunction }:
|
|
174
|
-
break;
|
|
175
|
-
case IMethodSymbol { ReducedFrom: { } extension }:
|
|
176
|
-
yield return (extension, EdgeExtraction.Static);
|
|
177
|
-
break;
|
|
178
|
-
case IPropertySymbol property:
|
|
179
|
-
foreach (var accessor in AccessorsThatRun(property, usage))
|
|
180
|
-
{
|
|
181
|
-
yield return (accessor, kind);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
break;
|
|
185
|
-
case IMethodSymbol or IFieldSymbol or IEventSymbol:
|
|
186
|
-
yield return (symbol, kind);
|
|
187
|
-
break;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/// <summary>
|
|
192
|
-
/// The accessors a property or indexer use runs, from where it stands: the target of <c>=</c> (including an object
|
|
193
|
-
/// initializer) runs the setter; a compound assignment, <c>++</c>/<c>--</c>, or a <c>ref</c>/<c>out</c> argument runs
|
|
194
|
-
/// both; anything else is a read and runs the getter. An accessor the property does not have runs nothing.
|
|
195
|
-
/// </summary>
|
|
196
|
-
private static IEnumerable<IMethodSymbol> AccessorsThatRun(IPropertySymbol property, SyntaxNode usage)
|
|
197
|
-
{
|
|
198
|
-
// The whole access expression: `a.B` for the name B, `a?.B` for a conditional access, the name itself otherwise.
|
|
199
|
-
var access = usage.Parent switch
|
|
200
|
-
{
|
|
201
|
-
MemberAccessExpressionSyntax member when member.Name == usage => member,
|
|
202
|
-
MemberBindingExpressionSyntax binding => (SyntaxNode)binding,
|
|
203
|
-
_ => usage,
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
var (reads, writes) = access.Parent switch
|
|
207
|
-
{
|
|
208
|
-
AssignmentExpressionSyntax assignment when assignment.Left == access =>
|
|
209
|
-
(!assignment.IsKind(SyntaxKind.SimpleAssignmentExpression), true),
|
|
210
|
-
PrefixUnaryExpressionSyntax or PostfixUnaryExpressionSyntax => (true, true),
|
|
211
|
-
ArgumentSyntax { RefKindKeyword.RawKind: not 0 } => (true, true),
|
|
212
|
-
_ => (true, false),
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
if (reads && property.GetMethod is { } getter)
|
|
216
|
-
{
|
|
217
|
-
yield return getter;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (writes && property.SetMethod is { } setter)
|
|
221
|
-
{
|
|
222
|
-
yield return setter;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// nameof(Foo.Bar) mentions a member without using it.
|
|
227
|
-
private static bool IsInsideNameOf(SyntaxNode node, SemanticModel model) =>
|
|
228
|
-
node.Ancestors().OfType<InvocationExpressionSyntax>().Any(invocation =>
|
|
229
|
-
invocation.Expression is IdentifierNameSyntax { Identifier.Text: "nameof" }
|
|
230
|
-
&& model.GetSymbolInfo(invocation).Symbol is null);
|
|
231
|
-
}
|