@phuc1403/musketeer 0.10.0 → 0.12.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 (160) hide show
  1. package/INSTALLATION.md +1 -0
  2. package/README.md +2 -2
  3. package/manifest.json +29 -3
  4. package/package.json +1 -1
  5. package/src/provisioner/detect.js +17 -2
  6. package/src/resolver.js +8 -3
  7. package/src/schema.js +1 -0
  8. package/src/settings-merger.js +0 -0
  9. package/template/.claude/hooks/inject-naming-rule-into-subagents.cjs +44 -0
  10. package/template/.claude/hooks/validate-cml-hook.js +6 -5
  11. package/template/.claude/rules/csharp-identifier-naming.md +77 -0
  12. package/template/.claude/skills/code-review/SKILL.md +4 -1
  13. package/template/.claude/skills/code-review/references/naming-rule-review.md +41 -0
  14. package/template/.claude/skills/logical-components/.gitattributes +2 -0
  15. package/template/.claude/skills/logical-components/SKILL.md +107 -0
  16. package/template/.claude/skills/logical-components/references/component-classifier-prompt.md +53 -0
  17. package/template/.claude/skills/logical-components/references/responsibility-agent-prompt.md +67 -0
  18. package/template/.claude/skills/logical-components/references/responsibility-verifier-prompt.md +39 -0
  19. package/template/.claude/skills/logical-components/scripts/Directory.Build.props +7 -0
  20. package/template/.claude/skills/logical-components/scripts/Directory.Build.rsp +1 -0
  21. package/template/.claude/skills/logical-components/scripts/Directory.Build.targets +6 -0
  22. package/template/.claude/skills/logical-components/scripts/Directory.Packages.props +8 -0
  23. package/template/.claude/skills/logical-components/scripts/LogicalComponents/BatchPlanning.cs +70 -0
  24. package/template/.claude/skills/logical-components/scripts/LogicalComponents/BatchesCommand.cs +47 -0
  25. package/template/.claude/skills/logical-components/scripts/LogicalComponents/CandidatesCommand.cs +98 -0
  26. package/template/.claude/skills/logical-components/scripts/LogicalComponents/CitableSource.cs +54 -0
  27. package/template/.claude/skills/logical-components/scripts/LogicalComponents/Citation.cs +37 -0
  28. package/template/.claude/skills/logical-components/scripts/LogicalComponents/ClassifyBatchesCommand.cs +86 -0
  29. package/template/.claude/skills/logical-components/scripts/LogicalComponents/ClassifyMergeCommand.cs +219 -0
  30. package/template/.claude/skills/logical-components/scripts/LogicalComponents/ComponentDiscovery.cs +180 -0
  31. package/template/.claude/skills/logical-components/scripts/LogicalComponents/ComponentRule.cs +130 -0
  32. package/template/.claude/skills/logical-components/scripts/LogicalComponents/ComponentsFile.cs +101 -0
  33. package/template/.claude/skills/logical-components/scripts/LogicalComponents/CouplingResolver.cs +251 -0
  34. package/template/.claude/skills/logical-components/scripts/LogicalComponents/DisplayName.cs +14 -0
  35. package/template/.claude/skills/logical-components/scripts/LogicalComponents/EdgeExtraction.cs +111 -0
  36. package/template/.claude/skills/logical-components/scripts/LogicalComponents/ExtractCommand.cs +87 -0
  37. package/template/.claude/skills/logical-components/scripts/LogicalComponents/ExtractConfig.cs +79 -0
  38. package/template/.claude/skills/logical-components/scripts/LogicalComponents/LogicalComponents.csproj +15 -0
  39. package/template/.claude/skills/logical-components/scripts/LogicalComponents/MarkdownCodeSpans.cs +72 -0
  40. package/template/.claude/skills/logical-components/scripts/LogicalComponents/MarkdownRenderer.cs +78 -0
  41. package/template/.claude/skills/logical-components/scripts/LogicalComponents/MemberUseResolution.cs +272 -0
  42. package/template/.claude/skills/logical-components/scripts/LogicalComponents/MemberUses.cs +231 -0
  43. package/template/.claude/skills/logical-components/scripts/LogicalComponents/PlainText.cs +55 -0
  44. package/template/.claude/skills/logical-components/scripts/LogicalComponents/PortResolution.cs +75 -0
  45. package/template/.claude/skills/logical-components/scripts/LogicalComponents/Program.cs +27 -0
  46. package/template/.claude/skills/logical-components/scripts/LogicalComponents/ProjectFilters.cs +77 -0
  47. package/template/.claude/skills/logical-components/scripts/LogicalComponents/PruneCommand.cs +164 -0
  48. package/template/.claude/skills/logical-components/scripts/LogicalComponents/RenderCommand.cs +234 -0
  49. package/template/.claude/skills/logical-components/scripts/LogicalComponents/ResponsibilityBatches.cs +84 -0
  50. package/template/.claude/skills/logical-components/scripts/LogicalComponents/ResponsibilityValidation.cs +197 -0
  51. package/template/.claude/skills/logical-components/scripts/LogicalComponents/SeamDispatch.cs +115 -0
  52. package/template/.claude/skills/logical-components/scripts/LogicalComponents/SourceCodeLines.cs +79 -0
  53. package/template/.claude/skills/logical-components/scripts/LogicalComponents/SymbolNames.cs +53 -0
  54. package/template/.claude/skills/logical-components/scripts/LogicalComponents/TypeMap.cs +106 -0
  55. package/template/.claude/skills/logical-components/scripts/LogicalComponents/WalkRoots.cs +63 -0
  56. package/template/.claude/skills/logical-components/scripts/LogicalComponents/WorkspaceLoader.cs +297 -0
  57. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/AssignIdsTests.cs +41 -0
  58. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/BatchPlanningTests.cs +90 -0
  59. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/BatchesCommandTests.cs +37 -0
  60. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/CandidatesCommandTests.cs +59 -0
  61. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/CitationTests.cs +42 -0
  62. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/ClassifyCommandsTests.cs +249 -0
  63. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/ComponentDiscoveryTests.cs +253 -0
  64. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/CouplingAppExtractionTests.cs +188 -0
  65. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/CouplingAppWorkspace.cs +30 -0
  66. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/CouplingExtractionTests.cs +174 -0
  67. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/DisplayNameTests.cs +17 -0
  68. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/ExtractConfigTests.cs +72 -0
  69. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/FixturePaths.cs +36 -0
  70. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/LogicalComponents.Tests.csproj +23 -0
  71. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/MarkdownRendererTests.cs +93 -0
  72. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/ProjectFiltersTests.cs +69 -0
  73. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/PruneCommandTests.cs +149 -0
  74. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/RenderCommandTests.cs +225 -0
  75. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/RenderGoldenTests.cs +34 -0
  76. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/RenderTestData.cs +55 -0
  77. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/ResponsibilityValidationTests.cs +315 -0
  78. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/SampleAppWorkspace.cs +27 -0
  79. package/template/.claude/skills/logical-components/scripts/LogicalComponents.Tests/WorkspaceLoaderTests.cs +266 -0
  80. package/template/.claude/skills/logical-components/scripts/NuGet.config +17 -0
  81. package/template/.claude/skills/logical-components/scripts/fixtures/BrokenApp/src/Broken.Application/Broken.Application.csproj +7 -0
  82. package/template/.claude/skills/logical-components/scripts/fixtures/BrokenApp/src/Broken.Application/Broken.cs +7 -0
  83. package/template/.claude/skills/logical-components/scripts/fixtures/CompositionRootApp/CompositionRootApp.slnx +5 -0
  84. package/template/.claude/skills/logical-components/scripts/fixtures/CompositionRootApp/src/Root.Application/HostBuilderExtensions.cs +15 -0
  85. package/template/.claude/skills/logical-components/scripts/fixtures/CompositionRootApp/src/Root.Application/HostBuilderShims.cs +25 -0
  86. package/template/.claude/skills/logical-components/scripts/fixtures/CompositionRootApp/src/Root.Application/NativeInterop.cs +17 -0
  87. package/template/.claude/skills/logical-components/scripts/fixtures/CompositionRootApp/src/Root.Application/Root.Application.csproj +7 -0
  88. package/template/.claude/skills/logical-components/scripts/fixtures/CompositionRootApp/src/Root.Application/RootAutofacModule.cs +11 -0
  89. package/template/.claude/skills/logical-components/scripts/fixtures/CompositionRootApp/src/Root.Application/RootGreeter.cs +7 -0
  90. package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/logical-components.json +48 -0
  91. package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/src/CouplingApp.Application/AbstractMemberCases.cs +27 -0
  92. package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/src/CouplingApp.Application/AttributeCases.cs +23 -0
  93. package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/src/CouplingApp.Application/CouplingApp.Application.csproj +7 -0
  94. package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/src/CouplingApp.Application/GenericBaseCases.cs +14 -0
  95. package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/src/CouplingApp.Application/GenericSeamBindingCases.cs +59 -0
  96. package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/src/CouplingApp.Application/ImplicitCallCases.cs +230 -0
  97. package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/src/CouplingApp.Application/PartialInheritCases.First.cs +11 -0
  98. package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/src/CouplingApp.Application/PartialInheritCases.Second.cs +14 -0
  99. package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/src/CouplingApp.Application/StackOverflowCases.cs +14 -0
  100. package/template/.claude/skills/logical-components/scripts/fixtures/CouplingApp/src/CouplingApp.Application/ViaDisambiguationCases.cs +36 -0
  101. package/template/.claude/skills/logical-components/scripts/fixtures/MultiTargetApp/MultiTargetApp.slnx +7 -0
  102. package/template/.claude/skills/logical-components/scripts/fixtures/MultiTargetApp/src/Legacy.FSharp/Legacy.FSharp.fsproj +8 -0
  103. package/template/.claude/skills/logical-components/scripts/fixtures/MultiTargetApp/src/Legacy.FSharp/Library.fs +5 -0
  104. package/template/.claude/skills/logical-components/scripts/fixtures/MultiTargetApp/src/Multi.Application/Multi.Application.csproj +7 -0
  105. package/template/.claude/skills/logical-components/scripts/fixtures/MultiTargetApp/src/Multi.Application/Scheduling.cs +14 -0
  106. package/template/.claude/skills/logical-components/scripts/fixtures/MultiTargetApp/src/Multi.Infrastructure/Multi.Infrastructure.csproj +12 -0
  107. package/template/.claude/skills/logical-components/scripts/fixtures/MultiTargetApp/src/Multi.Infrastructure/SchedulingClient.cs +12 -0
  108. package/template/.claude/skills/logical-components/scripts/fixtures/NoSolutionApp/src/Solo.Application/Solo.Application.csproj +7 -0
  109. package/template/.claude/skills/logical-components/scripts/fixtures/NoSolutionApp/src/Solo.Application/SoloGreeting.cs +7 -0
  110. package/template/.claude/skills/logical-components/scripts/fixtures/NoSolutionApp/src/Solo.Application.Tests/FakeGreeting.cs +6 -0
  111. package/template/.claude/skills/logical-components/scripts/fixtures/NoSolutionApp/src/Solo.Application.Tests/Solo.Application.Tests.csproj +11 -0
  112. package/template/.claude/skills/logical-components/scripts/fixtures/ObjOutputApp/ObjOutputApp.slnx +5 -0
  113. package/template/.claude/skills/logical-components/scripts/fixtures/ObjOutputApp/src/Obj.Application/Greeting.cs +7 -0
  114. package/template/.claude/skills/logical-components/scripts/fixtures/ObjOutputApp/src/Obj.Application/Obj.Application.csproj +14 -0
  115. package/template/.claude/skills/logical-components/scripts/fixtures/RenderGolden/components.json +67 -0
  116. package/template/.claude/skills/logical-components/scripts/fixtures/RenderGolden/expected.md +28 -0
  117. package/template/.claude/skills/logical-components/scripts/fixtures/RenderGolden/repo/src/App/OrderAcceptance.cs +16 -0
  118. package/template/.claude/skills/logical-components/scripts/fixtures/RenderGolden/repo/src/Infra/OrderQueue.cs +10 -0
  119. package/template/.claude/skills/logical-components/scripts/fixtures/RenderGolden/resp/batch-01.json +18 -0
  120. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/SampleApp.slnx +10 -0
  121. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/logical-components.json +78 -0
  122. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/CandidateCases.cs +37 -0
  123. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/CouplingCases.cs +33 -0
  124. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/DependencyInjection.cs +18 -0
  125. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/EdgeCases.cs +63 -0
  126. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/LoudNotifying.Base.cs +5 -0
  127. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/LoudNotifying.cs +7 -0
  128. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/OrderAcceptance.cs +17 -0
  129. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/OrderOptions.cs +9 -0
  130. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/OrderRejectedException.cs +7 -0
  131. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/OrderRequest.cs +4 -0
  132. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/OrderRunning.cs +18 -0
  133. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/OrderValidation.cs +6 -0
  134. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/Ports.cs +19 -0
  135. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/PriceRules.cs +7 -0
  136. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/ReceiptPrinting.Totals.cs +6 -0
  137. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/ReceiptPrinting.cs +7 -0
  138. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/ReviewCases.cs +107 -0
  139. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/ReviewFixCases.cs +143 -0
  140. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/RunClock.cs +12 -0
  141. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/Sample.Application.csproj +10 -0
  142. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Application/SeeThroughCases.cs +89 -0
  143. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Billing.Application/OrderRunning.cs +7 -0
  144. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Billing.Application/Sample.Billing.Application.csproj +11 -0
  145. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/ForwardedNotifier.cs +8 -0
  146. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/InfrastructureDependencyInjection.cs +16 -0
  147. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/Mailer.cs +6 -0
  148. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/OrderQueue.cs +11 -0
  149. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/QueueEntry.cs +4 -0
  150. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/QueueStore.cs +8 -0
  151. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/ReviewCases.cs +35 -0
  152. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/ReviewFixCases.cs +13 -0
  153. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/Sample.Infrastructure.csproj +13 -0
  154. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/SeeThroughCases.cs +18 -0
  155. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/src/Sample.Infrastructure/UnreachedPlumbing.cs +7 -0
  156. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/tests/Sample.Tests/FakeOrderQueue.cs +11 -0
  157. package/template/.claude/skills/logical-components/scripts/fixtures/SampleApp/tests/Sample.Tests/Sample.Tests.csproj +11 -0
  158. package/template/.claude/skills/logical-components/scripts/fixtures/SharedOutsideRepo/SharedClock.cs +7 -0
  159. package/template/.claude/skills/logical-components/scripts/global.json +5 -0
  160. package/template/.claude/skills/logical-components/scripts/smoke-sample.sh +26 -0
