Skip to content

Commit 2e81b0a

Browse files
committed
Add diagnostic for unexpected second identifier
1 parent 92178c0 commit 2e81b0a

File tree

11 files changed

+163
-112
lines changed

11 files changed

+163
-112
lines changed

Sources/SwiftParser/Patterns.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,9 +217,20 @@ extension Parser {
217217
while !self.at(.eof, .rightParen) && keepGoing && loopProgress.evaluate(currentToken) {
218218
// If the tuple element has a label, parse it.
219219
let labelAndColon = self.consume(if: .identifier, followedBy: .colon)
220-
let (label, colon) = (labelAndColon?.0, labelAndColon?.1)
220+
var (label, colon) = (labelAndColon?.0, labelAndColon?.1)
221+
222+
if colon == nil, peek().rawTokenKind == .identifier, peek().tokenText.isStartingWithUppercase {
223+
label = consumeAnyToken()
224+
colon = self.missingToken(.colon)
225+
}
226+
221227
let pattern = self.parsePattern()
222-
let trailingComma = self.consume(if: .comma)
228+
var trailingComma = self.consume(if: .comma)
229+
230+
if trailingComma == nil && self.at(TokenSpec(.identifier, allowAtStartOfLine: false)) {
231+
trailingComma = self.missingToken(RawTokenKind.comma)
232+
}
233+
223234
keepGoing = trailingComma != nil
224235
elements.append(
225236
RawTuplePatternElementSyntax(

Sources/SwiftParser/SyntaxUtils.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,14 @@ extension SyntaxText {
8888
var isEditorPlaceholder: Bool {
8989
return self.starts(with: SyntaxText("<#")) && self.hasSuffix(SyntaxText("#>"))
9090
}
91+
92+
var isStartingWithUppercase: Bool {
93+
if !self.isEmpty, let firstCharacterByte = self.baseAddress?.pointee {
94+
return 65 <= firstCharacterByte && firstCharacterByte <= 90
95+
} else {
96+
return false
97+
}
98+
}
9199
}
92100

93101
extension RawTokenKind {

Sources/SwiftParser/Types.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,7 @@ extension Parser {
536536
if let first = first,
537537
second == nil,
538538
colon?.isMissing == true,
539-
first.tokenText.description.first?.isUppercase == true
539+
first.tokenText.isStartingWithUppercase
540540
{
541541
elements.append(
542542
RawTupleTypeElementSyntax(

Sources/SwiftParserDiagnostics/DiagnosticExtensions.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,10 @@ extension FixIt.Changes {
137137
}
138138
}
139139

140-
/// Transfers the leading and trivia trivia of `nodes` to the trailing trivia
141-
/// of the previous token. While doing this, it tries to be smart, merging trivia
142-
/// where it makes sense and refusing to add e.g. a space after punctuation,
143-
/// where it usually doesn't make sense.
140+
/// Transfers the leading and trailing trivia of `nodes` to the previous token
141+
/// While doing this, it tries to be smart, merging trivia where it makes sense
142+
/// and refusing to add e.g. a space after punctuation, where it usually
143+
/// doesn't make sense.
144144
static func transferTriviaAtSides<SyntaxType: SyntaxProtocol>(from nodes: [SyntaxType]) -> Self {
145145
let removedTriviaAtSides = Trivia.merged(nodes.first?.leadingTrivia ?? [], nodes.last?.trailingTrivia ?? [])
146146
if !removedTriviaAtSides.isEmpty, let previousToken = nodes.first?.previousToken(viewMode: .sourceAccurate) {

Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
327327
FixIt(
328328
message: .joinIdentifiers,
329329
changes: [
330-
[.replace(oldNode: Syntax(previousToken), newNode: Syntax(TokenSyntax(.identifier(joined), presence: .present)))],
330+
[.replace(oldNode: Syntax(previousToken), newNode: Syntax(TokenSyntax(.identifier(joined), trailingTrivia: tokens.last?.trailingTrivia ?? [], presence: .present)))],
331331
.makeMissing(tokens),
332332
]
333333
)
@@ -338,7 +338,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
338338
FixIt(
339339
message: .joinIdentifiersWithCamelCase,
340340
changes: [
341-
[.replace(oldNode: Syntax(previousToken), newNode: Syntax(TokenSyntax(.identifier(joinedUsingCamelCase), presence: .present)))],
341+
[.replace(oldNode: Syntax(previousToken), newNode: Syntax(TokenSyntax(.identifier(joinedUsingCamelCase), trailingTrivia: tokens.last?.trailingTrivia ?? [], presence: .present)))],
342342
.makeMissing(tokens),
343343
]
344344
)

Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -383,9 +383,9 @@ public struct SpaceSeparatedIdentifiersError: ParserError {
383383
if let name = firstToken.parent?.ancestorOrSelf(mapping: {
384384
$0.nodeTypeNameForDiagnostics(allowBlockNames: false)
385385
}) {
386-
return "found an unexpected second identifier in \(name)"
386+
return "found an unexpected second identifier in \(name); is there an accidental break?"
387387
} else {
388-
return "found an unexpected second identifier"
388+
return "found an unexpected second identifier; is there an accidental break?"
389389
}
390390
}
391391
}
@@ -498,6 +498,9 @@ extension FixItMessage where Self == StaticParserFixIt {
498498
public static var changeIndentationToMatchClosingDelimiter: Self {
499499
.init("change indentation of this line to match closing delimiter")
500500
}
501+
public static var insertComma: Self {
502+
.init("insert ','")
503+
}
501504
public static var insertSemicolon: Self {
502505
.init("insert ';'")
503506
}

Sources/SwiftSyntax/SyntaxText.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
/// replacement character (`\u{FFFD}`).
3636
@_spi(RawSyntax)
3737
public struct SyntaxText {
38-
var buffer: UnsafeBufferPointer<UInt8>
38+
public var buffer: UnsafeBufferPointer<UInt8>
3939

4040
public init(buffer: UnsafeBufferPointer<UInt8>) {
4141
self.buffer = buffer

Tests/SwiftParserTest/ParserTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public class ParserTests: XCTestCase {
5454
}
5555
}
5656

57-
/// Run parsr tests on all of the Swift files in the given path, recursively.
57+
/// Run parser tests on all of the Swift files in the given path, recursively.
5858
func runParserTests(
5959
name: String,
6060
path: URL,

Tests/SwiftParserTest/TypeTests.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,17 @@
1515
import XCTest
1616

1717
final class TypeTests: XCTestCase {
18-
1918
func testMissingColonInType() {
2019
assertParse(
2120
"""
2221
var foo 1️⃣Bar = 1
2322
""",
2423
diagnostics: [
25-
DiagnosticSpec(message: "expected ':' in type annotation")
26-
]
24+
DiagnosticSpec(message: "expected ':' in type annotation", fixIts: ["insert ':'"])
25+
],
26+
fixedSource: """
27+
var foo: Bar = 1
28+
"""
2729
)
2830
}
2931

@@ -65,7 +67,6 @@ final class TypeTests: XCTestCase {
6567
}
6668

6769
func testClosureSignatures() {
68-
6970
assertParse(
7071
"""
7172
simple { [] str in

Tests/SwiftParserTest/translated/InvalidTests.swift

Lines changed: 72 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -388,92 +388,85 @@ final class InvalidTests: XCTestCase {
388388
)
389389
}
390390

391-
func testInvalid23a() {
392-
assertParse(
393-
"""
394-
func dog 1️⃣cow() {}
395-
""",
396-
diagnostics: [
397-
DiagnosticSpec(
398-
message: "found an unexpected second identifier in function",
399-
fixIts: [
400-
"join the identifiers together",
401-
"join the identifiers together with camel-case",
402-
]
403-
)
404-
],
405-
applyFixIts: ["join the identifiers together"],
406-
fixedSource: "func dogcow() {}"
407-
)
408-
}
409-
410-
func testInvalid23b() {
411-
assertParse(
412-
"""
413-
func dog 1️⃣cow() {}
414-
""",
415-
diagnostics: [
416-
DiagnosticSpec(
417-
message: "found an unexpected second identifier in function",
418-
fixIts: [
419-
"join the identifiers together",
420-
"join the identifiers together with camel-case",
421-
]
422-
)
423-
],
424-
applyFixIts: ["join the identifiers together with camel-case"],
425-
fixedSource: "func dogCow() {}"
426-
)
391+
func testInvalid23() {
392+
let testCases: [UInt: (fixIt: String, fixedSource: String)] = [
393+
#line: ("join the identifiers together", "func dogcow() {}"),
394+
#line: ("join the identifiers together with camel-case", "func dogCow() {}"),
395+
]
396+
397+
for (line, testCase) in testCases {
398+
assertParse(
399+
"""
400+
func dog 1️⃣cow() {}
401+
""",
402+
diagnostics: [
403+
DiagnosticSpec(
404+
message: "found an unexpected second identifier in function; is there an accidental break?",
405+
fixIts: [
406+
"join the identifiers together",
407+
"join the identifiers together with camel-case",
408+
]
409+
)
410+
],
411+
applyFixIts: [testCase.fixIt],
412+
fixedSource: testCase.fixedSource,
413+
line: line
414+
)
415+
}
427416
}
428417

429418
func testThreeIdentifersForFunctionName() {
430-
assertParse(
431-
"""
432-
func dog 1️⃣cow sheep() {}
433-
""",
434-
diagnostics: [
435-
DiagnosticSpec(
436-
message: "found an unexpected second identifier in function",
437-
fixIts: [
438-
"join the identifiers together",
439-
"join the identifiers together with camel-case",
440-
]
441-
)
442-
],
443-
applyFixIts: ["join the identifiers together with camel-case"],
444-
fixedSource: "func dogCowSheep() {}"
445-
)
446-
}
419+
let testCases: [UInt: (fixIt: String, fixedSource: String)] = [
420+
#line: ("join the identifiers together", "func dogcowsheep() {}"),
421+
#line: ("join the identifiers together with camel-case", "func dogCowSheep() {}"),
422+
]
447423

448-
func testInvalid24() {
449-
assertParse(
450-
"""
451-
func cat 1️⃣Mouse() {}
452-
""",
453-
diagnostics: [
454-
DiagnosticSpec(message: "found an unexpected second identifier in function", fixIts: ["join the identifiers together"])
455-
],
456-
fixedSource: "func catMouse() {}"
457-
)
424+
for (line, testCase) in testCases {
425+
assertParse(
426+
"""
427+
func dog 1️⃣cow sheep() {}
428+
""",
429+
diagnostics: [
430+
DiagnosticSpec(
431+
message: "found an unexpected second identifier in function; is there an accidental break?",
432+
fixIts: [
433+
"join the identifiers together",
434+
"join the identifiers together with camel-case",
435+
]
436+
)
437+
],
438+
applyFixIts: [testCase.fixIt],
439+
fixedSource: testCase.fixedSource,
440+
line: line
441+
)
442+
}
458443
}
459444

460445
func testInvalid25() {
461-
assertParse(
462-
"""
463-
func friend 1️⃣ship<T>(x: T) {}
464-
""",
465-
diagnostics: [
466-
DiagnosticSpec(
467-
message: "found an unexpected second identifier in function",
468-
fixIts: [
469-
"join the identifiers together",
470-
"join the identifiers together with camel-case",
471-
]
472-
)
473-
],
474-
applyFixIts: ["join the identifiers together with camel-case"],
475-
fixedSource: "func friendShip<T>(x: T) {}"
476-
)
446+
let testCases: [UInt: (fixIt: String, fixedSource: String)] = [
447+
#line: ("join the identifiers together", "func friendship<T>(x: T) {}"),
448+
#line: ("join the identifiers together with camel-case", "func friendShip<T>(x: T) {}"),
449+
]
450+
451+
for (line, testCase) in testCases {
452+
assertParse(
453+
"""
454+
func friend 1️⃣ship<T>(x: T) {}
455+
""",
456+
diagnostics: [
457+
DiagnosticSpec(
458+
message: "found an unexpected second identifier in function; is there an accidental break?",
459+
fixIts: [
460+
"join the identifiers together",
461+
"join the identifiers together with camel-case",
462+
]
463+
)
464+
],
465+
applyFixIts: [testCase.fixIt],
466+
fixedSource: testCase.fixedSource,
467+
line: line
468+
)
469+
}
477470
}
478471

479472
func testInvalid26() {

0 commit comments

Comments
 (0)