Skip to content

Commit 1fa2557

Browse files
authored
Show option-based errors before unexpected positional errors (#187)
This pushes any errors indicated by unexpected arguments after parsing out to the same late position. We were previously stopping immediately when the command is a leaf node; that isn't necessary and created an awkward second error path.
1 parent f9446dc commit 1fa2557

File tree

4 files changed

+24
-45
lines changed

4 files changed

+24
-45
lines changed

Sources/ArgumentParser/Parsing/ArgumentSet.swift

Lines changed: 6 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ extension ArgumentSet {
213213
///
214214
/// - Parameter all: The input (from the command line) that needs to be parsed
215215
/// - Parameter commandStack: commands that have been parsed
216-
func lenientParse(_ all: SplitArguments) throws -> LenientParsedValues {
216+
func lenientParse(_ all: SplitArguments) throws -> ParsedValues {
217217
// Create a local, mutable copy of the arguments:
218218
var inputArguments = all
219219

@@ -355,21 +355,11 @@ extension ArgumentSet {
355355

356356
// We have parsed all non-positional values at this point.
357357
// Next: parse / consume the positional values.
358-
do {
359-
var stripped = all
360-
stripped.removeAll(in: usedOrigins)
361-
try parsePositionalValues(from: stripped, into: &result)
362-
} catch {
363-
switch error {
364-
case ParserError.unexpectedExtraValues:
365-
// There were more positional values than we could parse.
366-
// If we‘re using subcommands, that could be expected.
367-
return .partial(result, error)
368-
default:
369-
throw error
370-
}
371-
}
372-
return .success(result)
358+
var unusedArguments = all
359+
unusedArguments.removeAll(in: usedOrigins)
360+
try parsePositionalValues(from: unusedArguments, into: &result)
361+
362+
return result
373363
}
374364
}
375365

@@ -450,16 +440,5 @@ extension ArgumentSet {
450440
try update([origin], nil, value, &result)
451441
} while argumentDefinition.isRepeatingPositional
452442
}
453-
454-
// Finished with the defined arguments; are there leftover values to parse?
455-
skipNonValues()
456-
guard argumentStack.isEmpty else {
457-
let extraValues: [(InputOrigin, String)] = argumentStack
458-
.map { $0.0 }
459-
.map {
460-
(InputOrigin(element: $0), unusedInput.originalInput(at: $0)!)
461-
}
462-
throw ParserError.unexpectedExtraValues(extraValues)
463-
}
464443
}
465444
}

Sources/ArgumentParser/Parsing/CommandParser.swift

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -110,19 +110,8 @@ extension CommandParser {
110110
// Build the argument set (i.e. information on how to parse):
111111
let commandArguments = ArgumentSet(currentNode.element)
112112

113-
// Parse the arguments into a ParsedValues:
114-
let parsedResult = try commandArguments.lenientParse(split)
115-
116-
let values: ParsedValues
117-
switch parsedResult {
118-
case .success(let v):
119-
values = v
120-
case .partial(let v, let e):
121-
values = v
122-
if currentNode.isLeaf {
123-
throw e
124-
}
125-
}
113+
// Parse the arguments, ignoring anything unexpected
114+
let values = try commandArguments.lenientParse(split)
126115

127116
// Decode the values from ParsedValues into the ParsableCommand:
128117
let decoder = ArgumentDecoder(values: values, previouslyDecoded: decodedArguments)

Sources/ArgumentParser/Parsing/ParsedValues.swift

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,6 @@ struct ParsedValues {
4343
var originalInput: [String]
4444
}
4545

46-
enum LenientParsedValues {
47-
case success(ParsedValues)
48-
case partial(ParsedValues, Swift.Error)
49-
}
50-
5146
extension ParsedValues {
5247
mutating func set(_ new: Any, forKey key: InputKey, inputOrigin: InputOrigin) {
5348
set(Element(key: key, value: new, inputOrigin: inputOrigin))

Tests/ArgumentParserUnitTests/ErrorMessageTests.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,3 +174,19 @@ extension ErrorMessageTests {
174174
AssertErrorMessage(OptOptions.self, ["-cbl"], "Value to be set with flag \'l\' in \'-cbl\' had already been set with flag \'c\' in \'-cbl\'")
175175
}
176176
}
177+
178+
// MARK: -
179+
180+
fileprivate struct Repeat: ParsableArguments {
181+
@Option() var count: Int?
182+
@Argument() var phrase: String
183+
}
184+
185+
extension ErrorMessageTests {
186+
func testBadOptionBeforeArgument() {
187+
AssertErrorMessage(
188+
Repeat.self,
189+
["--cont", "5", "Hello"],
190+
"Unknown option '--cont'. Did you mean '--count'?")
191+
}
192+
}

0 commit comments

Comments
 (0)