@@ -0,0 +1,197 @@
1
+ using System.Text.RegularExpressions;
2
+
3
+ namespace LogicalComponents;
4
+
5
+ /// <summary>
6
+ /// Checks the agents' responsibilities against the deterministic extraction before anything is rendered. Agents may
7
+ /// only describe the components extract found; every component needs at least one responsibility; each is one plain
8
+ /// line of text, said once; and its citation must cover real code inside that component's own declaration. All problems
9
+ /// are reported at once, sorted by component id then item, so a retry can fix everything in one pass.
10
+ /// </summary>
11
+ public static partial class ResponsibilityValidation
12
+ {
13
+ public const int MaxCitedLines = 60;
14
+ public const int MaxTextLength = 300;
15
+
16
+ /// <param name="sourceLines">The lines of every file a component is declared in, keyed by repo-relative path.</param>
17
+ /// <param name="fileOf">Which batch file each responsibility id came from, to route unknown-id errors.</param>
18
+ public static List<string> Validate(
19
+ ExtractResult extraction,
20
+ IReadOnlyDictionary<string, IReadOnlyList<Responsibility>> responsibilities,
21
+ IReadOnlyDictionary<string, IReadOnlyList<string>> sourceLines,
22
+ IReadOnlyDictionary<string, string>? fileOf = null)
23
+ {
24
+ var errors = new List<(string Id, int Item, string Message)>();
25
+ var components = extraction.Components.ToDictionary(c => c.Id, StringComparer.Ordinal);
26
+ // Computed once per file and reused across every citation into it, since CiteProblem may be called many
27
+ // times for the same file (several components, or several responsibilities of one component).
28
+ var codeLines = new Dictionary<string, IReadOnlyList<bool>>(StringComparer.Ordinal);
29
+
30
+ foreach (var id in responsibilities.Keys.Where(id => !components.ContainsKey(id)))
31
+ {
32
+ errors.Add((id, 0, UnknownId(id, components.Keys, fileOf)));
33
+ }
34
+
35
+ foreach (var component in extraction.Components)
36
+ {
37
+ if (!responsibilities.TryGetValue(component.Id, out var items) || items.Count == 0)
38
+ {
39
+ errors.Add((component.Id, 0, $"{component.Id}: no responsibilities"));
40
+ continue;
41
+ }
42
+
43
+ var seen = new Dictionary<string, int>(StringComparer.Ordinal);
44
+ for (var index = 0; index < items.Count; index++)
45
+ {
46
+ var item = index + 1;
47
+ var prefix = $"{component.Id}[{item}]: ";
48
+ if (TextProblem(items[index].Text) is { } textProblem)
49
+ {
50
+ errors.Add((component.Id, item, prefix + textProblem));
51
+ }
52
+ else if (!seen.TryAdd(Normalize(items[index].Text), item))
53
+ {
54
+ errors.Add((component.Id, item, $"{prefix}repeats item {seen[Normalize(items[index].Text)]}"));
55
+ }
56
+
57
+ if (CiteProblem(items[index].Cite, component, sourceLines, codeLines) is { } citeProblem)
58
+ {
59
+ errors.Add((component.Id, item, prefix + citeProblem));
60
+ }
61
+ }
62
+ }
63
+
64
+ return errors
65
+ .OrderBy(e => e.Id, StringComparer.Ordinal)
66
+ .ThenBy(e => e.Item)
67
+ .Select(e => e.Message)
68
+ .ToList();
69
+ }
70
+
71
+ private static string UnknownId(string id, IEnumerable<string> componentIds, IReadOnlyDictionary<string, string>? fileOf)
72
+ {
73
+ var message = $"{id}: not a component in components.json";
74
+ if (fileOf?.TryGetValue(id, out var file) == true)
75
+ {
76
+ message += $" ({file})";
77
+ }
78
+
79
+ var match = componentIds.FirstOrDefault(c => string.Equals(c, id, StringComparison.OrdinalIgnoreCase));
80
+ return match is null ? message : $"{message}; did you mean '{match}'?";
81
+ }
82
+
83
+ /// <summary>
84
+ /// One plain markdown line: no hidden or control unicode (<see cref="PlainText"/>), balanced code spans
85
+ /// (<see cref="MarkdownCodeSpans"/> - CommonMark backtick-run matching, so an escaped backtick cannot smuggle real
86
+ /// HTML past the check by being mistaken for a delimiter), and outside those code spans no backslash, angle
87
+ /// bracket, square bracket or bare URL - anything that could render as something other than plain text, or point
88
+ /// the reader somewhere the tool never checked.
89
+ /// </summary>
90
+ private static string? TextProblem(string text)
91
+ {
92
+ if (string.IsNullOrWhiteSpace(text))
93
+ {
94
+ return "text is empty";
95
+ }
96
+
97
+ if (text.Contains('\n') || text.Contains('\r'))
98
+ {
99
+ return "text must be a single line";
100
+ }
101
+
102
+ if (PlainText.HasDisallowedUnicode(text))
103
+ {
104
+ return "text contains a control or invisible unicode character";
105
+ }
106
+
107
+ if (text.Trim().Length > MaxTextLength)
108
+ {
109
+ return $"text is {text.Trim().Length} characters; keep it under {MaxTextLength}";
110
+ }
111
+
112
+ var (outsideCode, unclosed) = MarkdownCodeSpans.Strip(text);
113
+ if (unclosed)
114
+ {
115
+ return "text has an unclosed ` code span";
116
+ }
117
+
118
+ if (outsideCode.Contains('\\'))
119
+ {
120
+ return "text contains a backslash; put code in `backticks`";
121
+ }
122
+
123
+ if (outsideCode.IndexOfAny(['<', '>']) >= 0)
124
+ {
125
+ return "text contains HTML; put code in `backticks`";
126
+ }
127
+
128
+ if (outsideCode.IndexOfAny(['[', ']']) >= 0)
129
+ {
130
+ return "text contains a link; put code in `backticks`";
131
+ }
132
+
133
+ return outsideCode.Contains("://", StringComparison.Ordinal)
134
+ || outsideCode.Contains("www.", StringComparison.OrdinalIgnoreCase)
135
+ ? "text contains a URL; put code in `backticks`"
136
+ : null;
137
+ }
138
+
139
+ private static string? CiteProblem(
140
+ string cite,
141
+ Component component,
142
+ IReadOnlyDictionary<string, IReadOnlyList<string>> sourceLines,
143
+ Dictionary<string, IReadOnlyList<bool>> codeLines)
144
+ {
145
+ if (Citation.Parse(cite) is not { } citation)
146
+ {
147
+ return $"cite '{cite}' is not <file>:<line> or <file>:<start>-<end>";
148
+ }
149
+
150
+ var citable = component.Spans.Concat(component.Reaches ?? []).ToList();
151
+ var spans = citable.Where(s => s.File == citation.File).ToList();
152
+ if (spans.Count == 0)
153
+ {
154
+ return $"cite '{cite}' is not in the component's files: {string.Join(", ", citable.Select(s => s.File).Distinct())}";
155
+ }
156
+
157
+ // The render command checks every span file exists before validating; a missing one here is a caller bug.
158
+ var lines = sourceLines[citation.File];
159
+ if (citation.End > lines.Count)
160
+ {
161
+ return $"cite '{cite}' is past the end of the file ({lines.Count} lines)";
162
+ }
163
+
164
+ if (!codeLines.TryGetValue(citation.File, out var code))
165
+ {
166
+ code = SourceCodeLines.Compute(lines);
167
+ codeLines[citation.File] = code;
168
+ }
169
+
170
+ // Every code line must be citable, and the first and last lines must lie inside a citable span. Lines between
171
+ // two citable members (blank, comment, brace) are not code, so a citation may run across neighbouring helper
172
+ // members without claiming anything uncited, but it cannot start or end outside them.
173
+ bool Citable(int line) => spans.Any(s => s.Start <= line && line <= s.End);
174
+ if (Enumerable.Range(citation.Start, citation.LineCount).Any(n => !Citable(n) && (code[n - 1] || n == citation.Start || n == citation.End)))
175
+ {
176
+ var where = component.Reaches is { Count: > 0 }
177
+ ? "the component's declaration and the helper code it runs"
178
+ : "the component's declaration";
179
+ return $"cite '{cite}' is outside {where} ({string.Join(", ", spans.Select(s => $"{s.Start}-{s.End}"))})";
180
+ }
181
+
182
+ if (citation.LineCount > MaxCitedLines)
183
+ {
184
+ return $"cite '{cite}' spans {citation.LineCount} lines; cite at most {MaxCitedLines}";
185
+ }
186
+
187
+ return Enumerable.Range(citation.Start, citation.LineCount).Any(n => code[n - 1])
188
+ ? null
189
+ : $"cite '{cite}' covers no code (only blank, comment or brace lines)";
190
+ }
191
+
192
+ // " Stores EACH order. " and "stores each order." are the same responsibility.
193
+ private static string Normalize(string text) => Whitespace().Replace(text.Trim(), " ").ToLowerInvariant();
194
+
195
+ [GeneratedRegex(@"\s+")]
196
+ private static partial Regex Whitespace();
197
+ }
@@ -0,0 +1,115 @@
1
+ using Microsoft.CodeAnalysis;
2
+
3
+ namespace LogicalComponents;
4
+
5
+ /// <summary>Which member runs in an implementation when a member of a seam (interface or abstract class) is used.</summary>
6
+ public static class SeamDispatch
7
+ {
8
+ /// <summary>
9
+ /// The member that runs in <paramref name="implementation"/> when <paramref name="member"/> of
10
+ /// <paramref name="seam"/> (as constructed at the use) is used: the implementation of that exact construction of the
11
+ /// interface member (a class implementing <c>IHandler&lt;Ping&gt;</c> and <c>IHandler&lt;Pong&gt;</c> runs its Pong
12
+ /// member for a Pong use), else the one of an open construction; for an abstract class, the most derived override,
13
+ /// possibly the seam's own body.
14
+ /// </summary>
15
+ public static ISymbol? ImplementationOf(INamedTypeSymbol implementation, INamedTypeSymbol seam, ISymbol member)
16
+ {
17
+ var wanted = SymbolNames.MemberId(member);
18
+ if (seam.TypeKind == TypeKind.Interface)
19
+ {
20
+ var definition = SymbolNames.FullName(seam.OriginalDefinition);
21
+ var constructions = implementation.AllInterfaces
22
+ .Where(i => SymbolNames.FullName(i.OriginalDefinition) == definition)
23
+ .ToList();
24
+ var exact = constructions.Where(i => Display(i) == Display(seam)).ToList();
25
+ return (exact.Count > 0 ? exact : constructions)
26
+ .SelectMany(i => i.GetMembers(member.Name))
27
+ .Where(candidate => SymbolNames.MemberId(candidate) == wanted)
28
+ .Select(implementation.FindImplementationForInterfaceMember)
29
+ .FirstOrDefault(found => found is not null);
30
+ }
31
+
32
+ for (var type = implementation; type is not null; type = type.BaseType)
33
+ {
34
+ foreach (var candidate in type.GetMembers(member.Name))
35
+ {
36
+ for (var overridden = candidate; overridden is not null; overridden = Overridden(overridden))
37
+ {
38
+ if (SymbolNames.MemberId(overridden) == wanted)
39
+ {
40
+ return candidate;
41
+ }
42
+ }
43
+ }
44
+ }
45
+
46
+ return null;
47
+ }
48
+
49
+ private static ISymbol? Overridden(ISymbol symbol) => symbol switch
50
+ {
51
+ IMethodSymbol method => method.OverriddenMethod,
52
+ IPropertySymbol property => property.OverriddenProperty,
53
+ IEventSymbol @event => @event.OverriddenEvent,
54
+ _ => null,
55
+ };
56
+
57
+ /// <summary>
58
+ /// The bindings for walking <paramref name="runs"/> (the member <see cref="ImplementationOf"/> found) as it executes
59
+ /// for <paramref name="member"/> of <paramref name="seam"/> (as constructed at the use) in
60
+ /// <paramref name="implementation"/>: <paramref name="implementation"/>'s own type parameters, unified from its
61
+ /// construction of the seam (an open-generic decorator's own type argument, e.g. <c>Decorator&lt;T&gt; : IHandler&lt;T&gt;</c>
62
+ /// against <c>IHandler&lt;Ping&gt;</c>), plus a generic method declared on the seam (e.g. <c>Send&lt;T&gt;</c>) bound to
63
+ /// the type argument the use supplied. <paramref name="outer"/> resolves a use's type argument that is itself still
64
+ /// an unbound type parameter of an enclosing walk. Without this, <see cref="MemberUseResolution"/> would walk
65
+ /// <paramref name="runs"/> with its own type parameters unbound, and a seam it calls in turn would look like it
66
+ /// matches every construction (<see cref="PortResolution"/>).
67
+ /// </summary>
68
+ public static TypeMap BindingFor(
69
+ INamedTypeSymbol implementation, INamedTypeSymbol seam, ISymbol member, ISymbol runs, TypeMap outer)
70
+ {
71
+ var bindings = new List<(ITypeParameterSymbol Parameter, ITypeSymbol Argument)>();
72
+
73
+ if (seam.IsGenericType)
74
+ {
75
+ var definition = SymbolNames.FullName(seam.OriginalDefinition);
76
+ var construction = Supertypes(implementation, seam.TypeKind)
77
+ .FirstOrDefault(s => SymbolNames.FullName(s.OriginalDefinition) == definition);
78
+ for (var i = 0; construction is not null && i < construction.TypeArguments.Length && i < seam.TypeArguments.Length; i++)
79
+ {
80
+ if (construction.TypeArguments[i] is ITypeParameterSymbol parameter
81
+ && SymbolEqualityComparer.Default.Equals(parameter.ContainingSymbol, implementation))
82
+ {
83
+ bindings.Add((parameter, seam.TypeArguments[i]));
84
+ }
85
+ }
86
+ }
87
+
88
+ if (member is IMethodSymbol { TypeArguments.Length: > 0 } used && runs is IMethodSymbol { TypeArguments.Length: > 0 } implemented)
89
+ {
90
+ bindings.AddRange(TypeMap.Zip(implemented.OriginalDefinition.TypeParameters, used.TypeArguments)
91
+ .Select(b => (b.Parameter, outer.Substitute(b.Argument))));
92
+ }
93
+
94
+ return TypeMap.FromBindings(bindings);
95
+ }
96
+
97
+ private static IEnumerable<INamedTypeSymbol> Supertypes(INamedTypeSymbol type, TypeKind seamKind)
98
+ {
99
+ if (seamKind == TypeKind.Interface)
100
+ {
101
+ return type.AllInterfaces;
102
+ }
103
+
104
+ var bases = new List<INamedTypeSymbol>();
105
+ for (var current = type.BaseType; current is not null; current = current.BaseType)
106
+ {
107
+ bases.Add(current);
108
+ }
109
+
110
+ return bases;
111
+ }
112
+
113
+ // Compared as text: symbols of the same type seen from two projects' compilations are not the same instance.
114
+ private static string Display(ITypeSymbol type) => type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
115
+ }
@@ -0,0 +1,79 @@
1
+ using System.Text;
2
+
3
+ namespace LogicalComponents;
4
+
5
+ /// <summary>
6
+ /// Which lines of a whole file contain real code, for <see cref="ResponsibilityValidation"/>'s citation checks. Tracks
7
+ /// `/* */` block-comment state across the file (so a continuation line without a leading `*` is still recognised as a
8
+ /// comment, and a comment that closes mid-line is not mistaken for a comment-only line), and treats punctuation-only
9
+ /// lines, preprocessor directives, and a lone `else`/`try`/`finally`/`do` with no condition of its own as not code.
10
+ /// </summary>
11
+ public static class SourceCodeLines
12
+ {
13
+ public static IReadOnlyList<bool> Compute(IReadOnlyList<string> lines)
14
+ {
15
+ var result = new bool[lines.Count];
16
+ var inBlockComment = false;
17
+ for (var i = 0; i < lines.Count; i++)
18
+ {
19
+ result[i] = LineHasCode(lines[i], ref inBlockComment);
20
+ }
21
+
22
+ return result;
23
+ }
24
+
25
+ /// <summary>
26
+ /// Strips `//` line comments and `/* */` block comments from one line (updating <paramref name="inBlockComment"/>
27
+ /// for the next line), then true if what remains is more than punctuation <c>{}()[];,</c>, a preprocessor
28
+ /// directive, or a lone <c>else</c>/<c>try</c>/<c>finally</c>/<c>do</c>.
29
+ /// </summary>
30
+ private static bool LineHasCode(string line, ref bool inBlockComment)
31
+ {
32
+ var code = new StringBuilder();
33
+ var i = 0;
34
+ while (i < line.Length)
35
+ {
36
+ if (inBlockComment)
37
+ {
38
+ var close = line.IndexOf("*/", i, StringComparison.Ordinal);
39
+ if (close < 0)
40
+ {
41
+ break; // the rest of the line is still inside the comment
42
+ }
43
+
44
+ inBlockComment = false;
45
+ i = close + 2;
46
+ continue;
47
+ }
48
+
49
+ if (line[i] == '/' && i + 1 < line.Length && line[i + 1] == '/')
50
+ {
51
+ break; // the rest of the line is a line comment
52
+ }
53
+
54
+ if (line[i] == '/' && i + 1 < line.Length && line[i + 1] == '*')
55
+ {
56
+ var close = line.IndexOf("*/", i + 2, StringComparison.Ordinal);
57
+ if (close < 0)
58
+ {
59
+ inBlockComment = true;
60
+ break;
61
+ }
62
+
63
+ i = close + 2;
64
+ continue;
65
+ }
66
+
67
+ code.Append(line[i]);
68
+ i++;
69
+ }
70
+
71
+ if (code.ToString().TrimStart(' ', '\t').StartsWith('#'))
72
+ {
73
+ return false;
74
+ }
75
+
76
+ var stripped = code.ToString().Trim('{', '}', '(', ')', '[', ']', ';', ',', ' ', '\t');
77
+ return stripped.Length > 0 && stripped is not ("else" or "try" or "finally" or "do");
78
+ }
79
+ }
@@ -0,0 +1,53 @@
1
+ using Microsoft.CodeAnalysis;
2
+
3
+ namespace LogicalComponents;
4
+
5
+ /// <summary>Stable textual names for symbols, independent of nullable annotations and of where the repo is checked out.</summary>
6
+ public static class SymbolNames
7
+ {
8
+ // No miscellaneous options: in particular no nullable '?' suffix, so IServiceCollection? == IServiceCollection.
9
+ private static readonly SymbolDisplayFormat Qualified = new(
10
+ typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces);
11
+
12
+ /// <summary><c>Microsoft.Extensions.DependencyInjection.IServiceCollection</c>, whatever its nullability.</summary>
13
+ public static string QualifiedName(ITypeSymbol type) => type.ToDisplayString(Qualified);
14
+
15
+ /// <summary>
16
+ /// Full metadata name: namespace plus metadata name (generic arity kept, e.g. <c>Ns.Cache`1</c>). A nested type
17
+ /// carries its containing types (<c>Ns.Receipts+Builder</c>), so it can never collide with a top-level namesake.
18
+ /// </summary>
19
+ public static string FullName(INamedTypeSymbol type) =>
20
+ type.ContainingType is { } outer
21
+ ? $"{FullName(outer)}+{type.MetadataName}"
22
+ : type.ContainingNamespace.IsGlobalNamespace
23
+ ? type.MetadataName
24
+ : $"{type.ContainingNamespace.ToDisplayString()}.{type.MetadataName}";
25
+
26
+ /// <summary>
27
+ /// A member's identity across compilations (symbols of one member seen from two projects are not the same instance):
28
+ /// the documentation-comment id of its definition, e.g. <c>M:Ns.Handler`1.Handle(`0)</c>.
29
+ /// </summary>
30
+ public static string MemberId(ISymbol symbol) =>
31
+ symbol.OriginalDefinition.GetDocumentationCommentId() ?? symbol.OriginalDefinition.ToDisplayString();
32
+
33
+ /// <summary>A name safe as a diagram id: <c>Ns.Receipts+Cache`1</c> → <c>Ns_Receipts_Cache_1</c>.</summary>
34
+ public static string ToIdentifier(string fullName) =>
35
+ fullName.Replace('.', '_').Replace('+', '_').Replace('`', '_');
36
+
37
+ /// <summary>The simple name without arity: <c>Ns.Cache`1</c> → <c>Cache</c>, <c>Ns.Outer+Inner</c> → <c>Inner</c>.</summary>
38
+ public static string SimpleName(string fullName)
39
+ {
40
+ var name = fullName[(fullName.LastIndexOfAny(['.', '+']) + 1)..];
41
+ var tick = name.IndexOf('`');
42
+ return tick < 0 ? name : name[..tick];
43
+ }
44
+
45
+ /// <summary>The full name as a reader writes it: <c>Ns.Cache`1</c> → <c>Ns.Cache&lt;&gt;</c>, <c>`2</c> → <c>&lt;,&gt;</c>.</summary>
46
+ public static string ReadableFullName(string fullName)
47
+ {
48
+ var tick = fullName.LastIndexOf('`');
49
+ return tick < 0 || !int.TryParse(fullName[(tick + 1)..], out var arity)
50
+ ? fullName
51
+ : $"{fullName[..tick]}<{new string(',', arity - 1)}>";
52
+ }
53
+ }
@@ -0,0 +1,106 @@
1
+ using System.Collections.Immutable;
2
+ using Microsoft.CodeAnalysis;
3
+
4
+ namespace LogicalComponents;
5
+
6
+ /// <summary>
7
+ /// What a generic helper's type parameters stand for at the use being followed. Walking <c>Dispatch.Send&lt;T&gt;</c>
8
+ /// for <c>Send(new Ping())</c> maps <c>T</c> to <c>Ping</c>, so the <c>ICommandHandler&lt;T&gt;</c> it calls is
9
+ /// resolved as <c>ICommandHandler&lt;Ping&gt;</c> and reaches only the Ping handlers, not every handler.
10
+ /// </summary>
11
+ public sealed class TypeMap
12
+ {
13
+ private readonly Dictionary<ITypeParameterSymbol, ITypeSymbol> _map;
14
+
15
+ private TypeMap(Dictionary<ITypeParameterSymbol, ITypeSymbol> map)
16
+ {
17
+ _map = map;
18
+ Key = string.Join(";", map
19
+ .Select(p => $"{p.Key.ContainingSymbol.ToDisplayString()}.{p.Key.Name}={p.Value.ToDisplayString()}")
20
+ .Order(StringComparer.Ordinal));
21
+ }
22
+
23
+ public static TypeMap Empty { get; } = new(new Dictionary<ITypeParameterSymbol, ITypeSymbol>(SymbolEqualityComparer.Default));
24
+
25
+ /// <summary>Stable text of the bindings: the same member walked for two different bindings is two walks.</summary>
26
+ public string Key { get; }
27
+
28
+ /// <summary>
29
+ /// A map built from known (parameter, argument) pairs: a generic base class's own parameters bound to what the
30
+ /// derived class supplies (<see cref="CouplingResolver"/>), or an implementation's own parameters unified from its
31
+ /// construction of a seam, plus a generic seam method's parameters bound to the use's type arguments
32
+ /// (<see cref="SeamDispatch"/>). Pairs where the argument is the parameter itself carry no information and are
33
+ /// dropped, same as <see cref="For"/>.
34
+ /// </summary>
35
+ public static TypeMap FromBindings(IEnumerable<(ITypeParameterSymbol Parameter, ITypeSymbol Argument)> bindings)
36
+ {
37
+ var map = new Dictionary<ITypeParameterSymbol, ITypeSymbol>(SymbolEqualityComparer.Default);
38
+ foreach (var (parameter, argument) in bindings)
39
+ {
40
+ if (!SymbolEqualityComparer.Default.Equals(parameter, argument))
41
+ {
42
+ map[parameter] = argument;
43
+ }
44
+ }
45
+
46
+ return map.Count == 0 ? Empty : new TypeMap(map);
47
+ }
48
+
49
+ /// <summary>Pairs a generic definition's own type parameters with a construction's type arguments, positionally.</summary>
50
+ public static IEnumerable<(ITypeParameterSymbol Parameter, ITypeSymbol Argument)> Zip(
51
+ ImmutableArray<ITypeParameterSymbol> parameters, ImmutableArray<ITypeSymbol> arguments)
52
+ {
53
+ for (var i = 0; i < parameters.Length && i < arguments.Length; i++)
54
+ {
55
+ yield return (parameters[i], arguments[i]);
56
+ }
57
+ }
58
+
59
+ /// <summary>The type with every mapped type parameter replaced, inside generic arguments too.</summary>
60
+ public ITypeSymbol Substitute(ITypeSymbol type) => type switch
61
+ {
62
+ ITypeParameterSymbol parameter when _map.TryGetValue(parameter, out var bound) => bound,
63
+ INamedTypeSymbol { IsGenericType: true } named when named.TypeArguments.Any(Mentions) =>
64
+ named.OriginalDefinition.Construct([.. named.TypeArguments.Select(Substitute)]),
65
+ _ => type,
66
+ };
67
+
68
+ /// <summary>
69
+ /// The map for walking <paramref name="member"/>'s own code: the type parameters of its definition and of its
70
+ /// containing types, bound to the arguments this use supplies (read through this map, for nested generic helpers).
71
+ /// </summary>
72
+ public TypeMap For(ISymbol member)
73
+ {
74
+ var map = new Dictionary<ITypeParameterSymbol, ITypeSymbol>(SymbolEqualityComparer.Default);
75
+ void Bind(ImmutableArray<ITypeParameterSymbol> parameters, ImmutableArray<ITypeSymbol> arguments)
76
+ {
77
+ for (var i = 0; i < parameters.Length && i < arguments.Length; i++)
78
+ {
79
+ var argument = Substitute(arguments[i]);
80
+ if (!SymbolEqualityComparer.Default.Equals(parameters[i], argument))
81
+ {
82
+ map[parameters[i]] = argument;
83
+ }
84
+ }
85
+ }
86
+
87
+ if (member is IMethodSymbol method)
88
+ {
89
+ Bind(method.OriginalDefinition.TypeParameters, method.TypeArguments);
90
+ }
91
+
92
+ for (var type = member.ContainingType; type is not null; type = type.ContainingType)
93
+ {
94
+ Bind(type.OriginalDefinition.TypeParameters, type.TypeArguments);
95
+ }
96
+
97
+ return map.Count == 0 ? Empty : new TypeMap(map);
98
+ }
99
+
100
+ private bool Mentions(ITypeSymbol type) => type switch
101
+ {
102
+ ITypeParameterSymbol parameter => _map.ContainsKey(parameter),
103
+ INamedTypeSymbol { IsGenericType: true } named => named.TypeArguments.Any(Mentions),
104
+ _ => false,
105
+ };
106
+ }
@@ -0,0 +1,63 @@
1
+ using Microsoft.CodeAnalysis;
2
+ using Microsoft.CodeAnalysis.CSharp.Syntax;
3
+
4
+ namespace LogicalComponents;
5
+
6
+ /// <summary>
7
+ /// The syntax that runs when a member is used, for <see cref="MemberUseResolution"/> to walk. Always in (file, position)
8
+ /// order, so the walk, and the first path it records, is the same on every checkout.
9
+ /// </summary>
10
+ public static class WalkRoots
11
+ {
12
+ /// <summary>The declaration of a member: its body, accessor or initializer, from the implementing half if it is partial.</summary>
13
+ public static List<SyntaxNode> Body(ISymbol member) =>
14
+ Ordered(Implementation(member.OriginalDefinition).DeclaringSyntaxReferences.Select(r => r.GetSyntax()));
15
+
16
+ /// <summary>
17
+ /// What <c>new</c> runs: the constructor (for a primary constructor, only its parameter defaults and base call, never
18
+ /// the whole type) and the type's instance field and property initializers. <c>RunsImplicitBase</c> is set when the
19
+ /// constructor calls no <c>base(...)</c>/<c>this(...)</c> itself, so the base class's parameterless constructor runs.
20
+ /// </summary>
21
+ public static (List<SyntaxNode> Roots, bool RunsImplicitBase) Construction(IMethodSymbol constructor)
22
+ {
23
+ var roots = new List<SyntaxNode>();
24
+ var explicitChain = false;
25
+ foreach (var syntax in Implementation(constructor.OriginalDefinition).DeclaringSyntaxReferences.Select(r => r.GetSyntax()))
26
+ {
27
+ if (syntax is TypeDeclarationSyntax type)
28
+ {
29
+ var baseCall = type.BaseList?.Types.OfType<PrimaryConstructorBaseTypeSyntax>().FirstOrDefault();
30
+ explicitChain |= baseCall is not null;
31
+ roots.AddRange(new SyntaxNode?[] { type.ParameterList, baseCall }.OfType<SyntaxNode>());
32
+ }
33
+ else
34
+ {
35
+ explicitChain |= syntax is ConstructorDeclarationSyntax { Initializer: not null };
36
+ roots.Add(syntax);
37
+ }
38
+ }
39
+
40
+ foreach (var member in constructor.ContainingType.OriginalDefinition.GetMembers().Where(m => !m.IsStatic))
41
+ {
42
+ roots.AddRange(member.DeclaringSyntaxReferences.Select(r => r.GetSyntax()).Select(syntax => syntax switch
43
+ {
44
+ VariableDeclaratorSyntax { Initializer: { } initializer } => initializer,
45
+ PropertyDeclarationSyntax { Initializer: { } initializer } => initializer,
46
+ _ => null,
47
+ }).OfType<SyntaxNode>());
48
+ }
49
+
50
+ return (Ordered(roots), !explicitChain);
51
+ }
52
+
53
+ // A partial method, property or constructor is walked where its body is written.
54
+ private static ISymbol Implementation(ISymbol member) => member switch
55
+ {
56
+ IMethodSymbol { PartialImplementationPart: { } implementation } => implementation,
57
+ IPropertySymbol { PartialImplementationPart: { } implementation } => implementation,
58
+ _ => member,
59
+ };
60
+
61
+ private static List<SyntaxNode> Ordered(IEnumerable<SyntaxNode> roots) =>
62
+ roots.OrderBy(r => r.SyntaxTree.FilePath, StringComparer.Ordinal).ThenBy(r => r.SpanStart).ToList();
63
+ }