@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.
Files changed (168) hide show
  1. package/INSTALLATION.md +1 -1
  2. package/README.md +2 -2
  3. package/manifest.json +3 -3
  4. package/package.json +1 -1
  5. package/template/.claude/skills/logical-components/SKILL.md +165 -94
  6. package/template/.claude/skills/logical-components/assets/component-diagram-template.html +104 -0
  7. package/template/.claude/skills/logical-components/references/component-analyst-prompt.md +88 -0
  8. package/template/.claude/skills/logical-components/references/component-level-detection.md +95 -0
  9. package/template/.claude/skills/logical-components/references/logical-component-concepts.md +92 -0
  10. package/template/.claude/skills/logical-components/scripts/build-component-model.cjs +120 -0
  11. package/template/.claude/skills/logical-components/scripts/index-declared-symbols.cjs +106 -0
  12. package/template/.claude/skills/logical-components/scripts/lib/coupling-metrics.cjs +48 -0
  13. package/template/.claude/skills/logical-components/scripts/lib/declared-symbol-patterns.cjs +57 -0
  14. package/template/.claude/skills/logical-components/scripts/lib/html-sections.cjs +91 -0
  15. package/template/.claude/skills/logical-components/scripts/lib/mermaid-graph.cjs +55 -0
  16. package/template/.claude/skills/logical-components/scripts/lib/read-json-input.cjs +109 -0
  17. package/template/.claude/skills/logical-components/scripts/lib/repo-walk.cjs +61 -0
  18. package/template/.claude/skills/logical-components/scripts/lib/verify-claims.cjs +159 -0
  19. package/template/.claude/skills/logical-components/scripts/render-component-diagram-html.cjs +112 -0
  20. package/template/.claude/skills/logical-components/scripts/scan-folder-tree.cjs +88 -0
  21. package/template/.claude/skills/logical-components/scripts/select-stale-components.cjs +129 -0
  22. package/template/.claude/statusline.cjs +26 -22
  23. package/template/.claude/skills/logical-components/.gitattributes +0 -2
  24. package/template/.claude/skills/logical-components/references/component-classifier-prompt.md +0 -53
  25. package/template/.claude/skills/logical-components/references/responsibility-agent-prompt.md +0 -67
  26. package/template/.claude/skills/logical-components/references/responsibility-verifier-prompt.md +0 -39
  27. package/template/.claude/skills/logical-components/scripts/Directory.Build.props +0 -7
  28. package/template/.claude/skills/logical-components/scripts/Directory.Build.rsp +0 -1
  29. package/template/.claude/skills/logical-components/scripts/Directory.Build.targets +0 -6
  30. package/template/.claude/skills/logical-components/scripts/Directory.Packages.props +0 -8
  31. package/template/.claude/skills/logical-components/scripts/LogicalComponents/BatchPlanning.cs +0 -70
  32. package/template/.claude/skills/logical-components/scripts/LogicalComponents/BatchesCommand.cs +0 -47
  33. package/template/.claude/skills/logical-components/scripts/LogicalComponents/CandidatesCommand.cs +0 -98
  34. package/template/.claude/skills/logical-components/scripts/LogicalComponents/CitableSource.cs +0 -54
  35. package/template/.claude/skills/logical-components/scripts/LogicalComponents/Citation.cs +0 -37
  36. package/template/.claude/skills/logical-components/scripts/LogicalComponents/ClassifyBatchesCommand.cs +0 -86
  37. package/template/.claude/skills/logical-components/scripts/LogicalComponents/ClassifyMergeCommand.cs +0 -219
  38. package/template/.claude/skills/logical-components/scripts/LogicalComponents/ComponentDiscovery.cs +0 -180
  39. package/template/.claude/skills/logical-components/scripts/LogicalComponents/ComponentRule.cs +0 -130
  40. package/template/.claude/skills/logical-components/scripts/LogicalComponents/ComponentsFile.cs +0 -101
  41. package/template/.claude/skills/logical-components/scripts/LogicalComponents/CouplingResolver.cs +0 -251
  42. package/template/.claude/skills/logical-components/scripts/LogicalComponents/DisplayName.cs +0 -14
  43. package/template/.claude/skills/logical-components/scripts/LogicalComponents/EdgeExtraction.cs +0 -111
  44. package/template/.claude/skills/logical-components/scripts/LogicalComponents/ExtractCommand.cs +0 -87
  45. package/template/.claude/skills/logical-components/scripts/LogicalComponents/ExtractConfig.cs +0 -79
  46. package/template/.claude/skills/logical-components/scripts/LogicalComponents/LogicalComponents.csproj +0 -15
  47. package/template/.claude/skills/logical-components/scripts/LogicalComponents/MarkdownCodeSpans.cs +0 -72
  48. package/template/.claude/skills/logical-components/scripts/LogicalComponents/MarkdownRenderer.cs +0 -78
  49. package/template/.claude/skills/logical-components/scripts/LogicalComponents/MemberUseResolution.cs +0 -272
  50. package/template/.claude/skills/logical-components/scripts/LogicalComponents/MemberUses.cs +0 -231
  51. package/template/.claude/skills/logical-components/scripts/LogicalComponents/PlainText.cs +0 -55
  52. package/template/.claude/skills/logical-components/scripts/LogicalComponents/PortResolution.cs +0 -75
  53. package/template/.claude/skills/logical-components/scripts/LogicalComponents/Program.cs +0 -27
  54. package/template/.claude/skills/logical-components/scripts/LogicalComponents/ProjectFilters.cs +0 -77
  55. package/template/.claude/skills/logical-components/scripts/LogicalComponents/PruneCommand.cs +0 -164
  56. package/template/.claude/skills/logical-components/scripts/LogicalComponents/RenderCommand.cs +0 -234
  57. package/template/.claude/skills/logical-components/scripts/LogicalComponents/ResponsibilityBatches.cs +0 -84
  58. package/template/.claude/skills/logical-components/scripts/LogicalComponents/ResponsibilityValidation.cs +0 -197
  59. package/template/.claude/skills/logical-components/scripts/LogicalComponents/SeamDispatch.cs +0 -115
  60. package/template/.claude/skills/logical-components/scripts/LogicalComponents/SourceCodeLines.cs +0 -79
  61. package/template/.claude/skills/logical-components/scripts/LogicalComponents/SymbolNames.cs +0 -53
  62. package/template/.claude/skills/logical-components/scripts/LogicalComponents/TypeMap.cs +0 -106
  63. package/template/.claude/skills/logical-components/scripts/LogicalComponents/WalkRoots.cs +0 -63
  64. package/template/.claude/skills/logical-components/scripts/LogicalComponents/WorkspaceLoader.cs +0 -297
  65. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/AssignIdsTests.cs +0 -41
  66. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/BatchPlanningTests.cs +0 -90
  67. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/BatchesCommandTests.cs +0 -37
  68. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/CandidatesCommandTests.cs +0 -59
  69. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/CitationTests.cs +0 -42
  70. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/ClassifyCommandsTests.cs +0 -249
  71. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/ComponentDiscoveryTests.cs +0 -253
  72. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/CouplingAppExtractionTests.cs +0 -188
  73. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/CouplingAppWorkspace.cs +0 -30
  74. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/CouplingExtractionTests.cs +0 -174
  75. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/DisplayNameTests.cs +0 -17
  76. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/ExtractConfigTests.cs +0 -72
  77. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/FixturePaths.cs +0 -36
  78. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/LogicalComponents.Tests.csproj +0 -23
  79. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/MarkdownRendererTests.cs +0 -93
  80. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/ProjectFiltersTests.cs +0 -69
  81. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/PruneCommandTests.cs +0 -149
  82. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/RenderCommandTests.cs +0 -225
  83. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/RenderGoldenTests.cs +0 -34
  84. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/RenderTestData.cs +0 -55
  85. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/ResponsibilityValidationTests.cs +0 -315
  86. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/SampleAppWorkspace.cs +0 -27
  87. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/WorkspaceLoaderTests.cs +0 -266
  88. package/template/.claude/skills/logical-components/scripts/NuGet.config +0 -17
  89. package/template/.claude/skills/logical-components/scripts/fixtures/BrokenApp/src/Broken.Application/Broken.Application.csproj +0 -7
  90. package/template/.claude/skills/logical-components/scripts/fixtures/BrokenApp/src/Broken.Application/Broken.cs +0 -7
  91. package/template/.claude/skills/logical-components/scripts/fixtures/CompositionRootApp/CompositionRootApp.slnx +0 -5
  92. package/template/.claude/skills/logical-components/scripts/fixtures/CompositionRootApp/src/Root.Application/HostBuilderExtensions.cs +0 -15
  93. package/template/.claude/skills/logical-components/scripts/fixtures/CompositionRootApp/src/Root.Application/HostBuilderShims.cs +0 -25
  94. package/template/.claude/skills/logical-components/scripts/fixtures/CompositionRootApp/src/Root.Application/NativeInterop.cs +0 -17
  95. package/template/.claude/skills/logical-components/scripts/fixtures/CompositionRootApp/src/Root.Application/Root.Application.csproj +0 -7
  96. package/template/.claude/skills/logical-components/scripts/fixtures/CompositionRootApp/src/Root.Application/RootAutofacModule.cs +0 -11
  97. package/template/.claude/skills/logical-components/scripts/fixtures/CompositionRootApp/src/Root.Application/RootGreeter.cs +0 -7
  98. package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/logical-components.json +0 -48
  99. package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/src/CouplingApp.Application/AbstractMemberCases.cs +0 -27
  100. package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/src/CouplingApp.Application/AttributeCases.cs +0 -23
  101. package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/src/CouplingApp.Application/CouplingApp.Application.csproj +0 -7
  102. package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/src/CouplingApp.Application/GenericBaseCases.cs +0 -14
  103. package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/src/CouplingApp.Application/GenericSeamBindingCases.cs +0 -59
  104. package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/src/CouplingApp.Application/ImplicitCallCases.cs +0 -230
  105. package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/src/CouplingApp.Application/PartialInheritCases.First.cs +0 -11
  106. package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/src/CouplingApp.Application/PartialInheritCases.Second.cs +0 -14
  107. package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/src/CouplingApp.Application/StackOverflowCases.cs +0 -14
  108. package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/src/CouplingApp.Application/ViaDisambiguationCases.cs +0 -36
  109. package/template/.claude/skills/logical-components/scripts/fixtures/MultiTargetApp/MultiTargetApp.slnx +0 -7
  110. package/template/.claude/skills/logical-components/scripts/fixtures/MultiTargetApp/src/Legacy.FSharp/Legacy.FSharp.fsproj +0 -8
  111. package/template/.claude/skills/logical-components/scripts/fixtures/MultiTargetApp/src/Legacy.FSharp/Library.fs +0 -5
  112. package/template/.claude/skills/logical-components/scripts/fixtures/MultiTargetApp/src/Multi.Application/Multi.Application.csproj +0 -7
  113. package/template/.claude/skills/logical-components/scripts/fixtures/MultiTargetApp/src/Multi.Application/Scheduling.cs +0 -14
  114. package/template/.claude/skills/logical-components/scripts/fixtures/MultiTargetApp/src/Multi.Infrastructure/Multi.Infrastructure.csproj +0 -12
  115. package/template/.claude/skills/logical-components/scripts/fixtures/MultiTargetApp/src/Multi.Infrastructure/SchedulingClient.cs +0 -12
  116. package/template/.claude/skills/logical-components/scripts/fixtures/NoSolutionApp/src/Solo.Application/Solo.Application.csproj +0 -7
  117. package/template/.claude/skills/logical-components/scripts/fixtures/NoSolutionApp/src/Solo.Application/SoloGreeting.cs +0 -7
  118. package/template/.claude/skills/logical-components/scripts/fixtures/NoSolutionApp/src/Solo.Application.Tests/FakeGreeting.cs +0 -6
  119. package/template/.claude/skills/logical-components/scripts/fixtures/NoSolutionApp/src/Solo.Application.Tests/Solo.Application.Tests.csproj +0 -11
  120. package/template/.claude/skills/logical-components/scripts/fixtures/ObjOutputApp/ObjOutputApp.slnx +0 -5
  121. package/template/.claude/skills/logical-components/scripts/fixtures/ObjOutputApp/src/Obj.Application/Greeting.cs +0 -7
  122. package/template/.claude/skills/logical-components/scripts/fixtures/ObjOutputApp/src/Obj.Application/Obj.Application.csproj +0 -14
  123. package/template/.claude/skills/logical-components/scripts/fixtures/RenderGolden/components.json +0 -67
  124. package/template/.claude/skills/logical-components/scripts/fixtures/RenderGolden/expected.md +0 -28
  125. package/template/.claude/skills/logical-components/scripts/fixtures/RenderGolden/repo/src/App/OrderAcceptance.cs +0 -16
  126. package/template/.claude/skills/logical-components/scripts/fixtures/RenderGolden/repo/src/Infra/OrderQueue.cs +0 -10
  127. package/template/.claude/skills/logical-components/scripts/fixtures/RenderGolden/resp/batch-01.json +0 -18
  128. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/SampleApp.slnx +0 -10
  129. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/logical-components.json +0 -78
  130. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/CandidateCases.cs +0 -37
  131. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/CouplingCases.cs +0 -33
  132. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/DependencyInjection.cs +0 -18
  133. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/EdgeCases.cs +0 -63
  134. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/LoudNotifying.Base.cs +0 -5
  135. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/LoudNotifying.cs +0 -7
  136. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/OrderAcceptance.cs +0 -17
  137. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/OrderOptions.cs +0 -9
  138. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/OrderRejectedException.cs +0 -7
  139. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/OrderRequest.cs +0 -4
  140. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/OrderRunning.cs +0 -18
  141. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/OrderValidation.cs +0 -6
  142. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/Ports.cs +0 -19
  143. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/PriceRules.cs +0 -7
  144. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/ReceiptPrinting.Totals.cs +0 -6
  145. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/ReceiptPrinting.cs +0 -7
  146. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/ReviewCases.cs +0 -107
  147. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/ReviewFixCases.cs +0 -143
  148. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/RunClock.cs +0 -12
  149. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/Sample.Application.csproj +0 -10
  150. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/SeeThroughCases.cs +0 -89
  151. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Billing.Application/OrderRunning.cs +0 -7
  152. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Billing.Application/Sample.Billing.Application.csproj +0 -11
  153. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/ForwardedNotifier.cs +0 -8
  154. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/InfrastructureDependencyInjection.cs +0 -16
  155. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/Mailer.cs +0 -6
  156. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/OrderQueue.cs +0 -11
  157. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/QueueEntry.cs +0 -4
  158. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/QueueStore.cs +0 -8
  159. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/ReviewCases.cs +0 -35
  160. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/ReviewFixCases.cs +0 -13
  161. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/Sample.Infrastructure.csproj +0 -13
  162. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/SeeThroughCases.cs +0 -18
  163. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/UnreachedPlumbing.cs +0 -7
  164. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/tests/Sample.Tests/FakeOrderQueue.cs +0 -11
  165. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/tests/Sample.Tests/Sample.Tests.csproj +0 -11
  166. package/template/.claude/skills/logical-components/scripts/fixtures/SharedOutsideRepo/SharedClock.cs +0 -7
  167. package/template/.claude/skills/logical-components/scripts/global.json +0 -5
  168. package/template/.claude/skills/logical-components/scripts/smoke-sample.sh +0 -26
@@ -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
- }
@@ -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
- }
@@ -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
- }