From 1b1ae9a3c89ca08db1392ba1b5d3a91636f92ba9 Mon Sep 17 00:00:00 2001 From: Kim de Vos Date: Tue, 28 Mar 2023 09:33:32 +0200 Subject: [PATCH] Add diagnostic for missing space around `=` --- Sources/SwiftParser/Lexer/Cursor.swift | 18 +++++++++- .../LexerDiagnosticMessages.swift | 22 ++++++++++++ .../ParserDiagnosticMessages.swift | 3 ++ Sources/SwiftSyntax/TokenDiagnostic.swift | 2 ++ .../translated/RecoveryTests.swift | 36 ++++++++++++------- 5 files changed, 68 insertions(+), 13 deletions(-) diff --git a/Sources/SwiftParser/Lexer/Cursor.swift b/Sources/SwiftParser/Lexer/Cursor.swift index 16f5deb09b1..5f1c343c9ec 100644 --- a/Sources/SwiftParser/Lexer/Cursor.swift +++ b/Sources/SwiftParser/Lexer/Cursor.swift @@ -1967,7 +1967,23 @@ extension Lexer.Cursor { if self.input.baseAddress! - tokStart.input.baseAddress! == 1 { switch tokStart.peek() { case UInt8(ascii: "="): - return Lexer.Result(.equal) + if leftBound != rightBound { + var errorPos = tokStart + + if rightBound { + _ = errorPos.advance() + } + + return Lexer.Result( + .equal, + error: LexingDiagnostic( + .equalMustHaveConsistentWhitespaceOnBothSides, + position: errorPos + ) + ) + } else { + return Lexer.Result(.equal) + } case UInt8(ascii: "&"): if leftBound == rightBound || leftBound { break diff --git a/Sources/SwiftParserDiagnostics/LexerDiagnosticMessages.swift b/Sources/SwiftParserDiagnostics/LexerDiagnosticMessages.swift index 751cbe71806..09307f8ed0e 100644 --- a/Sources/SwiftParserDiagnostics/LexerDiagnosticMessages.swift +++ b/Sources/SwiftParserDiagnostics/LexerDiagnosticMessages.swift @@ -39,6 +39,7 @@ public extension TokenError { /// Please order the cases in this enum alphabetically by case name. public enum StaticTokenError: String, DiagnosticMessage { case editorPlaceholder = "editor placeholder in source file" + case equalMustHaveConsistentWhitespaceOnBothSides = "'=' must have consistent whitespace on both sides" case expectedBinaryExponentInHexFloatLiteral = "hexadecimal floating point literal must end with an exponent" case expectedClosingBraceInUnicodeEscape = #"expected '}' in \u{...} escape sequence"# case expectedDigitInFloatLiteral = "expected a digit in floating point exponent" @@ -134,6 +135,7 @@ public extension SwiftSyntax.TokenDiagnostic { switch self.kind { case .editorPlaceholder: return StaticTokenError.editorPlaceholder + case .equalMustHaveConsistentWhitespaceOnBothSides: return StaticTokenError.equalMustHaveConsistentWhitespaceOnBothSides case .expectedBinaryExponentInHexFloatLiteral: return StaticTokenError.expectedBinaryExponentInHexFloatLiteral case .expectedClosingBraceInUnicodeEscape: return StaticTokenError.expectedClosingBraceInUnicodeEscape case .expectedDigitInFloatLiteral: return StaticTokenError.expectedDigitInFloatLiteral @@ -200,6 +202,26 @@ public extension SwiftSyntax.TokenDiagnostic { return [ FixIt(message: .replaceCurlyQuoteByNormalQuote, changes: [[.replace(oldNode: Syntax(token), newNode: Syntax(fixedToken))]]) ] + case .equalMustHaveConsistentWhitespaceOnBothSides: + let hasLeadingSpace = token.previousToken(viewMode: .all)?.trailingTrivia.contains(where: { $0.isSpaceOrTab }) ?? false + let hasTrailingSpace = token.trailingTrivia.contains { $0.isSpaceOrTab } + var changes: [FixIt.Change] = [] + + if !hasLeadingSpace { + changes += [ + .replaceLeadingTrivia(token: token, newTrivia: .space) + ] + } + + if !hasTrailingSpace { + changes += [ + .replaceTrailingTrivia(token: token, newTrivia: .space) + ] + } + + return [ + FixIt(message: .insertWhitespace, changes: FixIt.Changes(changes: changes)) + ] default: return [] } diff --git a/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift b/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift index 47064aea51f..db70ffecdac 100644 --- a/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift +++ b/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift @@ -504,6 +504,9 @@ extension FixItMessage where Self == StaticParserFixIt { public static var insertNewline: Self { .init("insert newline") } + public static var insertWhitespace: Self { + .init("insert whitespace") + } public static var joinIdentifiers: Self { .init("join the identifiers together") } diff --git a/Sources/SwiftSyntax/TokenDiagnostic.swift b/Sources/SwiftSyntax/TokenDiagnostic.swift index 3cca78022ca..5b2be370e5a 100644 --- a/Sources/SwiftSyntax/TokenDiagnostic.swift +++ b/Sources/SwiftSyntax/TokenDiagnostic.swift @@ -23,6 +23,7 @@ public struct TokenDiagnostic: Hashable { // Please order these alphabetically case editorPlaceholder + case equalMustHaveConsistentWhitespaceOnBothSides case expectedBinaryExponentInHexFloatLiteral case expectedClosingBraceInUnicodeEscape case expectedDigitInFloatLiteral @@ -96,6 +97,7 @@ public struct TokenDiagnostic: Hashable { public var severity: Severity { switch kind { case .editorPlaceholder: return .error + case .equalMustHaveConsistentWhitespaceOnBothSides: return .error case .expectedBinaryExponentInHexFloatLiteral: return .error case .expectedClosingBraceInUnicodeEscape: return .error case .expectedDigitInFloatLiteral: return .error diff --git a/Tests/SwiftParserTest/translated/RecoveryTests.swift b/Tests/SwiftParserTest/translated/RecoveryTests.swift index 40f4a03eb6a..2de8062a7b3 100644 --- a/Tests/SwiftParserTest/translated/RecoveryTests.swift +++ b/Tests/SwiftParserTest/translated/RecoveryTests.swift @@ -1958,34 +1958,46 @@ final class RecoveryTests: XCTestCase { } func testRecovery163() { + // QoI: Common errors: 'let x= 5' and 'let x =5' could use Fix-its assertParse( """ - // QoI: Common errors: 'let x= 5' and 'let x =5' could use Fix-its func r22387625() { - let _= 5 - let _ =5 + let _1️⃣= 5 + let _ =2️⃣5 } """, diagnostics: [ - // TODO: Old parser expected error on line 3: '=' must have consistent whitespace on both sides, Fix-It replacements: 8 - 8 = ' ' - // TODO: Old parser expected error on line 4: '=' must have consistent whitespace on both sides, Fix-It replacements: 10 - 10 = ' ' - ] + DiagnosticSpec(locationMarker: "1️⃣", message: "'=' must have consistent whitespace on both sides", fixIts: ["insert whitespace"]), + DiagnosticSpec(locationMarker: "2️⃣", message: "'=' must have consistent whitespace on both sides", fixIts: ["insert whitespace"]), + ], + fixedSource: """ + func r22387625() { + let _ = 5 + let _ = 5 + } + """ ) } func testRecovery164() { + // https://github.com/apple/swift/issues/45723 assertParse( """ - // https://github.com/apple/swift/issues/45723 do { - let _: Int= 5 - let _: Array= [] + let _: Int1️⃣= 5 + let _: Array2️⃣= [] } """, diagnostics: [ - // TODO: Old parser expected error on line 3: '=' must have consistent whitespace on both sides, Fix-It replacements: 13 - 13 = ' ' - // TODO: Old parser expected error on line 4: '=' must have consistent whitespace on both sides, Fix-It replacements: 20 - 20 = ' ' - ] + DiagnosticSpec(locationMarker: "1️⃣", message: "'=' must have consistent whitespace on both sides", fixIts: ["insert whitespace"]), + DiagnosticSpec(locationMarker: "2️⃣", message: "'=' must have consistent whitespace on both sides", fixIts: ["insert whitespace"]), + ], + fixedSource: """ + do { + let _: Int = 5 + let _: Array = [] + } + """ ) }