Refactor Repl.Core: organize files, extract CoreReplApp engines, fix new analyzers#19
Merged
carldebilly merged 12 commits intomainfrom Apr 7, 2026
Merged
Conversation
Reorganize 52 internal implementation files from the root of Repl.Core into six thematic subdirectories: Routing/ (15), Parsing/ (14), Output/ (7), Console/ (9), Help/ (3), Session/ (4). All files retain namespace Repl so there is no compilation impact. Root file count drops from 122 to 75.
Split four oversized files into partial classes: - CoreReplApp.Interactive.cs (1859) -> Interactive (628) + Autocomplete (1237) - CoreReplApp.cs (1576) -> CoreReplApp (794) + Execution (806) - ConsoleLineReader.cs (1425) -> ConsoleLineReader (598) + Autocomplete (833) - HelpTextBuilder.cs (896) -> HelpTextBuilder (286) + Rendering (617) Remove redundant Public/ subdirectories by moving their contents into parent directories: Autocomplete, Interaction, Rendering, ShellCompletion, and Terminal.
This type is only constructed internally by AmbientCommandOptions.MapAmbient and stored in an internal dictionary. No external consumer needs direct access.
Relocate internal and public types from the Repl.Core root into their logical subdirectories: Rendering (4), Output (1), Parsing (4), Interaction (1), Console (4), Autocomplete (2), Parameters (3), Documentation (1), Terminal (2), Session (2), ShellCompletion (1). Root file count drops from 77 to 52.
Reorder CancellationToken to last position in Ask*Options records (CA1068 breaking change). Suppress MA0045 for intentionally synchronous console I/O code (Console.ReadKey/KeyAvailable have no async alternatives). Use pattern matching instead of HasValue (MA0171). Relax MA0045 and MA0167 in test/sample projects via .editorconfig.
Promote private nested types (ActiveRoutingGraph, PrefixTemplate, ContextValidationOutcome, AmbientCommandOutcome) to standalone internal files. Promote key methods from private to internal for cross-class access. Merge OptionParsing partial (62 lines) into Execution partial.
Move all documentation model generation logic into a standalone DocumentationEngine class. CoreReplApp.Documentation.cs becomes a thin delegation layer. No behavioral changes.
Move shell completion candidate resolution into a standalone ShellCompletionEngine class. Promote shared utility methods (IsGlobalOptionToken, TokenizeInputSpans, CollectVisibleMatchingRoutes) to internal visibility for cross-class access.
Move the entire autocomplete suggestion engine (1237 lines) into a standalone AutocompleteEngine class including token classification, candidate collection, live hints, and input analysis. Move shared utilities (IsGlobalOptionToken, TokenizeInputSpans, TokenSpan) into AutocompleteEngine as internal members.
Move context registration/validation, route resolution helpers, banner rendering, discoverable route/context filtering, prefix resolution, and error generation into a standalone RoutingEngine class. CoreReplApp Routing partial becomes a thin delegation layer.
Move interactive REPL session management, ambient command dispatch, history handling, prompt building, and input tokenization into a standalone InteractiveSession class. Execution calls Interactive through the app facade, breaking the bidirectional partial coupling.
Comment on lines
+505
to
+516
| foreach (var route in matchingRoutes) | ||
| { | ||
| if (commandPrefix.Length < route.Template.Segments.Count | ||
| && route.Template.Segments[commandPrefix.Length] is LiteralRouteSegment literal | ||
| && literal.Value.StartsWith(currentTokenPrefix, prefixComparison)) | ||
| { | ||
| candidates.Add(new ConsoleLineReader.AutocompleteSuggestion( | ||
| literal.Value, | ||
| Description: route.Command.Description, | ||
| Kind: ConsoleLineReader.AutocompleteSuggestionKind.Command)); | ||
| } | ||
| } |
Member
Author
There was a problem hiding this comment.
Acknowledged — keeping the existing foreach+if pattern here for consistency and readability. The LINQ Where alternative adds allocations without meaningful clarity gain in these hot paths.
Comment on lines
+227
to
+233
| foreach (var option in StaticShellGlobalOptions) | ||
| { | ||
| if (option.StartsWith(currentTokenPrefix, comparison)) | ||
| { | ||
| TryAddShellCompletionCandidate(option, dedupe, candidates); | ||
| } | ||
| } |
Member
Author
There was a problem hiding this comment.
Acknowledged — keeping the existing foreach+if pattern here for consistency and readability. The LINQ Where alternative adds allocations without meaningful clarity gain in these hot paths.
Comment on lines
+260
to
+266
| foreach (var alias in custom.Aliases) | ||
| { | ||
| if (alias.StartsWith(currentTokenPrefix, comparison)) | ||
| { | ||
| TryAddShellCompletionCandidate(alias, dedupe, candidates); | ||
| } | ||
| } |
Member
Author
There was a problem hiding this comment.
Acknowledged — keeping the existing foreach+if pattern here for consistency and readability. The LINQ Where alternative adds allocations without meaningful clarity gain in these hot paths.
- Fix bug: AddRouteShellOptionCandidates now reads OptionCaseSensitivity instead of hardcoding OrdinalIgnoreCase - Remove dead store (quote = null) in AutocompleteEngine tokenizer - Remove redundant ToString() calls on StringBuilder in ConsoleLineReader - Fix misaligned pragma indentation in InvocationOptionParser - Fix pragma restore splitting method call in RichPromptInteractionHandler
Comment on lines
+279
to
+285
| foreach (var token in route.OptionSchema.KnownTokens) | ||
| { | ||
| if (token.StartsWith(currentTokenPrefix, comparison)) | ||
| { | ||
| TryAddShellCompletionCandidate(token, dedupe, candidates); | ||
| } | ||
| } |
Comment on lines
+312
to
+318
| foreach (var token in tokens) | ||
| { | ||
| if (token.End <= cursor) | ||
| { | ||
| trailingPriorCount++; | ||
| } | ||
| } |
Comment on lines
+327
to
+333
| foreach (var token in tokens) | ||
| { | ||
| if (token.End <= cursor) | ||
| { | ||
| trailingPrior[index++] = token.Value; | ||
| } | ||
| } |
Comment on lines
+378
to
+385
| foreach (var extension in KnownExecutableExtensions) | ||
| { | ||
| if (fileName.EndsWith(extension, StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| var head = fileName[..^extension.Length]; | ||
| return string.IsNullOrWhiteSpace(head) ? null : head; | ||
| } | ||
| } |
Comment on lines
+555
to
+562
| foreach (var contextMatch in contextMatches) | ||
| { | ||
| var prefixLength = contextMatch.Context.Template.Segments.Count; | ||
| if (prefixLength > longestPrefixLength) | ||
| { | ||
| longestPrefixLength = prefixLength; | ||
| } | ||
| } |
Comment on lines
+235
to
+242
| foreach (var alias in options.Output.Aliases.Keys) | ||
| { | ||
| var opt = $"--{alias}"; | ||
| if (opt.StartsWith(currentTokenPrefix, comparison)) | ||
| { | ||
| TryAddShellCompletionCandidate(opt, dedupe, candidates); | ||
| } | ||
| } |
Comment on lines
+244
to
+251
| foreach (var format in options.Output.Transformers.Keys) | ||
| { | ||
| var opt = $"--output:{format}"; | ||
| if (opt.StartsWith(currentTokenPrefix, comparison)) | ||
| { | ||
| TryAddShellCompletionCandidate(opt, dedupe, candidates); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Major structural refactoring of
Repl.Coreto reduce complexity and improve maintainability.Routing/,Parsing/,Console/,Help/,Session/,Output/,Rendering/,Autocomplete/,Parameters/,Documentation/,Terminal/,ShellCompletion/). Root file count reduced from 122 to ~45.CoreReplApp.Interactive.cs,ConsoleLineReader.cs,HelpTextBuilder.cssplit into focused partials. Flatten unnecessaryPublic/subdirectories.CoreReplAppmonolith into 5 standalone internal classes —DocumentationEngine,ShellCompletionEngine,AutocompleteEngine,RoutingEngine,InteractiveSession. CoreReplApp remains as facade withExecutionas the only remaining substantial partial.AmbientCommandDefinitionmadeinternal..editorconfig.Breaking changes
AskOptions,AskSecretOptions,AskNumberOptions<T>,AskMultiChoiceOptions:CancellationTokenmoved from first to last positional parameter (CA1068). Code using positional construction (new AskOptions(ct)) must switch to named parameters (new AskOptions(CancellationToken: ct)).AmbientCommandDefinition: changed frompublictointernal.Test plan
dotnet build src/— 0 errorsRepl.Tests— 279/279 passedRepl.IntegrationTests— 383/383 passedRepl.McpTests— 113/113 passed