diff --git a/src/System.CommandLine/ArgumentArity.cs b/src/System.CommandLine/ArgumentArity.cs index c0153fc86e..6e3d496af5 100644 --- a/src/System.CommandLine/ArgumentArity.cs +++ b/src/System.CommandLine/ArgumentArity.cs @@ -81,7 +81,7 @@ public override int GetHashCode() var argumentResult = symbolResult switch { ArgumentResult a => a, - _ => symbolResult.Root!.FindResultFor(argument) + _ => symbolResult.FindResultFor(argument) }; var tokenCount = argumentResult?.Tokens.Count ?? 0; diff --git a/src/System.CommandLine/ParseResult.cs b/src/System.CommandLine/ParseResult.cs index b4573b78b0..b34f2ffb22 100644 --- a/src/System.CommandLine/ParseResult.cs +++ b/src/System.CommandLine/ParseResult.cs @@ -224,26 +224,30 @@ currentSymbol is not null ? currentSymbol.GetCompletions(context) : Array.Empty(); + string[] optionsWithArgumentLimitReached = currentSymbolResult is CommandResult commandResult + ? OptionsWithArgumentLimitReached(commandResult) + : Array.Empty(); + completions = - completions.Where(item => OptionsWithArgumentLimitReached(currentSymbolResult).All(s => s != item.Label)); + completions.Where(item => optionsWithArgumentLimitReached.All(s => s != item.Label)); return completions; - static IEnumerable OptionsWithArgumentLimitReached(SymbolResult symbolResult) => - symbolResult + static string[] OptionsWithArgumentLimitReached(CommandResult commandResult) => + commandResult .Children .Where(c => c.IsArgumentLimitReached) .OfType() - .Select(o => o.Symbol) - .OfType() - .SelectMany(c => c.Aliases); + .Select(o => o.Option) + .SelectMany(c => c.Aliases) + .ToArray(); } private SymbolResult SymbolToComplete(int? position = null) { var commandResult = CommandResult; - var allSymbolResultsForCompletion = AllSymbolResultsForCompletion().ToArray(); + var allSymbolResultsForCompletion = AllSymbolResultsForCompletion(); var currentSymbol = allSymbolResultsForCompletion.Last(); diff --git a/src/System.CommandLine/Parsing/ArgumentResult.cs b/src/System.CommandLine/Parsing/ArgumentResult.cs index 50b975acc1..0b8450eaf7 100644 --- a/src/System.CommandLine/Parsing/ArgumentResult.cs +++ b/src/System.CommandLine/Parsing/ArgumentResult.cs @@ -10,7 +10,7 @@ namespace System.CommandLine.Parsing /// /// A result produced when parsing an . /// - public class ArgumentResult : SymbolResult + public sealed class ArgumentResult : SymbolResult { private ArgumentConversionResult? _conversionResult; diff --git a/src/System.CommandLine/Parsing/CommandArgumentNode.cs b/src/System.CommandLine/Parsing/CommandArgumentNode.cs index d9d776a1fe..7c21746ff5 100644 --- a/src/System.CommandLine/Parsing/CommandArgumentNode.cs +++ b/src/System.CommandLine/Parsing/CommandArgumentNode.cs @@ -5,12 +5,12 @@ namespace System.CommandLine.Parsing { - internal class CommandArgumentNode : SyntaxNode + internal sealed class CommandArgumentNode : SyntaxNode { public CommandArgumentNode( Token token, Argument argument, - CommandNode parent) : base(token, parent) + CommandNode parent) : base(token) { Debug.Assert(token.Type == TokenType.Argument, $"Incorrect token type: {token}"); diff --git a/src/System.CommandLine/Parsing/CommandNode.cs b/src/System.CommandLine/Parsing/CommandNode.cs index b62875b22c..63eeb9b589 100644 --- a/src/System.CommandLine/Parsing/CommandNode.cs +++ b/src/System.CommandLine/Parsing/CommandNode.cs @@ -3,12 +3,11 @@ namespace System.CommandLine.Parsing { - internal class CommandNode : NonterminalSyntaxNode + internal sealed class CommandNode : NonterminalSyntaxNode { public CommandNode( Token token, - Command command, - CommandNode? parent) : base(token, parent) + Command command) : base(token) { Command = command; } diff --git a/src/System.CommandLine/Parsing/DirectiveNode.cs b/src/System.CommandLine/Parsing/DirectiveNode.cs index e4bebe1897..c747212378 100644 --- a/src/System.CommandLine/Parsing/DirectiveNode.cs +++ b/src/System.CommandLine/Parsing/DirectiveNode.cs @@ -5,13 +5,12 @@ namespace System.CommandLine.Parsing { - internal class DirectiveNode : SyntaxNode + internal sealed class DirectiveNode : SyntaxNode { public DirectiveNode( Token token, - CommandNode parent, string name, - string? value) : base(token, parent) + string? value) : base(token) { Debug.Assert(token.Type == TokenType.Directive, $"Incorrect token type: {token}"); diff --git a/src/System.CommandLine/Parsing/NonterminalSyntaxNode.cs b/src/System.CommandLine/Parsing/NonterminalSyntaxNode.cs index 175f892232..dcf9dd833d 100644 --- a/src/System.CommandLine/Parsing/NonterminalSyntaxNode.cs +++ b/src/System.CommandLine/Parsing/NonterminalSyntaxNode.cs @@ -9,11 +9,11 @@ internal abstract class NonterminalSyntaxNode : SyntaxNode { private List? _children; - protected NonterminalSyntaxNode(Token token, SyntaxNode? parent) : base(token, parent) + protected NonterminalSyntaxNode(Token token) : base(token) { } - public IReadOnlyList Children => _children is not null ? _children : Array.Empty(); + public IReadOnlyList? Children => _children; internal void AddChildNode(SyntaxNode node) => (_children ??= new()).Add(node); } diff --git a/src/System.CommandLine/Parsing/OptionArgumentNode.cs b/src/System.CommandLine/Parsing/OptionArgumentNode.cs index 36cd51006d..f9452cd514 100644 --- a/src/System.CommandLine/Parsing/OptionArgumentNode.cs +++ b/src/System.CommandLine/Parsing/OptionArgumentNode.cs @@ -5,12 +5,12 @@ namespace System.CommandLine.Parsing { - internal class OptionArgumentNode : SyntaxNode + internal sealed class OptionArgumentNode : SyntaxNode { public OptionArgumentNode( Token token, Argument argument, - OptionNode parent) : base(token, parent) + OptionNode parent) : base(token) { Debug.Assert(token.Type == TokenType.Argument, $"Incorrect token type: {token}"); diff --git a/src/System.CommandLine/Parsing/OptionNode.cs b/src/System.CommandLine/Parsing/OptionNode.cs index bd4f30e800..25ca29023b 100644 --- a/src/System.CommandLine/Parsing/OptionNode.cs +++ b/src/System.CommandLine/Parsing/OptionNode.cs @@ -3,12 +3,11 @@ namespace System.CommandLine.Parsing { - internal class OptionNode : NonterminalSyntaxNode + internal sealed class OptionNode : NonterminalSyntaxNode { public OptionNode( Token token, - Option option, - CommandNode parent) : base(token, parent) + Option option) : base(token) { Option = option; } diff --git a/src/System.CommandLine/Parsing/OptionResult.cs b/src/System.CommandLine/Parsing/OptionResult.cs index 934f77dcfb..38419b1b0c 100644 --- a/src/System.CommandLine/Parsing/OptionResult.cs +++ b/src/System.CommandLine/Parsing/OptionResult.cs @@ -10,7 +10,7 @@ namespace System.CommandLine.Parsing /// /// A result produced when parsing an . /// - public class OptionResult : SymbolResult + public sealed class OptionResult : SymbolResult { private ArgumentConversionResult? _argumentConversionResult; private Dictionary? _defaultArgumentValues; diff --git a/src/System.CommandLine/Parsing/ParseOperation.cs b/src/System.CommandLine/Parsing/ParseOperation.cs index aa36d10f9c..34997725bb 100644 --- a/src/System.CommandLine/Parsing/ParseOperation.cs +++ b/src/System.CommandLine/Parsing/ParseOperation.cs @@ -5,7 +5,7 @@ namespace System.CommandLine.Parsing { - internal class ParseOperation + internal sealed class ParseOperation { private readonly List _tokens; private readonly CommandLineConfiguration _configuration; @@ -43,8 +43,7 @@ private CommandNode ParseRootCommand() { var rootCommandNode = new CommandNode( CurrentToken, - _configuration.RootCommand, - null); + _configuration.RootCommand); Advance(); @@ -57,7 +56,7 @@ private CommandNode ParseRootCommand() private void ParseSubcommand(CommandNode parentNode) { - var commandNode = new CommandNode(CurrentToken, (Command)CurrentToken.Symbol!, parentNode); + var commandNode = new CommandNode(CurrentToken, (Command)CurrentToken.Symbol!); Advance(); @@ -135,8 +134,7 @@ private void ParseOption(CommandNode parent) { OptionNode optionNode = new( CurrentToken, - (Option)CurrentToken.Symbol!, - parent); + (Option)CurrentToken.Symbol!); Advance(); @@ -209,7 +207,7 @@ void ParseDirective(CommandNode parent) ? withoutBrackets.Slice(indexOfColon + 1).ToString() : null; - var directiveNode = new DirectiveNode(token, parent, key, value); + var directiveNode = new DirectiveNode(token, key, value); parent.AddChildNode(directiveNode); diff --git a/src/System.CommandLine/Parsing/ParseResultVisitor.cs b/src/System.CommandLine/Parsing/ParseResultVisitor.cs index cdc04ac739..53d7d5b48b 100644 --- a/src/System.CommandLine/Parsing/ParseResultVisitor.cs +++ b/src/System.CommandLine/Parsing/ParseResultVisitor.cs @@ -21,7 +21,6 @@ internal sealed class ParseResultVisitor private readonly Dictionary _symbolResults = new(); - private List? _optionResults; private List? _argumentResults; private RootCommandResult? _rootCommandResult; @@ -52,9 +51,11 @@ internal ParseResultVisitor( } } - public void Visit(SyntaxNode node) + internal void Visit(CommandNode rootCommandNode) { - VisitInternal(node); + VisitRootCommandNode(rootCommandNode); + + VisitChildren(rootCommandNode); Stop(); } @@ -69,29 +70,16 @@ private void VisitInternal(SyntaxNode node) break; case CommandNode commandNode: - if (commandNode.Parent is null) - { - VisitRootCommandNode(commandNode); - } - else - { - VisitCommandNode(commandNode); - } + VisitCommandNode(commandNode); - for (var i = 0; i < commandNode.Children.Count; i++) - { - VisitInternal(commandNode.Children[i]); - } + VisitChildren(commandNode); break; case OptionNode optionNode: VisitOptionNode(optionNode); - for (var i = 0; i < optionNode.Children.Count; i++) - { - VisitInternal(optionNode.Children[i]); - } + VisitChildren(optionNode); break; @@ -116,10 +104,7 @@ private void AddToResult(CommandResult result) private void AddToResult(OptionResult result) { _innermostCommandResult?.AddChild(result); - if (_symbolResults.TryAdd(result.Option, result)) - { - (_optionResults ??= new()).Add(result); - } + _symbolResults.TryAdd(result.Option, result); } private void AddToResult(ArgumentResult result) @@ -241,6 +226,17 @@ private void VisitDirectiveNode(DirectiveNode directiveNode) } } + private void VisitChildren(NonterminalSyntaxNode parentNode) + { + if (parentNode.Children is not null) + { + for (var i = 0; i < parentNode.Children.Count; i++) + { + VisitInternal(parentNode.Children[i]); + } + } + } + private void Stop() { if (_isHelpRequested) @@ -254,9 +250,9 @@ private void Stop() ValidateCommandResult(); - if (_optionResults is not null) + foreach (var symbolPair in _symbolResults) { - foreach (var optionResult in _optionResults) + if (symbolPair.Value is OptionResult optionResult) { ValidateAndConvertOptionResult(optionResult); } @@ -307,7 +303,7 @@ private void ValidateAndConvertArgumentResults(IList arguments, List= argumentResults.Count) @@ -560,8 +556,8 @@ private void PopulateDefaultValues() var options = commandResult.Command.Options; for (var i = 0; i < options.Count; i++) { - Symbol symbol = options[i]; - Handle(_rootCommandResult!.FindResultForSymbol(symbol), symbol); + Option option = options[i]; + Handle(_rootCommandResult!.FindResultFor(option), option); } } @@ -570,8 +566,8 @@ private void PopulateDefaultValues() var arguments = commandResult.Command.Arguments; for (var i = 0; i < arguments.Count; i++) { - Symbol symbol = arguments[i]; - Handle(_rootCommandResult!.FindResultForSymbol(symbol), symbol); + Argument argument = arguments[i]; + Handle(_rootCommandResult!.FindResultFor(argument), argument); } } @@ -608,10 +604,7 @@ void Handle(SymbolResult? symbolResult, Symbol symbol) optionResult.AddChild(childArgumentResult); commandResult.AddChild(optionResult); - if (_symbolResults.TryAdd(optionResult.Symbol, optionResult)) - { - (_optionResults ??= new()).Add(optionResult); - } + _symbolResults.TryAdd(optionResult.Option, optionResult); break; diff --git a/src/System.CommandLine/Parsing/RootCommandResult.cs b/src/System.CommandLine/Parsing/RootCommandResult.cs index c5f0ae28ba..2f7888005e 100644 --- a/src/System.CommandLine/Parsing/RootCommandResult.cs +++ b/src/System.CommandLine/Parsing/RootCommandResult.cs @@ -5,7 +5,7 @@ namespace System.CommandLine.Parsing { - internal class RootCommandResult : CommandResult + internal sealed class RootCommandResult : CommandResult { private readonly Dictionary _symbolResults; @@ -17,54 +17,13 @@ public RootCommandResult( _symbolResults = symbolResults; } - internal override RootCommandResult Root => this; - public override ArgumentResult? FindResultFor(Argument argument) - { - if (_symbolResults.TryGetValue(argument, out var result) && - result is ArgumentResult argumentResult) - { - return argumentResult; - } - - return default; - } + => _symbolResults.TryGetValue(argument, out SymbolResult? result) ? (ArgumentResult)result : default; public override CommandResult? FindResultFor(Command command) - { - if (_symbolResults.TryGetValue(command, out var result) && - result is CommandResult commandResult) - { - return commandResult; - } - - return default; - } + => _symbolResults.TryGetValue(command, out SymbolResult? result) ? (CommandResult)result : default; public override OptionResult? FindResultFor(Option option) - { - if (_symbolResults.TryGetValue(option, out var result) && - result is OptionResult optionResult) - { - return optionResult; - } - - return default; - } - - internal SymbolResult? FindResultForSymbol(Symbol symbol) - { - switch (symbol) - { - case Argument argument: - return FindResultFor(argument); - case Command command: - return FindResultFor(command); - case Option option: - return FindResultFor(option); - default: - throw new ArgumentException($"Unsupported symbol type: {symbol.GetType()}"); - } - } + => _symbolResults.TryGetValue(option, out SymbolResult? result) ? (OptionResult)result : default; } } diff --git a/src/System.CommandLine/Parsing/SymbolResult.cs b/src/System.CommandLine/Parsing/SymbolResult.cs index 5415ef2458..433477ece7 100644 --- a/src/System.CommandLine/Parsing/SymbolResult.cs +++ b/src/System.CommandLine/Parsing/SymbolResult.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.CommandLine.Binding; +using System.Diagnostics; using System.Linq; namespace System.CommandLine.Parsing @@ -23,8 +24,6 @@ private protected SymbolResult( Symbol = symbol ?? throw new ArgumentNullException(nameof(symbol)); Parent = parent; - - Root = parent?.Root; } /// @@ -45,8 +44,6 @@ private protected SymbolResult( /// public SymbolResult? Parent { get; } - internal virtual RootCommandResult? Root { get; } - /// /// The symbol to which the result applies. /// @@ -111,24 +108,34 @@ public LocalizationResources LocalizationResources /// /// The argument for which to find a result. /// An argument result if the argument was matched by the parser or has a default value; otherwise, null. - public virtual ArgumentResult? FindResultFor(Argument argument) => - Root?.FindResultFor(argument); + public virtual ArgumentResult? FindResultFor(Argument argument) => GetRoot().FindResultFor(argument); /// /// Finds a result for the specific command anywhere in the parse tree, including parent and child symbol results. /// /// The command for which to find a result. /// An command result if the command was matched by the parser; otherwise, null. - public virtual CommandResult? FindResultFor(Command command) => - Root?.FindResultFor(command); + public virtual CommandResult? FindResultFor(Command command) => GetRoot().FindResultFor(command); /// /// Finds a result for the specific option anywhere in the parse tree, including parent and child symbol results. /// /// The option for which to find a result. /// An option result if the option was matched by the parser or has a default value; otherwise, null. - public virtual OptionResult? FindResultFor(Option option) => - Root?.FindResultFor(option); + public virtual OptionResult? FindResultFor(Option option) => GetRoot().FindResultFor(option); + + private SymbolResult GetRoot() + { + SymbolResult result = this; + while (result.Parent is not null) + { + result = result.Parent; + } + + Debug.Assert(result is RootCommandResult); + + return result; + } /// public T GetValue(Argument argument) diff --git a/src/System.CommandLine/Parsing/SymbolResultVisitor.cs b/src/System.CommandLine/Parsing/SymbolResultVisitor.cs deleted file mode 100644 index 443e32cca1..0000000000 --- a/src/System.CommandLine/Parsing/SymbolResultVisitor.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace System.CommandLine.Parsing -{ - internal abstract class SymbolResultVisitor - { - public void Visit(SymbolResult symbolResult) - { - Start(symbolResult); - - VisitInternal(symbolResult); - - Stop(symbolResult); - } - - private void VisitInternal(SymbolResult node) - { - switch (node) - { - case ArgumentResult argumentResult: - VisitArgumentResult(argumentResult); - - break; - - case RootCommandResult rootCommandResult: - VisitRootCommandResult(rootCommandResult); - - for (var i = 0; i < rootCommandResult.Children.Count; i++) - { - VisitInternal(rootCommandResult.Children[i]); - } - - break; - - case CommandResult commandResult: - VisitCommandResult(commandResult); - - for (var i = 0; i < commandResult.Children.Count; i++) - { - VisitInternal(commandResult.Children[i]); - } - - break; - - case OptionResult optionResult: - VisitOptionResult(optionResult); - - for (var i = 0; i < optionResult.Children.Count; i++) - { - VisitInternal(optionResult.Children[i]); - } - - break; - } - } - - protected virtual void VisitOptionResult(OptionResult optionResult) - { - } - - protected virtual void VisitCommandResult(CommandResult commandResult) - { - } - - protected virtual void VisitArgumentResult(ArgumentResult argumentResult) - { - } - - protected virtual void VisitRootCommandResult(RootCommandResult rootCommandResult) - { - } - - protected virtual void Start(SymbolResult node) - { - } - - protected virtual void Stop(SymbolResult node) - { - } - } -} \ No newline at end of file diff --git a/src/System.CommandLine/Parsing/SyntaxNode.cs b/src/System.CommandLine/Parsing/SyntaxNode.cs index 48105dec91..5ad049f415 100644 --- a/src/System.CommandLine/Parsing/SyntaxNode.cs +++ b/src/System.CommandLine/Parsing/SyntaxNode.cs @@ -5,16 +5,11 @@ namespace System.CommandLine.Parsing { internal abstract class SyntaxNode { - protected SyntaxNode( - Token token, - SyntaxNode? parent) + protected SyntaxNode(Token token) { Token = token; - Parent = parent; } - public SyntaxNode? Parent { get; } - public Token Token { get; } public override string ToString() => Token.Value;