diff --git a/Sources/SwiftBasicFormat/Trivia+FormatExtensions.swift b/Sources/SwiftBasicFormat/Trivia+FormatExtensions.swift index 2bd55763743..40806430033 100644 --- a/Sources/SwiftBasicFormat/Trivia+FormatExtensions.swift +++ b/Sources/SwiftBasicFormat/Trivia+FormatExtensions.swift @@ -45,7 +45,9 @@ extension Trivia { /// Returns the indentation of the last trivia piece in this trivia that is /// not a whitespace. - func indentation(isOnNewline: Bool) -> Trivia? { + /// - Parameter isOnNewline: Specifies if the character before this trivia is a newline character, i.e. if this trivia already starts on a new line. + /// - Returns: An optional ``Trivia`` with indentation of the last trivia piece. + public func indentation(isOnNewline: Bool) -> Trivia? { let lastNonWhitespaceTriviaPieceIndex = self.pieces.lastIndex(where: { !$0.isWhitespace }) ?? self.pieces.endIndex let piecesBeforeLastNonWhitespace = self.pieces[.. diff --git a/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift b/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift index bbbf31e724c..d66b870da13 100644 --- a/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift +++ b/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift @@ -51,6 +51,31 @@ fileprivate func getTokens(between first: TokenSyntax, and second: TokenSyntax) } fileprivate extension TokenSyntax { + /// The indentation of this token + /// + /// In contrast to `indentation`, this does not walk to previous tokens to + /// find the indentation of the line this token occurs on. + private var indentation: Trivia? { + let previous = self.previousToken(viewMode: .sourceAccurate) + return ((previous?.trailingTrivia ?? []) + leadingTrivia).indentation(isOnNewline: false) + } + + /// Returns the indentation of the line this token occurs on + var indentationOfLine: Trivia { + var token: TokenSyntax = self + if let indentation = token.indentation { + return indentation + } + while let previous = token.previousToken(viewMode: .sourceAccurate) { + token = previous + if let indentation = token.indentation { + return indentation + } + } + + return [] + } + /// Assuming this token is a `poundAvailableKeyword` or `poundUnavailableKeyword` /// returns the opposite keyword. var negatedAvailabilityKeyword: TokenSyntax { @@ -702,13 +727,28 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor { // Only diagnose the missing semicolon if the item doesn't contain any errors. // If the item contains errors, the root cause is most likely something different and not the missing semicolon. let position = semicolon.previousToken(viewMode: .sourceAccurate)?.endPositionBeforeTrailingTrivia + var fixIts: [FixIt] = [ + FixIt(message: .insertSemicolon, changes: .makePresent(semicolon)) + ] + if let firstToken = node.firstToken(viewMode: .sourceAccurate), + let lastToken = node.lastToken(viewMode: .sourceAccurate) + { + fixIts.insert( + FixIt( + message: .insertNewline, + changes: [ + .replaceTrailingTrivia(token: lastToken, newTrivia: lastToken.trailingTrivia + .newlines(1) + firstToken.indentationOfLine) + ] + ), + at: 0 + ) + } + addDiagnostic( semicolon, position: position, .consecutiveStatementsOnSameLine, - fixIts: [ - FixIt(message: .insertSemicolon, changes: .makePresent(semicolon)) - ], + fixIts: fixIts, handledNodes: [semicolon.id] ) } else { @@ -1155,13 +1195,28 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor { // Only diagnose the missing semicolon if the decl doesn't contain any errors. // If the decl contains errors, the root cause is most likely something different and not the missing semicolon. let position = semicolon.previousToken(viewMode: .sourceAccurate)?.endPositionBeforeTrailingTrivia + var fixIts: [FixIt] = [ + FixIt(message: .insertSemicolon, changes: .makePresent(semicolon)) + ] + if let firstToken = node.firstToken(viewMode: .sourceAccurate), + let lastToken = node.lastToken(viewMode: .sourceAccurate) + { + fixIts.insert( + FixIt( + message: .insertNewline, + changes: [ + .replaceTrailingTrivia(token: lastToken, newTrivia: lastToken.trailingTrivia + .newlines(1) + firstToken.indentationOfLine) + ] + ), + at: 0 + ) + } + addDiagnostic( semicolon, position: position, .consecutiveDeclarationsOnSameLine, - fixIts: [ - FixIt(message: .insertSemicolon, changes: .makePresent(semicolon)) - ], + fixIts: fixIts, handledNodes: [semicolon.id] ) } else { diff --git a/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift b/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift index 60e8dd06126..10da98e7b15 100644 --- a/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift +++ b/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift @@ -106,10 +106,10 @@ extension DiagnosticMessage where Self == StaticParserError { .init("'class' constraint can only appear on protocol declarations") } public static var consecutiveDeclarationsOnSameLine: Self { - .init("consecutive declarations on a line must be separated by ';'") + .init("consecutive declarations on a line must be separated by newline or ';'") } public static var consecutiveStatementsOnSameLine: Self { - .init("consecutive statements on a line must be separated by ';'") + .init("consecutive statements on a line must be separated by newline or ';'") } public static var cStyleForLoop: Self { .init("C-style for statement has been removed in Swift 3") diff --git a/Tests/SwiftParserTest/Assertions.swift b/Tests/SwiftParserTest/Assertions.swift index 496794dd756..dcef165bbca 100644 --- a/Tests/SwiftParserTest/Assertions.swift +++ b/Tests/SwiftParserTest/Assertions.swift @@ -275,15 +275,13 @@ class FixItApplier: SyntaxRewriter { var changes: [FixIt.Change] init(diagnostics: [Diagnostic], withMessages messages: [String]?) { + let messages = messages ?? diagnostics.compactMap { $0.fixIts.first?.message.message } + self.changes = diagnostics .flatMap { $0.fixIts } .filter { - if let messages { - return messages.contains($0.message.message) - } else { - return true - } + return messages.contains($0.message.message) } .flatMap { $0.changes } } diff --git a/Tests/SwiftParserTest/DeclarationTests.swift b/Tests/SwiftParserTest/DeclarationTests.swift index 62b5734bf2f..c6084fd3833 100644 --- a/Tests/SwiftParserTest/DeclarationTests.swift +++ b/Tests/SwiftParserTest/DeclarationTests.swift @@ -329,9 +329,54 @@ final class DeclarationTests: XCTestCase { internal(set) var defaultProp = 0 """, diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), - DiagnosticSpec(locationMarker: "2️⃣", message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + ], + applyFixIts: ["insert newline"], + fixedSource: """ + open + open(set) + var openProp = 0 + public public(set) var publicProp = 0 + package package(set) var packageProp = 0 + internal internal(set) var internalProp = 0 + fileprivate fileprivate(set) var fileprivateProp = 0 + private private(set) var privateProp = 0 + internal(set) var defaultProp = 0 + """ + ) + + assertParse( + """ + open1️⃣ open(set)2️⃣ var openProp = 0 + public public(set) var publicProp = 0 + package package(set) var packageProp = 0 + internal internal(set) var internalProp = 0 + fileprivate fileprivate(set) var fileprivateProp = 0 + private private(set) var privateProp = 0 + internal(set) var defaultProp = 0 + """, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), ], + applyFixIts: ["insert ';'"], fixedSource: """ open; open(set); var openProp = 0 public public(set) var publicProp = 0 @@ -1025,18 +1070,74 @@ final class DeclarationTests: XCTestCase { ) } - func testTextRecovery() { + func testTextRecovery1() { + assertParse( + """ + Lorem1️⃣ ipsum2️⃣ dolor3️⃣ sit4️⃣ amet5️⃣, consectetur adipiscing elit + """, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "3️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "4️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec(locationMarker: "5️⃣", message: "extraneous code ', consectetur adipiscing elit' at top level"), + ], + applyFixIts: ["insert newline"], + fixedSource: """ + Lorem + ipsum + dolor + sit + amet, consectetur adipiscing elit + """ + ) + } + + func testTextRecovery2() { assertParse( """ Lorem1️⃣ ipsum2️⃣ dolor3️⃣ sit4️⃣ amet5️⃣, consectetur adipiscing elit """, diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), - DiagnosticSpec(locationMarker: "2️⃣", message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), - DiagnosticSpec(locationMarker: "3️⃣", message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), - DiagnosticSpec(locationMarker: "4️⃣", message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "3️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "4️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), DiagnosticSpec(locationMarker: "5️⃣", message: "extraneous code ', consectetur adipiscing elit' at top level"), ], + applyFixIts: ["insert ';'"], fixedSource: """ Lorem; ipsum; dolor; sit; amet, consectetur adipiscing elit """ @@ -2409,4 +2510,24 @@ final class DeclarationTests: XCTestCase { fixedSource: "protocol X<<#identifier#>> {}" ) } + + func testCorrectIndentationWithNewline() { + assertParse( + """ + func + test() {}1️⃣ var other: Int + """, + diagnostics: [ + DiagnosticSpec( + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ) + ], + fixedSource: """ + func + test() {} + var other: Int + """ + ) + } } diff --git a/Tests/SwiftParserTest/ExpressionTests.swift b/Tests/SwiftParserTest/ExpressionTests.swift index dee4ec33c65..851747ff808 100644 --- a/Tests/SwiftParserTest/ExpressionTests.swift +++ b/Tests/SwiftParserTest/ExpressionTests.swift @@ -857,8 +857,8 @@ final class ExpressionTests: XCTestCase { diagnostics: [ DiagnosticSpec( locationMarker: "3️⃣", - message: "consecutive statements on a line must be separated by ';'", - fixIts: ["insert ';'"] + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] ), DiagnosticSpec( locationMarker: "5️⃣", @@ -879,6 +879,53 @@ final class ExpressionTests: XCTestCase { fixIts: ["insert '}'"] ), ], + applyFixIts: ["insert newline", #"insert '"'"#, "insert '}'"], + fixedSource: #""" + func nestThoseIfs() { + \n + if false != true { + \n + print + "\(i)\"\n" + } + } + """# + ) + + assertParse( + #""" + func nestThoseIfs() 1️⃣{ + \n + if false != true 2️⃣{ + \n + print3️⃣ 4️⃣"\(i)\"\n5️⃣ + """#, + diagnostics: [ + DiagnosticSpec( + locationMarker: "3️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "5️⃣", + message: #"expected '"' to end string literal"#, + notes: [NoteSpec(locationMarker: "4️⃣", message: #"to match this opening '"'"#)], + fixIts: [#"insert '"'"#] + ), + DiagnosticSpec( + locationMarker: "5️⃣", + message: "expected '}' to end 'if' statement", + notes: [NoteSpec(locationMarker: "2️⃣", message: "to match this opening '{'")], + fixIts: ["insert '}'"] + ), + DiagnosticSpec( + locationMarker: "5️⃣", + message: "expected '}' to end function", + notes: [NoteSpec(locationMarker: "1️⃣", message: "to match this opening '{'")], + fixIts: ["insert '}'"] + ), + ], + applyFixIts: ["insert ';'", #"insert '"'"#, "insert '}'"], fixedSource: #""" func nestThoseIfs() { \n @@ -1958,7 +2005,7 @@ final class StatementExpressionTests: XCTestCase { ) } - func testUnterminatedString3() { + func testUnterminatedString3a() { assertParse( #""" 1️⃣"abc2️⃣ @@ -1973,8 +2020,8 @@ final class StatementExpressionTests: XCTestCase { ), DiagnosticSpec( locationMarker: "3️⃣", - message: "consecutive statements on a line must be separated by ';'", - fixIts: ["insert ';'"] + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] ), DiagnosticSpec( locationMarker: "4️⃣", @@ -1983,6 +2030,41 @@ final class StatementExpressionTests: XCTestCase { fixIts: [#"insert '"'"#] ), ], + applyFixIts: [#"insert '"'"#, "insert newline"], + fixedSource: #""" + "abc" + \(def) + "" + """# + ) + } + + func testUnterminatedString3b() { + assertParse( + #""" + 1️⃣"abc2️⃣ + \(def)3️⃣"4️⃣ + """#, + diagnostics: [ + DiagnosticSpec( + locationMarker: "2️⃣", + message: #"expected '"' to end string literal"#, + notes: [NoteSpec(locationMarker: "1️⃣", message: #"to match this opening '"'"#)], + fixIts: [#"insert '"'"#] + ), + DiagnosticSpec( + locationMarker: "3️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "4️⃣", + message: #"expected '"' to end string literal"#, + notes: [NoteSpec(locationMarker: "3️⃣", message: #"to match this opening '"'"#)], + fixIts: [#"insert '"'"#] + ), + ], + applyFixIts: [#"insert '"'"#, "insert ';'"], fixedSource: #""" "abc" \(def); "" @@ -2051,7 +2133,7 @@ final class StatementExpressionTests: XCTestCase { ) } - func testUnterminatedString6() { + func testUnterminatedString6a() { assertParse( #""" 1️⃣"abc2️⃣\3️⃣ @@ -2070,8 +2152,8 @@ final class StatementExpressionTests: XCTestCase { ), DiagnosticSpec( locationMarker: "4️⃣", - message: "consecutive statements on a line must be separated by ';'", - fixIts: ["insert ';'"] + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] ), DiagnosticSpec( locationMarker: "5️⃣", @@ -2080,6 +2162,45 @@ final class StatementExpressionTests: XCTestCase { fixIts: [#"insert '"'"#] ), ], + applyFixIts: [#"insert '"'"#, "insert newline"], + fixedSource: #""" + "abc\" + (def) + "" + """# + ) + } + + func testUnterminatedString6b() { + assertParse( + #""" + 1️⃣"abc2️⃣\3️⃣ + (def)4️⃣"5️⃣ + """#, + diagnostics: [ + DiagnosticSpec( + locationMarker: "2️⃣", + message: "invalid escape sequence in literal" + ), + DiagnosticSpec( + locationMarker: "3️⃣", + message: #"expected '"' to end string literal"#, + notes: [NoteSpec(locationMarker: "1️⃣", message: #"to match this opening '"'"#)], + fixIts: [#"insert '"'"#] + ), + DiagnosticSpec( + locationMarker: "4️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "5️⃣", + message: #"expected '"' to end string literal"#, + notes: [NoteSpec(locationMarker: "4️⃣", message: #"to match this opening '"'"#)], + fixIts: [#"insert '"'"#] + ), + ], + applyFixIts: [#"insert '"'"#, "insert ';'"], fixedSource: #""" "abc\" (def); "" @@ -2087,7 +2208,39 @@ final class StatementExpressionTests: XCTestCase { ) } - func testUnterminatedString7() { + func testUnterminatedString7a() { + assertParse( + #""" + #1️⃣ + "abc"2️⃣#3️⃣ + """#, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "expected identifier in macro expansion", + fixIts: ["insert identifier"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "3️⃣", + message: "expected identifier in macro expansion", + fixIts: ["insert identifier"] + ), + ], + applyFixIts: ["insert identifier", "insert newline"], + fixedSource: #""" + #<#identifier#> + "abc" + #<#identifier#> + """# + ) + } + + func testUnterminatedString7b() { assertParse( #""" #1️⃣ @@ -2095,9 +2248,14 @@ final class StatementExpressionTests: XCTestCase { """#, diagnostics: [ DiagnosticSpec(locationMarker: "1️⃣", message: "expected identifier in macro expansion", fixIts: ["insert identifier"]), - DiagnosticSpec(locationMarker: "2️⃣", message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), DiagnosticSpec(locationMarker: "3️⃣", message: "expected identifier in macro expansion", fixIts: ["insert identifier"]), ], + applyFixIts: ["insert identifier", "insert ';'"], fixedSource: #""" #<#identifier#> "abc"; #<#identifier#> @@ -2105,7 +2263,40 @@ final class StatementExpressionTests: XCTestCase { ) } - func testUnterminatedString8() { + func testUnterminatedString8a() { + assertParse( + #""" + #"1️⃣ + abc2️⃣"#3️⃣ + """#, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: ##"expected '"#' to end string literal"##, + fixIts: [##"insert '"#'"##] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "3️⃣", + message: #"expected '"' to end string literal"#, + notes: [NoteSpec(locationMarker: "2️⃣", message: #"to match this opening '"'"#)], + fixIts: [#"insert '"'"#] + ), + ], + applyFixIts: [##"insert '"#'"##, "insert newline", #"insert '"'"#], + fixedSource: #""" + #""# + abc + "#" + """# + ) + } + + func testUnterminatedString8b() { assertParse( #""" #"1️⃣ @@ -2119,8 +2310,8 @@ final class StatementExpressionTests: XCTestCase { ), DiagnosticSpec( locationMarker: "2️⃣", - message: "consecutive statements on a line must be separated by ';'", - fixIts: ["insert ';'"] + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] ), DiagnosticSpec( locationMarker: "3️⃣", @@ -2129,6 +2320,7 @@ final class StatementExpressionTests: XCTestCase { fixIts: [#"insert '"'"#] ), ], + applyFixIts: [##"insert '"#'"##, "insert ';'", #"insert '"'"#], fixedSource: #""" #""# abc; "#" @@ -2187,56 +2379,192 @@ final class StatementExpressionTests: XCTestCase { ) } - func testConsecutiveStatements1() { + func testConsecutiveStatements1a() { + assertParse( + "{a1️⃣ b2️⃣ c}", + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + ], + applyFixIts: ["insert newline"], + fixedSource: """ + {a + b + c} + """ + ) + } + + func testConsecutiveStatements1b() { assertParse( "{a1️⃣ b2️⃣ c}", diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), - DiagnosticSpec(locationMarker: "2️⃣", message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), ], + applyFixIts: ["insert ';'"], fixedSource: """ {a; b; c} """ ) } + func testConsecutiveStatements2a() { + assertParse( + "switch x {case y: a1️⃣ b2️⃣ c}", + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + ], + applyFixIts: ["insert newline"], + fixedSource: """ + switch x {case y: a + b + c} + """ + ) + } + func testConsecutiveStatements2() { assertParse( "switch x {case y: a1️⃣ b2️⃣ c}", diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), - DiagnosticSpec(locationMarker: "2️⃣", message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), ], + applyFixIts: ["insert ';'"], fixedSource: """ switch x {case y: a; b; c} """ ) } - func testConsecutiveStatements3() { + func testConsecutiveStatements3a() { assertParse( """ var i: Int { a1️⃣ b2️⃣ c } """, diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), - DiagnosticSpec(locationMarker: "2️⃣", message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), ], + applyFixIts: ["insert newline"], + fixedSource: """ + var i: Int { a + b + c } + """ + ) + } + + func testConsecutiveStatements3b() { + assertParse( + """ + var i: Int { a1️⃣ b2️⃣ c } + """, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + ], + applyFixIts: ["insert ';'"], fixedSource: """ var i: Int { a; b; c } """ ) } - func testConsecutiveStatements4() { + func testConsecutiveStatements4a() { assertParse( """ var i: Int { get {a1️⃣ b} set {c2️⃣ d} } """, diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), - DiagnosticSpec(locationMarker: "2️⃣", message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), ], + applyFixIts: ["insert newline"], + fixedSource: """ + var i: Int { get {a + b} set {c + d} } + """ + ) + } + + func testConsecutiveStatements4b() { + assertParse( + """ + var i: Int { get {a1️⃣ b} set {c2️⃣ d} } + """, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + ], + applyFixIts: ["insert ';'"], fixedSource: """ var i: Int { get {a; b} set {c; d} } """ @@ -2280,14 +2608,37 @@ final class StatementExpressionTests: XCTestCase { ) } - func testStringLiteralAfterKeyPath() { + func testStringLiteralAfterKeyPath1() { assertParse( #""" \String.?1️⃣"" """#, diagnostics: [ - DiagnosticSpec(message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]) + DiagnosticSpec( + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ) + ], + applyFixIts: ["insert newline"], + fixedSource: #""" + \String.? + "" + """# + ) + } + + func testStringLiteralAfterKeyPath2() { + assertParse( + #""" + \String.?1️⃣"" + """#, + diagnostics: [ + DiagnosticSpec( + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ) ], + applyFixIts: ["insert ';'"], fixedSource: #""" \String.?; "" """# diff --git a/Tests/SwiftParserTest/RegexLiteralTests.swift b/Tests/SwiftParserTest/RegexLiteralTests.swift index 14c28ca1fae..f98df0324bb 100644 --- a/Tests/SwiftParserTest/RegexLiteralTests.swift +++ b/Tests/SwiftParserTest/RegexLiteralTests.swift @@ -1208,15 +1208,49 @@ final class RegexLiteralTests: XCTestCase { ) } - func testPrefixOpSplitting2() { + func testPrefixOpSplitting2a() { assertParse( """ let x1️⃣ .2️⃣/abc/ """, diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), - DiagnosticSpec(locationMarker: "2️⃣", message: "expected name in member access", fixIts: ["insert name"]), + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "expected name in member access", + fixIts: ["insert name"] + ), + ], + applyFixIts: ["insert newline", "insert name"], + fixedSource: """ + let x + .<#identifier#>/abc/ + """ + ) + } + + func testPrefixOpSplitting2b() { + assertParse( + """ + let x1️⃣ .2️⃣/abc/ + """, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "expected name in member access", + fixIts: ["insert name"] + ), ], + applyFixIts: ["insert ';'", "insert name"], fixedSource: """ let x; .<#identifier#>/abc/ """ diff --git a/Tests/SwiftParserTest/translated/ConsecutiveStatementsTests.swift b/Tests/SwiftParserTest/translated/ConsecutiveStatementsTests.swift index 144dd3f3fe4..a2168a47b98 100644 --- a/Tests/SwiftParserTest/translated/ConsecutiveStatementsTests.swift +++ b/Tests/SwiftParserTest/translated/ConsecutiveStatementsTests.swift @@ -15,12 +15,27 @@ import XCTest final class ConsecutiveStatementsTests: XCTestCase { - func testSimple() { + func testSimple1() { assertParse( "let x = 21️⃣ let y = 3", diagnostics: [ - DiagnosticSpec(message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]) + DiagnosticSpec(message: "consecutive statements on a line must be separated by newline or ';'", fixIts: ["insert newline", "insert ';'"]) ], + applyFixIts: ["insert newline"], + fixedSource: """ + let x = 2 + let y = 3 + """ + ) + } + + func testSimple2() { + assertParse( + "let x = 21️⃣ let y = 3", + diagnostics: [ + DiagnosticSpec(message: "consecutive statements on a line must be separated by newline or ';'", fixIts: ["insert newline", "insert ';'"]) + ], + applyFixIts: ["insert ';'"], fixedSource: "let x = 2; let y = 3" ) } @@ -45,7 +60,59 @@ final class ConsecutiveStatementsTests: XCTestCase { ) } - func testConsecutiveStatements2() { + func testConsecutiveStatements2a() { + assertParse( + """ + // Within a function + func test(i: inout Int, j: inout Int) { + // Okay + let q : Int; i = j; j = i; _ = q + if i != j { i = j } + // Errors + i = j1️⃣ j = i + let r : Int2️⃣ i = j + let s : Int3️⃣ let t : Int + _ = r; _ = s; _ = t + } + """, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "3️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + ], + applyFixIts: ["insert newline"], + fixedSource: """ + // Within a function + func test(i: inout Int, j: inout Int) { + // Okay + let q : Int; i = j; j = i; _ = q + if i != j { i = j } + // Errors + i = j + j = i + let r : Int + i = j + let s : Int + let t : Int + _ = r; _ = s; _ = t + } + """ + ) + } + + func testConsecutiveStatements2b() { assertParse( """ // Within a function @@ -61,10 +128,23 @@ final class ConsecutiveStatementsTests: XCTestCase { } """, diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), - DiagnosticSpec(locationMarker: "2️⃣", message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), - DiagnosticSpec(locationMarker: "3️⃣", message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "3️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), ], + applyFixIts: ["insert ';'"], fixedSource: """ // Within a function func test(i: inout Int, j: inout Int) { @@ -81,7 +161,7 @@ final class ConsecutiveStatementsTests: XCTestCase { ) } - func testConsecutiveStatements3() { + func testConsecutiveStatements3a() { assertParse( """ struct X { @@ -102,12 +182,107 @@ final class ConsecutiveStatementsTests: XCTestCase { } """, diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "consecutive declarations on a line must be separated by ';'", fixIts: ["insert ';'"]), - DiagnosticSpec(locationMarker: "2️⃣", message: "consecutive declarations on a line must be separated by ';'", fixIts: ["insert ';'"]), - DiagnosticSpec(locationMarker: "3️⃣", message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), - DiagnosticSpec(locationMarker: "4️⃣", message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), - DiagnosticSpec(locationMarker: "5️⃣", message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive declarations on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive declarations on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "3️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "4️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "5️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), ], + applyFixIts: ["insert newline"], + fixedSource: """ + struct X { + // In a sequence of declarations. + var a, b : Int + func d() -> Int {} + var prop : Int { return 4 + } + var other : Float + // Within property accessors + subscript(i: Int) -> Float { + get { + var x = i + x = i + x + return Float(x) + } + set { + var x = i + x = i + 1 + _ = x + } + } + } + """ + ) + } + + func testConsecutiveStatements3b() { + assertParse( + """ + struct X { + // In a sequence of declarations. + var a, b : Int1️⃣ func d() -> Int {} + var prop : Int { return 4 + }2️⃣ var other : Float + // Within property accessors + subscript(i: Int) -> Float { + get { + var x = i3️⃣ x = i + x4️⃣ return Float(x) + } + set { + var x = i5️⃣ x = i + 1 + _ = x + } + } + } + """, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive declarations on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive declarations on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "3️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "4️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "5️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + ], + applyFixIts: ["insert ';'"], fixedSource: """ struct X { // In a sequence of declarations. @@ -129,7 +304,37 @@ final class ConsecutiveStatementsTests: XCTestCase { ) } - func testConsecutiveStatements4() { + func testConsecutiveStatements4a() { + assertParse( + """ + class C { + // In a sequence of declarations. + var a, b : Int1️⃣ func d() -> Int {} + init() { + a = 0 + b = 0 + } + } + """, + diagnostics: [ + DiagnosticSpec(message: "consecutive declarations on a line must be separated by newline or ';'", fixIts: ["insert newline", "insert ';'"]) + ], + applyFixIts: ["insert newline"], + fixedSource: """ + class C { + // In a sequence of declarations. + var a, b : Int + func d() -> Int {} + init() { + a = 0 + b = 0 + } + } + """ + ) + } + + func testConsecutiveStatements4b() { assertParse( """ class C { @@ -142,8 +347,9 @@ final class ConsecutiveStatementsTests: XCTestCase { } """, diagnostics: [ - DiagnosticSpec(message: "consecutive declarations on a line must be separated by ';'", fixIts: ["insert ';'"]) + DiagnosticSpec(message: "consecutive declarations on a line must be separated by newline or ';'", fixIts: ["insert newline", "insert ';'"]) ], + applyFixIts: ["insert ';'"], fixedSource: """ class C { // In a sequence of declarations. @@ -157,7 +363,27 @@ final class ConsecutiveStatementsTests: XCTestCase { ) } - func testConsecutiveStatements5() { + func testConsecutiveStatements5a() { + assertParse( + """ + protocol P { + func a()1️⃣ func b() + } + """, + diagnostics: [ + DiagnosticSpec(message: "consecutive declarations on a line must be separated by newline or ';'", fixIts: ["insert newline", "insert ';'"]) + ], + applyFixIts: ["insert newline"], + fixedSource: """ + protocol P { + func a() + func b() + } + """ + ) + } + + func testConsecutiveStatements5b() { assertParse( """ protocol P { @@ -165,8 +391,9 @@ final class ConsecutiveStatementsTests: XCTestCase { } """, diagnostics: [ - DiagnosticSpec(message: "consecutive declarations on a line must be separated by ';'", fixIts: ["insert ';'"]) + DiagnosticSpec(message: "consecutive declarations on a line must be separated by newline or ';'", fixIts: ["insert newline", "insert ';'"]) ], + applyFixIts: ["insert ';'"], fixedSource: """ protocol P { func a(); func b() @@ -175,7 +402,7 @@ final class ConsecutiveStatementsTests: XCTestCase { ) } - func testConsecutiveStatements6() { + func testConsecutiveStatements6a() { assertParse( """ enum Color { @@ -184,9 +411,50 @@ final class ConsecutiveStatementsTests: XCTestCase { } """, diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "consecutive declarations on a line must be separated by ';'", fixIts: ["insert ';'"]), - DiagnosticSpec(locationMarker: "2️⃣", message: "consecutive declarations on a line must be separated by ';'", fixIts: ["insert ';'"]), + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive declarations on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive declarations on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), ], + applyFixIts: ["insert newline"], + fixedSource: """ + enum Color { + case Red + case Blue + func a() {} + func b() {} + } + """ + ) + } + + func testConsecutiveStatements6b() { + assertParse( + """ + enum Color { + case Red1️⃣ case Blue + func a() {}2️⃣ func b() {} + } + """, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive declarations on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive declarations on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + ], + applyFixIts: ["insert ';'"], fixedSource: """ enum Color { case Red; case Blue @@ -196,6 +464,34 @@ final class ConsecutiveStatementsTests: XCTestCase { ) } + func testConsecutiveStatements7a() { + assertParse( + """ + // At the top level + var i, j : Int1️⃣ i = j2️⃣ j = i + """, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + ], + applyFixIts: ["insert newline"], + fixedSource: """ + // At the top level + var i, j : Int + i = j + j = i + """ + ) + } + func testConsecutiveStatements7() { assertParse( """ @@ -203,13 +499,46 @@ final class ConsecutiveStatementsTests: XCTestCase { var i, j : Int1️⃣ i = j2️⃣ j = i """, diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), - DiagnosticSpec(locationMarker: "2️⃣", message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), ], + applyFixIts: ["insert ';'"], fixedSource: """ // At the top level var i, j : Int; i = j; j = i """ ) } + + func testConsecutiveStatements8() { + assertParse( + """ + class Foo { + func a() {}1️⃣/* some comment */ func b() {} + } + """, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive declarations on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ) + ], + applyFixIts: ["insert newline"], + fixedSource: """ + class Foo { + func a() {}/* some comment */ + func b() {} + } + """ + ) + } } diff --git a/Tests/SwiftParserTest/translated/ForwardSlashRegexSkippingInvalidTests.swift b/Tests/SwiftParserTest/translated/ForwardSlashRegexSkippingInvalidTests.swift index 3b7335df54e..506b1464c71 100644 --- a/Tests/SwiftParserTest/translated/ForwardSlashRegexSkippingInvalidTests.swift +++ b/Tests/SwiftParserTest/translated/ForwardSlashRegexSkippingInvalidTests.swift @@ -133,7 +133,7 @@ final class ForwardSlashRegexSkippingInvalidTests: XCTestCase { ) } - func testForwardSlashRegexSkippingInvalid8() { + func testForwardSlashRegexSkippingInvalid8a() { assertParse( #""" func i() { @@ -143,8 +143,8 @@ final class ForwardSlashRegexSkippingInvalidTests: XCTestCase { diagnostics: [ DiagnosticSpec( locationMarker: "1️⃣", - message: "consecutive statements on a line must be separated by ';'", - fixIts: ["insert ';'"] + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] ), DiagnosticSpec( locationMarker: "2️⃣", @@ -153,6 +153,37 @@ final class ForwardSlashRegexSkippingInvalidTests: XCTestCase { fixIts: [#"insert '"'"#] ), ], + applyFixIts: ["insert newline", #"insert '"'"#], + fixedSource: #""" + func i() { + _ = /x + "[abc] {" + } + """# + ) + } + + func testForwardSlashRegexSkippingInvalid8b() { + assertParse( + #""" + func i() { + _ = /x1️⃣ ℹ️"[abc] {2️⃣ + } + """#, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: #"expected '"' to end string literal"#, + notes: [NoteSpec(message: #"to match this opening '"'"#)], + fixIts: [#"insert '"'"#] + ), + ], + applyFixIts: ["insert ';'", #"insert '"'"#], fixedSource: #""" func i() { _ = /x; "[abc] {" diff --git a/Tests/SwiftParserTest/translated/ForwardSlashRegexTests.swift b/Tests/SwiftParserTest/translated/ForwardSlashRegexTests.swift index f71ea9046e8..c5a636adc18 100644 --- a/Tests/SwiftParserTest/translated/ForwardSlashRegexTests.swift +++ b/Tests/SwiftParserTest/translated/ForwardSlashRegexTests.swift @@ -405,7 +405,7 @@ final class ForwardSlashRegexTests: XCTestCase { ) } - func testForwardSlashRegex45() { + func testForwardSlashRegex45a() { // This is parsed as /x /... assertParse( """ @@ -414,10 +414,42 @@ final class ForwardSlashRegexTests: XCTestCase { } """, diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec(locationMarker: "2️⃣", message: "expected expression in operator", fixIts: ["insert expression"]), + DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected code '...' in 'do' statement"), + ], + applyFixIts: ["insert newline", "insert expression"], + fixedSource: """ + do { + _ = /x + /<#expression#>... + } + """ + ) + } + + func testForwardSlashRegex45b() { + // This is parsed as /x /... + assertParse( + """ + do { + _ = /x1️⃣ /2️⃣... + } + """, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), DiagnosticSpec(locationMarker: "2️⃣", message: "expected expression in operator", fixIts: ["insert expression"]), DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected code '...' in 'do' statement"), ], + applyFixIts: ["insert ';'", "insert expression"], fixedSource: """ do { _ = /x; /<#expression#>... @@ -1469,7 +1501,7 @@ final class ForwardSlashRegexTests: XCTestCase { ) } - func testForwardSlashRegex150() { + func testForwardSlashRegex150a() { assertParse( #""" _ = ^/"/1️⃣"2️⃣ @@ -1477,8 +1509,8 @@ final class ForwardSlashRegexTests: XCTestCase { diagnostics: [ DiagnosticSpec( locationMarker: "1️⃣", - message: "consecutive statements on a line must be separated by ';'", - fixIts: ["insert ';'"] + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] ), DiagnosticSpec( locationMarker: "2️⃣", @@ -1487,13 +1519,40 @@ final class ForwardSlashRegexTests: XCTestCase { fixIts: [#"insert '"'"#] ), ], + applyFixIts: ["insert newline", #"insert '"'"#], + fixedSource: #""" + _ = ^/"/ + "" + """# + ) + } + + func testForwardSlashRegex150b() { + assertParse( + #""" + _ = ^/"/1️⃣"2️⃣ + """#, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: #"expected '"' to end string literal"#, + notes: [NoteSpec(locationMarker: "1️⃣", message: #"to match this opening '"'"#)], + fixIts: [#"insert '"'"#] + ), + ], + applyFixIts: ["insert ';'", #"insert '"'"#], fixedSource: #""" _ = ^/"/; "" """# ) } - func testForwardSlashRegex151() { + func testForwardSlashRegex151a() { assertParse( #""" _ = ^/"[/1️⃣"2️⃣ @@ -1501,17 +1560,43 @@ final class ForwardSlashRegexTests: XCTestCase { diagnostics: [ DiagnosticSpec( locationMarker: "1️⃣", - message: "consecutive statements on a line must be separated by ';'", - fixIts: ["insert ';'"] + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] ), DiagnosticSpec( locationMarker: "2️⃣", message: #"expected '"' to end string literal"#, notes: [NoteSpec(locationMarker: "1️⃣", message: #"to match this opening '"'"#)], + fixIts: [#"insert '"'"#] + ), + ], + applyFixIts: ["insert newline", #"insert '"'"#], + fixedSource: #""" + _ = ^/"[/ + "" + """# + ) + } + func testForwardSlashRegex151b() { + assertParse( + #""" + _ = ^/"[/1️⃣"2️⃣ + """#, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: #"expected '"' to end string literal"#, + notes: [NoteSpec(locationMarker: "1️⃣", message: #"to match this opening '"'"#)], fixIts: [#"insert '"'"#] ), ], + applyFixIts: ["insert ';'", #"insert '"'"#], fixedSource: #""" _ = ^/"[/; "" """# diff --git a/Tests/SwiftParserTest/translated/InvalidTests.swift b/Tests/SwiftParserTest/translated/InvalidTests.swift index e94655871e4..50a423fb926 100644 --- a/Tests/SwiftParserTest/translated/InvalidTests.swift +++ b/Tests/SwiftParserTest/translated/InvalidTests.swift @@ -213,7 +213,11 @@ final class InvalidTests: XCTestCase { """#, diagnostics: [ DiagnosticSpec(locationMarker: "1️⃣", message: "all statements inside a switch must be covered by a 'case' or 'default' label", fixIts: ["insert label"]), - DiagnosticSpec(locationMarker: "2️⃣", message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), ], fixedSource: #""" func testNotCoveredCase(x: Int) { @@ -222,7 +226,8 @@ final class InvalidTests: XCTestCase { let y = "foo" switch y { case "bar": - blah; blah // ignored + blah + blah // ignored } case "baz": break diff --git a/Tests/SwiftParserTest/translated/OperatorsTests.swift b/Tests/SwiftParserTest/translated/OperatorsTests.swift index 4a7a8f62ab2..42cbd8cab3a 100644 --- a/Tests/SwiftParserTest/translated/OperatorsTests.swift +++ b/Tests/SwiftParserTest/translated/OperatorsTests.swift @@ -331,28 +331,71 @@ final class OperatorsTests: XCTestCase { ) } - func testOperators34() { + func testOperators34a() { assertParse( """ foo!!1️⃣foo """, diagnostics: [ - DiagnosticSpec(message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]) + DiagnosticSpec(message: "consecutive statements on a line must be separated by newline or ';'", fixIts: ["insert newline", "insert ';'"]) ], + applyFixIts: ["insert newline"], + fixedSource: """ + foo!! + foo + """ + ) + } + + func testOperators34b() { + assertParse( + """ + foo!!1️⃣foo + """, + diagnostics: [ + DiagnosticSpec( + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ) + ], + applyFixIts: ["insert ';'"], fixedSource: """ foo!!; foo """ ) } - func testOperators35() { + func testOperators35a() { + assertParse( + """ + foo??1️⃣bar + """, + diagnostics: [ + DiagnosticSpec( + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ) + ], + applyFixIts: ["insert newline"], + fixedSource: """ + foo?? + bar + """ + ) + } + + func testOperators35b() { assertParse( """ foo??1️⃣bar """, diagnostics: [ - DiagnosticSpec(message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]) + DiagnosticSpec( + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ) ], + applyFixIts: ["insert ';'"], fixedSource: """ foo??; bar """ diff --git a/Tests/SwiftParserTest/translated/PoundAssertTests.swift b/Tests/SwiftParserTest/translated/PoundAssertTests.swift index 7bc6d95899c..26169743645 100644 --- a/Tests/SwiftParserTest/translated/PoundAssertTests.swift +++ b/Tests/SwiftParserTest/translated/PoundAssertTests.swift @@ -31,15 +31,33 @@ final class PoundAssertTests: XCTestCase { ) } - func testPoundAssert3() { + func testPoundAssert3a() { assertParse( #""" #assert1️⃣ true2️⃣, "error message") """#, diagnostics: [ - DiagnosticSpec(message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), + DiagnosticSpec(message: "consecutive statements on a line must be separated by newline or ';'", fixIts: ["insert newline", "insert ';'"]), DiagnosticSpec(locationMarker: "2️⃣", message: #"extraneous code ', "error message")' at top level"#), ], + applyFixIts: ["insert newline"], + fixedSource: #""" + #assert + true, "error message") + """# + ) + } + + func testPoundAssert3b() { + assertParse( + #""" + #assert1️⃣ true2️⃣, "error message") + """#, + diagnostics: [ + DiagnosticSpec(message: "consecutive statements on a line must be separated by newline or ';'", fixIts: ["insert newline", "insert ';'"]), + DiagnosticSpec(locationMarker: "2️⃣", message: #"extraneous code ', "error message")' at top level"#), + ], + applyFixIts: ["insert ';'"], fixedSource: #""" #assert; true, "error message") """# diff --git a/Tests/SwiftParserTest/translated/RecoveryTests.swift b/Tests/SwiftParserTest/translated/RecoveryTests.swift index be919f619c8..62595356355 100644 --- a/Tests/SwiftParserTest/translated/RecoveryTests.swift +++ b/Tests/SwiftParserTest/translated/RecoveryTests.swift @@ -43,7 +43,7 @@ final class RecoveryTests: XCTestCase { ) } - func testRecovery7() { + func testRecovery7a() { assertParse( """ func useContainer() -> () { @@ -61,8 +61,8 @@ final class RecoveryTests: XCTestCase { ), DiagnosticSpec( locationMarker: "2️⃣", - message: "consecutive statements on a line must be separated by ';'", - fixIts: ["insert ';'"] + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] ), DiagnosticSpec( locationMarker: "3️⃣", @@ -78,6 +78,54 @@ final class RecoveryTests: XCTestCase { message: "unexpected code in function" ), ], + applyFixIts: ["insert '>'", "insert newline", "insert expression"], + fixedSource: """ + func useContainer() -> () { + var a : Containera + type [skip this greater: >] > <#expression#>, b : Int + b = 5 // no-warning + a.exists() + } + """ + ) + } + + func testRecovery7b() { + assertParse( + """ + func useContainer() -> () { + var a : Containerℹ️] >4️⃣, b : Int + b = 5 // no-warning + a.exists() + } + """, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "expected '>' to end generic argument clause", + notes: [NoteSpec(message: "to match this opening '<'")], + fixIts: ["insert '>'"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "3️⃣", + message: "unexpected code 'this greater: >' in subscript" + ), + DiagnosticSpec( + locationMarker: "4️⃣", + message: "expected expression after operator", + fixIts: ["insert expression"] + ), + DiagnosticSpec( + locationMarker: "4️⃣", + message: "unexpected code in function" + ), + ], + applyFixIts: ["insert '>'", "insert ';'", "insert expression"], fixedSource: """ func useContainer() -> () { var a : Containera; type [skip this greater: >] > <#expression#>, b : Int @@ -108,7 +156,41 @@ final class RecoveryTests: XCTestCase { ) } - func testRecovery10() { + func testRecovery10a() { + assertParse( + """ + // Here is an extra random close-brace! + 1️⃣} + //===--- Recovery for braced blocks. + func braceStmt1() { + { braceStmt1(); }2️⃣a + } + """, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "unexpected brace before function" + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + ], + applyFixIts: ["insert newline"], + fixedSource: """ + // Here is an extra random close-brace! + } + //===--- Recovery for braced blocks. + func braceStmt1() { + { braceStmt1(); } + a + } + """ + ) + } + + func testRecovery10b() { assertParse( """ // Here is an extra random close-brace! @@ -119,9 +201,17 @@ final class RecoveryTests: XCTestCase { } """, diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected brace before function"), - DiagnosticSpec(locationMarker: "2️⃣", message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), + DiagnosticSpec( + locationMarker: "1️⃣", + message: "unexpected brace before function" + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), ], + applyFixIts: ["insert ';'"], fixedSource: """ // Here is an extra random close-brace! } @@ -785,7 +875,7 @@ final class RecoveryTests: XCTestCase { ) } - func testRecovery51() { + func testRecovery51a() { assertParse( """ switch 1️⃣{ 2️⃣42 }()3️⃣ { @@ -793,11 +883,64 @@ final class RecoveryTests: XCTestCase { } """, diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "expected expression in 'switch' statement"), - DiagnosticSpec(locationMarker: "2️⃣", message: "all statements inside a switch must be covered by a 'case' or 'default' label", fixIts: ["insert label"]), - DiagnosticSpec(locationMarker: "3️⃣", message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), - DiagnosticSpec(locationMarker: "4️⃣", message: "'case' can only appear inside a 'switch' statement or 'enum' declaration"), + DiagnosticSpec( + locationMarker: "1️⃣", + message: "expected expression in 'switch' statement" + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "all statements inside a switch must be covered by a 'case' or 'default' label", + fixIts: ["insert label"] + ), + DiagnosticSpec( + locationMarker: "3️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "4️⃣", + message: "'case' can only appear inside a 'switch' statement or 'enum' declaration" + ), + ], + applyFixIts: ["insert label", "insert newline"], + fixedSource: """ + switch { + case <#identifier#>: 42 }() + { + case _: return + } + """ + ) + } + + func testRecovery51b() { + assertParse( + """ + switch 1️⃣{ 2️⃣42 }()3️⃣ { + 4️⃣case _: return + } + """, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "expected expression in 'switch' statement" + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "all statements inside a switch must be covered by a 'case' or 'default' label", + fixIts: ["insert label"] + ), + DiagnosticSpec( + locationMarker: "3️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "4️⃣", + message: "'case' can only appear inside a 'switch' statement or 'enum' declaration" + ), ], + applyFixIts: ["insert label", "insert ';'"], fixedSource: """ switch { case <#identifier#>: 42 }(); { @@ -938,7 +1081,39 @@ final class RecoveryTests: XCTestCase { ) } - func testRecovery62() { + func testRecovery62a() { + assertParse( + """ + enum EE 1️⃣EE where T : Multi { + case a2️⃣ 3️⃣a + case b + } + """, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "found an unexpected second identifier in enum; is there an accidental break?", + fixIts: ["join the identifiers together"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive declarations on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec(locationMarker: "3️⃣", message: "unexpected code 'a' before enum case"), + ], + applyFixIts: ["join the identifiers together", "insert newline"], + fixedSource: """ + enum EEEE where T : Multi { + case a + a + case b + } + """ + ) + } + + func testRecovery62b() { assertParse( """ enum EE 1️⃣EE where T : Multi { @@ -954,11 +1129,12 @@ final class RecoveryTests: XCTestCase { ), DiagnosticSpec( locationMarker: "2️⃣", - message: "consecutive declarations on a line must be separated by ';'", - fixIts: ["insert ';'"] + message: "consecutive declarations on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] ), DiagnosticSpec(locationMarker: "3️⃣", message: "unexpected code 'a' before enum case"), ], + applyFixIts: ["join the identifiers together", "insert ';'"], fixedSource: """ enum EEEE where T : Multi { case a;a @@ -1565,8 +1741,8 @@ final class RecoveryTests: XCTestCase { diagnostics: [ DiagnosticSpec( locationMarker: "1️⃣", - message: "consecutive declarations on a line must be separated by ';'", - fixIts: ["insert ';'"] + message: "consecutive declarations on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] ), DiagnosticSpec( locationMarker: "1️⃣", @@ -1605,7 +1781,8 @@ final class RecoveryTests: XCTestCase { ], fixedSource: """ struct ErrorTypeInVarDeclDictionaryType { - let a1: String;: + let a1: String + : let a2: [String: Int] let a3: [String: [Int]] let a4: [String: Int] @@ -2708,7 +2885,44 @@ final class RecoveryTests: XCTestCase { ) } - func testRecovery170() { + func testRecovery170a() { + // QoI: terrible recovery when using '·' for an operator + assertParse( + """ + infix operator 1️⃣· 2️⃣{ + associativity none precedence3️⃣ 150 + 4️⃣} + """, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "'·' is considered an identifier and must not appear within an operator name" + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "operator should not be declared with body", + fixIts: ["remove operator body"] + ), + DiagnosticSpec( + locationMarker: "3️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "4️⃣", + message: "extraneous brace at top level" + ), + ], + applyFixIts: ["remove operator body", "insert newline"], + fixedSource: """ + infix operator · precedence + 150 + } + """ + ) + } + + func testRecovery170b() { // QoI: terrible recovery when using '·' for an operator assertParse( """ @@ -2717,11 +2931,26 @@ final class RecoveryTests: XCTestCase { 4️⃣} """, diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "'·' is considered an identifier and must not appear within an operator name"), - DiagnosticSpec(locationMarker: "2️⃣", message: "operator should not be declared with body", fixIts: ["remove operator body"]), - DiagnosticSpec(locationMarker: "3️⃣", message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), - DiagnosticSpec(locationMarker: "4️⃣", message: "extraneous brace at top level"), + DiagnosticSpec( + locationMarker: "1️⃣", + message: "'·' is considered an identifier and must not appear within an operator name" + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "operator should not be declared with body", + fixIts: ["remove operator body"] + ), + DiagnosticSpec( + locationMarker: "3️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "4️⃣", + message: "extraneous brace at top level" + ), ], + applyFixIts: ["remove operator body", "insert ';'"], fixedSource: """ infix operator · precedence; 150 } diff --git a/Tests/SwiftParserTest/translated/TrailingClosuresTests.swift b/Tests/SwiftParserTest/translated/TrailingClosuresTests.swift index 19074f89137..7b1ff780eea 100644 --- a/Tests/SwiftParserTest/translated/TrailingClosuresTests.swift +++ b/Tests/SwiftParserTest/translated/TrailingClosuresTests.swift @@ -199,7 +199,7 @@ final class TrailingClosuresTests: XCTestCase { ) } - func testTrailingClosures14() { + func testTrailingClosures14a() { // TODO: The diagnostics here are perhaps a little overboard. assertParse( """ @@ -210,9 +210,44 @@ final class TrailingClosuresTests: XCTestCase { _ = produce { 2 } `default`: { 3 } """, diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]), + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), DiagnosticSpec(locationMarker: "2️⃣", message: "'default' label can only appear inside a 'switch' statement"), ], + applyFixIts: ["insert newline"], + fixedSource: """ + func produce(fn: () -> Int?, default d: () -> Int) -> Int { + return fn() ?? d() + } + _ = produce { 0 } + default: { 1 } + _ = produce { 2 } `default`: { 3 } + """ + ) + } + + func testTrailingClosures14b() { + // TODO: The diagnostics here are perhaps a little overboard. + assertParse( + """ + func produce(fn: () -> Int?, default d: () -> Int) -> Int { + return fn() ?? d() + } + _ = produce { 0 }1️⃣ 2️⃣default: { 1 } + _ = produce { 2 } `default`: { 3 } + """, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec(locationMarker: "2️⃣", message: "'default' label can only appear inside a 'switch' statement"), + ], + applyFixIts: ["insert ';'"], fixedSource: """ func produce(fn: () -> Int?, default d: () -> Int) -> Int { return fn() ?? d() diff --git a/Tests/SwiftParserTest/translated/TrailingSemiTests.swift b/Tests/SwiftParserTest/translated/TrailingSemiTests.swift index 90fa62dd45d..2c920a92456 100644 --- a/Tests/SwiftParserTest/translated/TrailingSemiTests.swift +++ b/Tests/SwiftParserTest/translated/TrailingSemiTests.swift @@ -56,7 +56,7 @@ final class TrailingSemiTests: XCTestCase { ) } - func testTrailingSemi3() { + func testTrailingSemi3a() { assertParse( """ class C { @@ -73,11 +73,28 @@ final class TrailingSemiTests: XCTestCase { } """, diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "consecutive declarations on a line must be separated by ';'", fixIts: ["insert ';'"]), - DiagnosticSpec(locationMarker: "2️⃣", message: "consecutive declarations on a line must be separated by ';'", fixIts: ["insert ';'"]), - DiagnosticSpec(locationMarker: "3️⃣", message: "consecutive declarations on a line must be separated by ';'", fixIts: ["insert ';'"]), - DiagnosticSpec(locationMarker: "4️⃣", message: "consecutive declarations on a line must be separated by ';'", fixIts: ["insert ';'"]), + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive declarations on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive declarations on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "3️⃣", + message: "consecutive declarations on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "4️⃣", + message: "consecutive declarations on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), ], + applyFixIts: ["insert ';'"], fixedSource: """ class C { var a : Int = 10; func aa() {}; @@ -95,7 +112,67 @@ final class TrailingSemiTests: XCTestCase { ) } - func testTrailingSemi4() { + func testTrailingSemi3b() { + assertParse( + """ + class C { + var a : Int = 101️⃣ func aa() {}; + #if FLAG1 + var aaa: Int = 422️⃣ func aaaa() {}; + #elseif FLAG2 + var aaa: Int = 423️⃣ func aaaa() {} + #else + var aaa: Int = 424️⃣ func aaaa() {} + #endif + func b () {}; + class func c () {}; + } + """, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive declarations on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive declarations on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "3️⃣", + message: "consecutive declarations on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec( + locationMarker: "4️⃣", + message: "consecutive declarations on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + ], + applyFixIts: ["insert newline"], + fixedSource: """ + class C { + var a : Int = 10 + func aa() {}; + #if FLAG1 + var aaa: Int = 42 + func aaaa() {}; + #elseif FLAG2 + var aaa: Int = 42 + func aaaa() {} + #else + var aaa: Int = 42 + func aaaa() {} + #endif + func b () {}; + class func c () {}; + } + """ + ) + } + + func testTrailingSemi4a() { assertParse( """ extension S { @@ -106,8 +183,9 @@ final class TrailingSemiTests: XCTestCase { } """, diagnostics: [ - DiagnosticSpec(message: "consecutive declarations on a line must be separated by ';'", fixIts: ["insert ';'"]) + DiagnosticSpec(message: "consecutive declarations on a line must be separated by newline or ';'", fixIts: ["insert newline", "insert ';'"]) ], + applyFixIts: ["insert ';'"], fixedSource: """ extension S { //var a : Int ; @@ -119,6 +197,32 @@ final class TrailingSemiTests: XCTestCase { ) } + func testTrailingSemi4b() { + assertParse( + """ + extension S { + //var a : Int ; + func bb () {}; + static func cc () {}; + func dd() {}1️⃣ subscript(i: Int) -> Int { return 1 } + } + """, + diagnostics: [ + DiagnosticSpec(message: "consecutive declarations on a line must be separated by newline or ';'", fixIts: ["insert newline", "insert ';'"]) + ], + applyFixIts: ["insert newline"], + fixedSource: """ + extension S { + //var a : Int ; + func bb () {}; + static func cc () {}; + func dd() {} + subscript(i: Int) -> Int { return 1 } + } + """ + ) + } + func testTrailingSemi5() { assertParse( """ @@ -183,8 +287,9 @@ final class TrailingSemiTests: XCTestCase { } """, diagnostics: [ - DiagnosticSpec(message: "consecutive declarations on a line must be separated by ';'", fixIts: ["insert ';'"]) + DiagnosticSpec(message: "consecutive declarations on a line must be separated by newline or ';'", fixIts: ["insert newline", "insert ';'"]) ], + applyFixIts: ["insert ';'"], fixedSource: """ struct X { #if FLAG1 @@ -195,7 +300,58 @@ final class TrailingSemiTests: XCTestCase { ) } - func testMissingSemiInIfConfigOfStmts() { + func testMissingNewlineInIfConfigOfDecls() { + assertParse( + """ + struct X { + #if FLAG1 + func foo() {}1️⃣ func bar() {} + #endif + } + """, + diagnostics: [ + DiagnosticSpec(message: "consecutive declarations on a line must be separated by newline or ';'", fixIts: ["insert newline", "insert ';'"]) + ], + applyFixIts: ["insert newline"], + fixedSource: """ + struct X { + #if FLAG1 + func foo() {} + func bar() {} + #endif + } + """ + ) + } + + func testMissingSemiInIfConfigOfStmts1() { + assertParse( + """ + func foo() { + #if FLAG1 + let a = 11️⃣ let b = 2 + #endif + } + """, + diagnostics: [ + DiagnosticSpec( + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ) + ], + applyFixIts: ["insert newline"], + fixedSource: """ + func foo() { + #if FLAG1 + let a = 1 + let b = 2 + #endif + } + """ + ) + } + + func testMissingSemiInIfConfigOfStmts2() { assertParse( """ func foo() { @@ -205,8 +361,12 @@ final class TrailingSemiTests: XCTestCase { } """, diagnostics: [ - DiagnosticSpec(message: "consecutive statements on a line must be separated by ';'", fixIts: ["insert ';'"]) + DiagnosticSpec( + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ) ], + applyFixIts: ["insert ';'"], fixedSource: """ func foo() { #if FLAG1