diff --git a/Sources/SwiftParser/Parameters.swift b/Sources/SwiftParser/Parameters.swift index 355684d109f..1a40901237b 100644 --- a/Sources/SwiftParser/Parameters.swift +++ b/Sources/SwiftParser/Parameters.swift @@ -156,7 +156,22 @@ extension Parser { defaultValue = nil } - let trailingComma = self.consume(if: .comma) + var trailingComma: Token? + if self.at(.comma) { + trailingComma = self.consume(if: .comma) + } else if !self.at(.rightParen) { + let canParseIdentifier: Bool = withLookahead { + $0.canParseTypeIdentifier(allowKeyword: false) + } + + let canParseAttribute: Bool = withLookahead { + $0.consume(if: .atSign) != nil && $0.canParseCustomAttribute() + } + + if canParseIdentifier || canParseAttribute { + trailingComma = Token(missing: .comma, arena: self.arena) + } + } return RawFunctionParameterSyntax( attributes: attrs, diff --git a/Tests/SwiftParserTest/DeclarationTests.swift b/Tests/SwiftParserTest/DeclarationTests.swift index 5a278a4e4cf..261edaf59e6 100644 --- a/Tests/SwiftParserTest/DeclarationTests.swift +++ b/Tests/SwiftParserTest/DeclarationTests.swift @@ -1531,7 +1531,8 @@ final class DeclarationTests: ParserTestCase { leftSquare: .leftSquareToken(), element: IdentifierTypeSyntax(name: .identifier("third")), rightSquare: .rightSquareToken(presence: .missing) - ) + ), + trailingComma: .commaToken(presence: .missing) ), diagnostics: [ DiagnosticSpec( @@ -1547,11 +1548,12 @@ final class DeclarationTests: ParserTestCase { ), DiagnosticSpec( locationMarker: "4️⃣", - message: "unexpected code 'fourth: Int' in parameter clause" + message: "expected ',' in parameter", + fixIts: ["insert ','"] ), ], fixedSource: """ - func foo(first second: [third]fourth: Int) {} + func foo(first second: [third], fourth: Int) {} """ ) } @@ -3417,4 +3419,85 @@ final class DeclarationTests: ParserTestCase { ] ) } + + func testMissingCommaInParameters() { + assertParse( + "func a(foo: Bar1️⃣ foo2: Bar2) {}", + diagnostics: [ + DiagnosticSpec( + message: "expected ',' in parameter", + fixIts: ["insert ','"] + ) + ], + fixedSource: "func a(foo: Bar, foo2: Bar2) {}" + ) + } + + func testMissingMultipleCommasInParameters() { + assertParse( + "func a(foo: Bar1️⃣ foo2: Bar2, foo3: Bar32️⃣ foo4: Bar4) {}", + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "expected ',' in parameter", + fixIts: ["insert ','"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "expected ',' in parameter", + fixIts: ["insert ','"] + ), + ], + fixedSource: "func a(foo: Bar, foo2: Bar2, foo3: Bar3, foo4: Bar4) {}" + ) + } + + func testMissingCommaInParametersAndAttributes() { + assertParse( + "func a(foo: Bar1️⃣ @escaping foo1: () -> Void) {}", + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "expected ',' in parameter", + fixIts: ["insert ','"] + ) + ], + fixedSource: "func a(foo: Bar, @escaping foo1: () -> Void) {}" + ) + } + + func testMissingCommaInParametersWithNewline() { + assertParse( + """ + func foo(a: Int1️⃣ + x: Int = 2 + ) {} + """, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "expected ',' in parameter", + fixIts: ["insert ','"] + ) + ], + fixedSource: + """ + func foo(a: Int, + x: Int = 2 + ) {} + """ + ) + } + + func testMissingCommaWithgarbageCode() { + assertParse( + "func foo(x: Int 1️⃣Int<@abc)", + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "unexpected code 'Int<@abc' in parameter clause" + ) + ] + ) + } }