From 264dbd5378dd8dc1e08f6d3917f9df50870feec4 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Fri, 26 Aug 2022 15:25:13 +0200 Subject: [PATCH 1/6] Allow skipping the self parse test by settinging an environment variable This allows me to run all other tests in a few seconds. --- Tests/SwiftParserTest/ParserTests.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Tests/SwiftParserTest/ParserTests.swift b/Tests/SwiftParserTest/ParserTests.swift index f83d315d890..bcb2022f876 100644 --- a/Tests/SwiftParserTest/ParserTests.swift +++ b/Tests/SwiftParserTest/ParserTests.swift @@ -4,6 +4,9 @@ import SwiftParser public class ParserTests: XCTestCase { func testSelfParse() throws { + // Allow skipping the self parse test in local development environments + // because it takes very long compared to all the other tests. + try XCTSkipIf(ProcessInfo.processInfo.environment["SKIP_SELF_PARSE"] == "1") let currentDir = URL(fileURLWithPath: #file) .deletingLastPathComponent() .deletingLastPathComponent() From 127a71b01dd27df18d1ac59f7bd8757348de3cbf Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Fri, 26 Aug 2022 16:35:14 +0200 Subject: [PATCH 2/6] Unify AssertParse and XCTAssert[Single]Diagnostic Update `AssertParse` to take the a list of expected diagnostic specifications that we are expected to produce. --- .../ParserDiagnosticMessages.swift | 2 +- .../SwiftParser.docc/FixingBugs.md | 29 +- .../LocationMarkers.swift | 64 +++++ .../Syntax+Assertions.swift | 49 +--- Tests/SwiftParserTest/Assertions.swift | 156 +++++++++-- Tests/SwiftParserTest/Availability.swift | 19 +- Tests/SwiftParserTest/Declarations.swift | 248 ++++++----------- .../DiagnosticAssertions.swift | 90 ------ Tests/SwiftParserTest/DiagnosticTests.swift | 159 ++++++----- Tests/SwiftParserTest/Directives.swift | 22 +- Tests/SwiftParserTest/Expressions.swift | 261 ++++++++---------- Tests/SwiftParserTest/RecoveryTests.swift | 244 +++++++++------- Tests/SwiftParserTest/Statements.swift | 88 +++--- Tests/SwiftParserTest/Types.swift | 21 +- 14 files changed, 719 insertions(+), 733 deletions(-) create mode 100644 Sources/_SwiftSyntaxTestSupport/LocationMarkers.swift delete mode 100644 Tests/SwiftParserTest/DiagnosticAssertions.swift diff --git a/Sources/SwiftParser/Diagnostics/ParserDiagnosticMessages.swift b/Sources/SwiftParser/Diagnostics/ParserDiagnosticMessages.swift index e1cccde67d0..c3945037110 100644 --- a/Sources/SwiftParser/Diagnostics/ParserDiagnosticMessages.swift +++ b/Sources/SwiftParser/Diagnostics/ParserDiagnosticMessages.swift @@ -90,7 +90,7 @@ public enum StaticParserError: String, DiagnosticMessage { } public enum StaticParserFixIt: String, FixItMessage { - case moveThrowBeforeArrow = "Move 'throws' in before of '->'" + case moveThrowBeforeArrow = "Move 'throws' before '->'" public var message: String { self.rawValue } diff --git a/Sources/SwiftParser/SwiftParser.docc/FixingBugs.md b/Sources/SwiftParser/SwiftParser.docc/FixingBugs.md index 71dde081d9d..51a632996e1 100644 --- a/Sources/SwiftParser/SwiftParser.docc/FixingBugs.md +++ b/Sources/SwiftParser/SwiftParser.docc/FixingBugs.md @@ -10,14 +10,13 @@ Once you’ve written a test case (see below), set a breakpoint in `Parser.parse 1. Add a new test case in `SwiftParserTest` that looks like the following ```swift - try AssertParse({ $0.parseSourceFile() }) { + AssertParse( """ <#your code that does not round trip#> """ - } + ) ``` 2. Run the test case, read the error message to figure out which part of the source file does not round-trip -3. Optional: Reduce the test case even further by deleting more source code and calling into a specific production of the parser instead of `Parser.parseSourceFile` ## Parse of Valid Source Failed @@ -51,7 +50,7 @@ Unhelpful diagnostics can result from two reasons: To distinguish these cases run the following command and look at the dumped syntax tree. Use your own judgment to decide whether this models the intended meaning of the source code reasonably well. ``` -swift-parser-test print-tree /path/to/file/with/bad/diagnostic +swift-parser-test print-tree /path/to/file/with/unhelpful/diagnostic.swift ``` Fixing the first case where the parser does not recover according to the user’s intent is similar to [Parse of Valid Source Code Produced an Invalid Syntax Tree](#Parse-of-Valid-Source-Code-Produced-an-Invalid-Syntax-Tree). See for documentation how parser recovery works and determine how to recover better from the invalid source code. @@ -61,21 +60,19 @@ To add a new, more contextual diagnostic, perform the following steps. 1. Add a test case to `DiagnosticTests.swift` like the following: ```swift - let source = """ - <#your code that produces a bad diagnostic#> - } - """ - let loop = withParser(source: source) { - Syntax(raw: $0.parserSourceFile().raw) - } + AssertParse( + """ + <#your code that produced the unhelpful diagnostic#> + """, + diagnostics: [ + DiagnosticSpec(message: "<#expected diagnostic message#>") + ] + ) ``` -2. Optional: Call a more specific production than `parseSourceFile` in the test case. +2. Mark the location at which you expect the diagnostic to be produced with `#^DIAG^#`. If you expect multiple diagnostics to be produced, you can use multiple of these markers with different names and use these markers by passing a `locationMarker` to `DiagnosticSpec`. 3. Determine which node encompasses all information that is necessary to produce the improved diagnostic – for example `FunctionSignatureSyntax` contains all information to diagnose if the `throws` keyword was written after the `->` instead of in front of it. 4. If the diagnostic message you want to emit does not exist yet, add a case to for the new diagnostic. 5. If the function does not already exist, write a new visit method on . 6. In that visitation method, detect the pattern for which the improved diagnostic should be emitted and emit it using `diagnostics.append`. 7. Mark the missing or garbage nodes that are covered by the new diagnostic as handled by adding their `SyntaxIdentifier`s to `handledNodes`. -8. Assert that the new diagnostic is emitted by addding the following to your test case: - ```swift - XCTAssertSingleDiagnostic(in: tree, line: <#expected line#>, column: <#expected column#>, expectedKind: .<#expected diagnostic kind#>) - ``` +8. If the diagnostic produces Fix-Its assert that they are generated by adding the Fix-It's message to the `DiagnosticSpec` with the `fixIt` parameter and asserting that applying the Fix-Its produces the correct source code by adding the `fixedSource` parameter to `AssertParse`. diff --git a/Sources/_SwiftSyntaxTestSupport/LocationMarkers.swift b/Sources/_SwiftSyntaxTestSupport/LocationMarkers.swift new file mode 100644 index 00000000000..29f53fdb27f --- /dev/null +++ b/Sources/_SwiftSyntaxTestSupport/LocationMarkers.swift @@ -0,0 +1,64 @@ +//===--- LocationMarkers.swift --------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +/// Finds all marked ranges in the given text, see `Marker`. +fileprivate func findMarkedRanges(text: String) -> [Marker] { + var markers = [Marker]() + while let marker = nextMarkedRange(text: text, from: markers.last?.range.upperBound ?? text.startIndex) { + markers.append(marker) + } + return markers +} + +fileprivate func nextMarkedRange(text: String, from: String.Index) -> Marker? { + guard let start = text.range(of: "#^", range: from ..< text.endIndex), + let end = text.range(of: "^#", range: start.upperBound ..< text.endIndex) else { + return nil + } + + let markerRange = start.lowerBound ..< end.upperBound + let name = text[start.upperBound ..< end.lowerBound] + + // Expand to the whole line if the line only contains the marker + let lineRange = text.lineRange(for: start) + if text[lineRange].trimmingCharacters(in: .whitespacesAndNewlines) == text[markerRange] { + return Marker(name: name, range: lineRange) + } + return Marker(name: name, range: markerRange) +} + +fileprivate struct Marker { + /// The name of the marker without the `#^` and `^#` markup. + let name: Substring + /// The range of the marker. + /// + /// If the marker contains all the the non-whitepace characters on the line, + /// this is the range of the entire line. Otherwise it's the range of the + /// marker itself, including the `#^` and `^#` markup. + let range: Range +} + +public func extractMarkers(_ markedText: String) -> (markers: [String: Int], textWithoutMarkers: String) { + var text = "" + var markers = [String: Int]() + var lastIndex = markedText.startIndex + for marker in findMarkedRanges(text: markedText) { + text += markedText[lastIndex ..< marker.range.lowerBound] + lastIndex = marker.range.upperBound + + assert(markers[String(marker.name)] == nil, "Marker names must be unique") + markers[String(marker.name)] = text.utf8.count + } + text += markedText[lastIndex ..< markedText.endIndex] + + return (markers, text) +} diff --git a/Sources/_SwiftSyntaxTestSupport/Syntax+Assertions.swift b/Sources/_SwiftSyntaxTestSupport/Syntax+Assertions.swift index bf21d1f97b4..e9b9c99aecc 100644 --- a/Sources/_SwiftSyntaxTestSupport/Syntax+Assertions.swift +++ b/Sources/_SwiftSyntaxTestSupport/Syntax+Assertions.swift @@ -135,17 +135,7 @@ public struct SubtreeMatcher { private var actualTree: Syntax public init(_ markedText: String, parse: (String) throws -> Syntax) throws { - var text = "" - var markers = [String: Int]() - var lastIndex = markedText.startIndex - for marker in findMarkedRanges(text: markedText) { - text += markedText[lastIndex ..< marker.range.lowerBound] - lastIndex = marker.range.upperBound - - assert(markers[String(marker.name)] == nil, "Marker names must be unique") - markers[String(marker.name)] = text.utf8.count - } - text += markedText[lastIndex ..< markedText.endIndex] + let (markers, text) = extractMarkers(markedText) self.markers = markers.isEmpty ? ["DEFAULT": 0] : markers self.actualTree = try parse(text) @@ -195,43 +185,6 @@ public enum SubtreeError: Error, CustomStringConvertible { } } -/// Finds all marked ranges in the given text, see `Marker`. -fileprivate func findMarkedRanges(text: String) -> [Marker] { - var markers = [Marker]() - while let marker = nextMarkedRange(text: text, from: markers.last?.range.upperBound ?? text.startIndex) { - markers.append(marker) - } - return markers -} - -fileprivate func nextMarkedRange(text: String, from: String.Index) -> Marker? { - guard let start = text.range(of: "#^", range: from ..< text.endIndex), - let end = text.range(of: "^#", range: start.upperBound ..< text.endIndex) else { - return nil - } - - let markerRange = start.lowerBound ..< end.upperBound - let name = text[start.upperBound ..< end.lowerBound] - - // Expand to the whole line if the line only contains the marker - let lineRange = text.lineRange(for: start) - if text[lineRange].trimmingCharacters(in: .whitespacesAndNewlines) == text[markerRange] { - return Marker(name: name, range: lineRange) - } - return Marker(name: name, range: markerRange) -} - -fileprivate struct Marker { - /// The name of the marker without the `#^` and `^#` markup. - let name: Substring - /// The range of the marker. - /// - /// If the marker contains all the the non-whitepace characters on the line, - /// this is the range of the entire line. Otherwise it's the range of the - /// marker itself, including the `#^` and `^#` markup. - let range: Range -} - fileprivate class SyntaxTypeFinder: SyntaxAnyVisitor { private let offset: Int private let type: SyntaxProtocol.Type diff --git a/Tests/SwiftParserTest/Assertions.swift b/Tests/SwiftParserTest/Assertions.swift index 42ea04bac8f..4629509457d 100644 --- a/Tests/SwiftParserTest/Assertions.swift +++ b/Tests/SwiftParserTest/Assertions.swift @@ -1,6 +1,8 @@ import XCTest @_spi(RawSyntax) import SwiftSyntax @_spi(Testing) @_spi(RawSyntax) import SwiftParser +import SwiftDiagnostics +import _SwiftSyntaxTestSupport // MARK: Lexing Assertions @@ -49,33 +51,147 @@ func AssertEqualTokens(_ actual: [Lexer.Lexeme], _ expected: [Lexer.Lexeme], fil // MARK: Parsing Assertions +/// An abstract data structure to describe how a diagnostic produced by the parser should look like. +struct DiagnosticSpec { + /// The name of a maker (of the form `#^DIAG^#`) in the source code that marks the location where the diagnostis should be produced. + let locationMarker: String + /// If not `nil`, assert that the diagnostic has the given ID. + let id: MessageID? + /// If not `nil`, assert that the diagnostic has the given message. + let message: String? + /// If not `nil`, assert that the highlighted range has this content. + let highlight: String? + /// If not `nil`, assert that the diagnostic contains fix-its with these messages. + /// Use the `fixedSource` parameter on `AssertParse` to check that applying the Fix-It yields the expected result. + let fixIts: [String]? + + init(locationMarker: String = "DIAG", id: MessageID? = nil, message: String?, highlight: String? = nil, fixIts: [String]? = nil) { + self.locationMarker = locationMarker + self.id = id + self.message = message + self.highlight = highlight + self.fixIts = fixIts + } +} + +class FixItApplier: SyntaxRewriter { + var changes: [FixIt.Change] + + init(diagnostics: [Diagnostic]) { + self.changes = diagnostics.flatMap({ $0.fixIts }).flatMap({ $0.changes }) + } + + public override func visitAny(_ node: Syntax) -> Syntax? { + for change in changes { + switch change { + case .replace(oldNode: let oldNode, newNode: let newNode) where oldNode.id == node.id: + return newNode + default: + break + } + } + return nil + } + + /// Applies all Fix-Its in `diagnostics` to `tree` and returns the fixed syntax tree. + public static func applyFixes(in diagnostics: [Diagnostic], to tree: T) -> Syntax { + let applier = FixItApplier(diagnostics: diagnostics) + return applier.visit(Syntax(tree)) + } +} + +/// Assert that the diagnostic `diag` produced in `tree` matches `spec`, +/// using `markerLocations` to translate markers to actual source locations. +func AssertDiagnostic( + _ diag: Diagnostic, + in tree: T, + markerLocations: [String: Int], + expected spec: DiagnosticSpec, + file: StaticString = #filePath, + line: UInt = #line +) { + if let markerLoc = markerLocations[spec.locationMarker] { + let locationConverter = SourceLocationConverter(file: "/test.swift", source: tree.description) + let actualLocation = diag.location(converter: locationConverter) + let expectedLocation = locationConverter.location(for: AbsolutePosition(utf8Offset: markerLoc)) + if let actualLine = actualLocation.line, + let actualColumn = actualLocation.column, + let expectedLine = expectedLocation.line, + let expectedColumn = expectedLocation.column { + XCTAssertEqual( + actualLine, expectedLine, + "Expected diagnostic on line \(expectedLine) but got \(actualLine)", + file: file, line: line + ) + XCTAssertEqual( + actualColumn, expectedColumn, + "Expected diagnostic on column \(expectedColumn) but got \(actualColumn)", + file: file, line: line + ) + } else { + XCTFail("Failed to resolve diagnostic location to line/column", file: file, line: line) + } + } else { + XCTFail("Did not find marker #^\(spec.locationMarker)^# in the source code", file: file, line: line) + } + if let id = spec.id { + XCTAssertEqual(diag.diagnosticID, id, file: file, line: line) + } + if let message = spec.message { + XCTAssertEqual(diag.message, message, file: file, line: line) + } + if let highlight = spec.highlight { + AssertStringsEqualWithDiff(diag.highlights.map(\.description).joined(), highlight, file: file, line: line) + } + if let fixIts = spec.fixIts { + XCTAssertEqual( + fixIts, diag.fixIts.map(\.message.message), + "Fix-Its for diagnostic did not match expected Fix-Its", + file: file, line: line + ) + } +} + +/// Parse `markedSource` and perform the following assertions: +/// - The parsed syntax tree should be printable back to the original source code (round-tripping) +/// - Parsing produced the given `diagnostics` (`diagnostics = []` asserts that the parse was successful) +/// - If `fixedSource` is not `nil`, assert that applying all fixes from the diagnostics produces `fixedSource` +/// The source file can be marked with markers of the form `#^DIAG^#` to mark source locations that can be referred to by `diagnostics`. +/// These markers are removed before parsing the source file. +/// By default, `DiagnosticSpec` asserts that the diagnostics is produced at a location marked by `#^DIAG^#`. +/// `parseSyntax` can be used to adjust the production that should be used as the entry point to parse the source code. func AssertParse( - _ parseSyntax: (inout Parser) -> Node, - allowErrors: Bool = false, + _ markedSource: String, + _ parseSyntax: (inout Parser) -> Node = { $0.parseSourceFile() }, + diagnostics expectedDiagnostics: [DiagnosticSpec] = [], + fixedSource expectedFixedSource: String? = nil, file: StaticString = #file, - line: UInt = #line, - _ source: () -> String -) throws { + line: UInt = #line +) { // Verify the parser can round-trip the source - let src = source() - var source = src - source.withUTF8 { buf in + let (markerLocations, source) = extractMarkers(markedSource) + var src = source + src.withUTF8 { buf in var parser = Parser(buf) withExtendedLifetime(parser) { - let parse = Syntax(raw: parseSyntax(&parser).raw) - AssertStringsEqualWithDiff("\(parse)", src, additionalInfo: """ + let tree = Syntax(raw: parseSyntax(&parser).raw) + AssertStringsEqualWithDiff("\(tree)", source, additionalInfo: """ + Source failed to round-trip. + Actual syntax tree: - \(parse.recursiveDescription) + \(tree.recursiveDescription) + """, file: file, line: line) + let diags = ParseDiagnosticsGenerator.diagnostics(for: tree) + XCTAssertEqual(diags.count, expectedDiagnostics.count, """ + Expected \(expectedDiagnostics.count) diagnostics but received \(diags.count): + \(diags.map(\.debugDescription).joined(separator: "\n")) """, file: file, line: line) - if !allowErrors { - let diagnostics = ParseDiagnosticsGenerator.diagnostics(for: Syntax(raw: parse.raw)) - XCTAssertEqual( - diagnostics.count, 0, - """ - Received the following diagnostics while parsing the source code: - \(diagnostics) - """, - file: file, line: line) + for (diag, expectedDiag) in zip(diags, expectedDiagnostics) { + AssertDiagnostic(diag, in: tree, markerLocations: markerLocations, expected: expectedDiag, file: file, line: line) + } + if let expectedFixedSource = expectedFixedSource { + let fixedSource = FixItApplier.applyFixes(in: diags, to: tree).description + AssertStringsEqualWithDiff(fixedSource, expectedFixedSource, file: file, line: line) } } } diff --git a/Tests/SwiftParserTest/Availability.swift b/Tests/SwiftParserTest/Availability.swift index 534253e62b1..d0fbb88d3b9 100644 --- a/Tests/SwiftParserTest/Availability.swift +++ b/Tests/SwiftParserTest/Availability.swift @@ -4,7 +4,7 @@ import XCTest final class AvailabilityTests: XCTestCase { func testAvailableMember() throws { - try AssertParse({ $0.parseSourceFile() }) { + AssertParse( """ @available(OSX 10.0, introduced: 10.12) // expected-error@-1 {{'introduced' can't be combined with shorthand specification 'OSX 10.0'}} @@ -14,18 +14,19 @@ final class AvailabilityTests: XCTestCase { @available(iOS 6.0, OSX 10.8, *) func availableOnMultiplePlatforms() {} """ - } + ) - try AssertParse({ $0.parseClassDeclaration(.empty) }) { + AssertParse( """ class IncrementalParseTransition { @available(*, deprecated, message: "Use the initializer taking 'ConcurrentEdits' instead") public convenience init() {} } - """ - } + """, + { $0.parseClassDeclaration(.empty) } + ) - try AssertParse({ $0.parseSourceFile() }) { + AssertParse( """ extension String { @available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *) @@ -35,9 +36,9 @@ final class AvailabilityTests: XCTestCase { public func fiddle() { } } """ - } + ) - try AssertParse({ $0.parseSourceFile() }) { + AssertParse( """ @available( iOSApplicationExtension, @@ -47,6 +48,6 @@ final class AvailabilityTests: XCTestCase { "Use something else because this is definitely deprecated.") func f2() {} """ - } + ) } } diff --git a/Tests/SwiftParserTest/Declarations.swift b/Tests/SwiftParserTest/Declarations.swift index 038985aafbe..9cfc79fab63 100644 --- a/Tests/SwiftParserTest/Declarations.swift +++ b/Tests/SwiftParserTest/Declarations.swift @@ -3,71 +3,41 @@ import XCTest final class DeclarationTests: XCTestCase { - func testImports() throws { - try AssertParse({ $0.parseImportDeclaration(.empty) }) { - "import Foundation" - } + func testImports() { + AssertParse("import Foundation") - try AssertParse({ $0.parseDeclaration() }) { - "@_spi(Private) import SwiftUI" - } + AssertParse("@_spi(Private) import SwiftUI") - try AssertParse({ $0.parseDeclaration() }) { - "@_exported import class Foundation.Thread" - } + AssertParse("@_exported import class Foundation.Thread") - try AssertParse({ $0.parseDeclaration() }) { - """ - @_private(sourceFile: "YetAnotherFile.swift") import Foundation - """ - } + AssertParse(#"@_private(sourceFile: "YetAnotherFile.swift") import Foundation"#) } - func testStructParsing() throws { - try AssertParse({ $0.parseStructDeclaration(.empty) }) { - """ - struct Foo { - } - """ - } + func testStructParsing() { + AssertParse("struct Foo {}") } - func testFuncParsing() throws { - try AssertParse({ $0.parseFuncDeclaration(.empty) }) { - """ - func foo() { - } - """ - } + func testFuncParsing() { + AssertParse("func foo() {}") - try AssertParse({ $0.parseFuncDeclaration(.empty) }) { - """ - func foo() -> Slice> { - } - """ - } + AssertParse("func foo() -> Slice> {}") - try AssertParse({ $0.parseSourceFile() }) { + AssertParse( """ func onEscapingAutoclosure(_ fn: @Sendable @autoclosure @escaping () -> Int) { } func onEscapingAutoclosure2(_ fn: @escaping @autoclosure @Sendable () -> Int) { } - func bar(_ : String) async throws -> [[String]: Array] {} + func bar(_ : String) async -> [[String]: Array] {} func tupleMembersFunc() -> (Type.Inner, Type2.Inner2) {} func myFun(var1: S) { // do stuff } """ - } + ) } - func testClassParsing() throws { - try AssertParse({ $0.parseClassDeclaration(.empty) }) { - """ - class Foo { - } - """ - } + func testClassParsing() { + AssertParse("class Foo {}") - try AssertParse({ $0.parseSourceFile() }) { + AssertParse( """ @dynamicMemberLookup @available(swift 4.0) public class MyClass { @@ -75,25 +45,21 @@ final class DeclarationTests: XCTestCase { let B: Double } """ - } + ) - try AssertParse({ $0.parseGenericParameters() }) { - "<@NSApplicationMain T: AnyObject>" - } + AssertParse( + "<@NSApplicationMain T: AnyObject>", + { $0.parseGenericParameters() } + ) } - func testActorParsing() throws { - try AssertParse({ $0.parseActorDeclaration(.empty) }) { - """ - actor Foo { - } - """ - } + func testActorParsing() { + AssertParse("actor Foo {}") - try AssertParse({ $0.parseActorDeclaration(.empty) }) { + AssertParse( """ actor Foo { - nonisolated init?() throws { + nonisolated init?() { for (x, y, z) in self.triples { assert(isSafe) } @@ -103,24 +69,15 @@ final class DeclarationTests: XCTestCase { } } """ - } + ) } - func testProtocolParsing() throws { - try AssertParse({ $0.parseProtocolDeclaration(.empty) }) { - """ - protocol Foo { - } - """ - } + func testProtocolParsing() { + AssertParse("protocol Foo {}") - try AssertParse({ $0.parseProtocolDeclaration(.empty) }) { - """ - protocol P { init() } - """ - } + AssertParse("protocol P { init() }") - try AssertParse({ $0.parseProtocolDeclaration(.empty) }) { + AssertParse( """ protocol P { associatedtype Foo: Bar where X.Y == Z.W.W.Self @@ -129,40 +86,37 @@ final class DeclarationTests: XCTestCase { subscript(index: Int) -> R } """ - } + ) } - func testVariableDeclarations() throws { - try AssertParse({ $0.parseDeclaration() }) { - """ - private unowned(unsafe) var foo: Int - """ - } + func testVariableDeclarations() { + AssertParse("private unowned(unsafe) var foo: Int") + + AssertParse("_ = foo?.description") - try AssertParse({ $0.parseSourceFile() }, allowErrors: true) { - "_ = foo/* */?.description" - } + AssertParse( + "_ = foo/* */?.description#^DIAG^#", + diagnostics: [ + DiagnosticSpec(message: "Expected ':' after '? ...' in ternary expression") + ] + ) - try AssertParse({ $0.parseLetOrVarDeclaration(.empty) }) { - "var a = Array?(from: decoder)" - } + AssertParse("var a = Array?(from: decoder)") - try AssertParse({ $0.parseSourceFile() }) { - "@Wrapper var café = 42" - } + AssertParse("@Wrapper var café = 42") - try AssertParse({ $0.parseLetOrVarDeclaration(.empty) }) { + AssertParse( """ var x: T { - get async throws { + get async { foo() bar() } } """ - } + ) - try AssertParse({ $0.parseLetOrVarDeclaration(.empty) }) { + AssertParse( """ var foo: Int { _read { @@ -174,40 +128,28 @@ final class DeclarationTests: XCTestCase { } } """ - } + ) - try AssertParse({ $0.parseSourceFile() }) { + AssertParse( """ async let a = fetch("1.jpg") async let b: Image = fetch("2.jpg") async let secondPhotoToFetch = fetch("3.jpg") async let theVeryLastPhotoWeWant = fetch("4.jpg") """ - } + ) } - func testTypealias() throws { - try AssertParse({ $0.parseTypealiasDeclaration(.empty) }) { - """ - typealias Foo = Int - """ - } + func testTypealias() { + AssertParse("typealias Foo = Int") - try AssertParse({ $0.parseTypealiasDeclaration(.empty) }) { - """ - typealias MyAlias = (_ a: Int, _ b: Double, _ c: Bool, _ d: String) -> Bool - """ - } + AssertParse("typealias MyAlias = (_ a: Int, _ b: Double, _ c: Bool, _ d: String) -> Bool") - try AssertParse({ $0.parseSourceFile() }) { - """ - typealias A = @attr1 @attr2(hello) (Int) -> Void - """ - } + AssertParse("typealias A = @attr1 @attr2(hello) (Int) -> Void") } - func testPrecedenceGroup() throws { - try AssertParse({ $0.parsePrecedenceGroupDeclaration(.empty) }) { + func testPrecedenceGroup() { + AssertParse( """ precedencegroup FooGroup { higherThan: Group1, Group2 @@ -216,28 +158,24 @@ final class DeclarationTests: XCTestCase { assignment: false } """ - } + ) - try AssertParse({ $0.parsePrecedenceGroupDeclaration(.empty) }) { + AssertParse( """ precedencegroup FunnyPrecedence { associativity: left higherThan: MultiplicationPrecedence } """ - } + ) } - func testOperators() throws { - try AssertParse({ $0.parseDeclaration() }) { - """ - infix operator *-* : FunnyPrecedence - """ - } + func testOperators() { + AssertParse("infix operator *-* : FunnyPrecedence") } - func testObjCAttribute() throws { - try AssertParse({ $0.parseSourceFile() }) { + func testObjCAttribute() { + AssertParse( """ @objc( thisMethodHasAVeryLongName: @@ -246,11 +184,11 @@ final class DeclarationTests: XCTestCase { ) func f() {} """ - } + ) } - func testDifferentiableAttribute() throws { - try AssertParse({ $0.parseSourceFile() }) { + func testDifferentiableAttribute() { + AssertParse( """ @differentiable(wrt: x where T: D) func foo(_ x: T) -> T {} @@ -264,23 +202,19 @@ final class DeclarationTests: XCTestCase { @differentiable(wrt: (x, y)) func foo(_ x: T) -> T {} """ - } + ) } - func testParsePoundError() throws { - try AssertParse({ $0.parsePoundDiagnosticDeclaration() }) { - #"#error("Unsupported platform")"# - } + func testParsePoundError() { + AssertParse(#"#error("Unsupported platform")"#) } - func testParsePoundWarning() throws { - try AssertParse({ $0.parsePoundDiagnosticDeclaration() }) { - #"#warning("Unsupported platform")"# - } + func testParsePoundWarning() { + AssertParse(#"#warning("Unsupported platform")"#) } - func testParseSpecializeAttribute() throws { - try AssertParse({ $0.parseSourceFile() }) { + func testParseSpecializeAttribute() { + AssertParse( #""" @_specialize(where T == Int, U == Float) mutating func exchangeSecond(_ u: U, _ t: T) -> (U, T) { @@ -337,9 +271,9 @@ final class DeclarationTests: XCTestCase { __consuming func __specialize__copyContents(initializing: Swift.UnsafeMutableBufferPointer) -> (Iterator, Int) { Builtin.unreachable() } } """# - } + ) - try AssertParse({ $0.parseSourceFile() }) { + AssertParse( """ @_specialize(where T: _Trivial(32), T: _Trivial(64), T: _Trivial, T: _RefCountedObject) @_specialize(where T: _Trivial, T: _Trivial(64)) @@ -350,20 +284,20 @@ final class DeclarationTests: XCTestCase { return 55555 } """ - } + ) } - func testParseDynamicReplacement() throws { - try AssertParse({ $0.parseDeclaration() }) { + func testParseDynamicReplacement() { + AssertParse( """ @_dynamicReplacement(for: dynamic_replaceable()) func replacement() { dynamic_replaceable() } """ - } + ) - try AssertParse({ $0.parseDeclaration() }) { + AssertParse( """ @_dynamicReplacement(for: subscript(_:)) subscript(x y: Int) -> Int { @@ -375,29 +309,29 @@ final class DeclarationTests: XCTestCase { } } """ - } + ) - try AssertParse({ $0.parseDeclaration() }) { + AssertParse( """ @_dynamicReplacement(for: dynamic_replaceable_var) var r : Int { return 0 } """ - } + ) - try AssertParse({ $0.parseDeclaration() }) { + AssertParse( """ @_dynamicReplacement(for: init(x:)) init(y: Int) { self.init(x: y + 1) } """ - } + ) } - func testEnumParsing() throws { - try AssertParse({ $0.parseEnumDeclaration(.empty) }) { + func testEnumParsing() { + AssertParse( """ enum Content { case keyPath(KeyPath) @@ -405,17 +339,17 @@ final class DeclarationTests: XCTestCase { case value(Value?) } """ - } + ) } - func testStandaloneModifier() throws { - try AssertParse({ $0.parseSourceFile() }) { + func testStandaloneModifier() { + AssertParse( """ struct a { public } """ - } + ) } } diff --git a/Tests/SwiftParserTest/DiagnosticAssertions.swift b/Tests/SwiftParserTest/DiagnosticAssertions.swift deleted file mode 100644 index 3e0cf9b55a2..00000000000 --- a/Tests/SwiftParserTest/DiagnosticAssertions.swift +++ /dev/null @@ -1,90 +0,0 @@ -import SwiftDiagnostics -import SwiftSyntax -import SwiftParser -import XCTest -import _SwiftSyntaxTestSupport - -public class FixItApplier: SyntaxRewriter { - var changes: [FixIt.Change] - - init(diagnostics: [Diagnostic]) { - self.changes = diagnostics.flatMap({ $0.fixIts }).flatMap({ $0.changes }) - } - - public override func visitAny(_ node: Syntax) -> Syntax? { - for change in changes { - switch change { - case .replace(oldNode: let oldNode, newNode: let newNode) where oldNode.id == node.id: - return newNode - default: - break - } - } - return nil - } - - /// Applies all Fix-Its in `diagnostics` to `tree` and returns the fixed syntax tree. - public static func applyFixes(in diagnostics: [Diagnostic], to tree: T) -> Syntax { - let applier = FixItApplier(diagnostics: diagnostics) - return applier.visit(Syntax(tree)) - } -} - -/// Asserts that the diagnostics `diag` inside `tree` occurs at `line` and -/// `column`. -/// If `id` is not `nil`, assert that the diagnostic has the given message. -/// If `message` is not `nil`, assert that the diagnostic has the given message. -/// If `highlight` is not `nil`, assert that the highlighted range has this content. -func XCTAssertDiagnostic( - _ diag: Diagnostic, - in tree: T, - line: Int, - column: Int, - id: MessageID? = nil, - message: String? = nil, - highlight: String? = nil, - testFile: StaticString = #filePath, - testLine: UInt = #line -) { - let locationConverter = SourceLocationConverter(file: "/test.swift", source: tree.description) - let location = diag.location(converter: locationConverter) - XCTAssertEqual(location.line, line, "Expected diagnostic on line \(line) but got \(location.line ?? -1)", file: testFile, line: testLine) - XCTAssertEqual(location.column, column, "Expected diagnostic on column \(column) but got \(location.column ?? -1)", file: testFile, line: testLine) - if let id = id { - XCTAssertEqual(diag.diagnosticID, id, file: testFile, line: testLine) - } - if let message = message { - XCTAssertEqual(diag.message, message, file: testFile, line: testLine) - } - if let highlight = highlight { - AssertStringsEqualWithDiff(diag.highlights.map(\.description).joined(), highlight, file: testFile, line: testLine) - } -} - -/// Assert that producing diagnostics for `tree` generates a single diagnostic -/// at `line` and `column`. -/// If `message` is not `nil`, assert that the diagnostic has the given message. -/// If `id` is not `nil`, assert that the diagnostic has the given message. -/// If `expectedFixedSource` is not `nil`, assert that the source code that it is source code that result from applying all Fix-Its. -func XCTAssertSingleDiagnostic( - in tree: T, - line: Int, - column: Int, - id: MessageID? = nil, - message: String? = nil, - highlight: String? = nil, - expectedFixedSource: String? = nil, - testFile: StaticString = #filePath, - testLine: UInt = #line -) { - let diags = ParseDiagnosticsGenerator.diagnostics(for: tree) - guard diags.count == 1 else { - XCTFail("Received \(diags.count) diagnostics but expected excatly one: \(diags)", file: testFile, line: testLine) - return - } - XCTAssertDiagnostic(diags.first!, in: tree, line: line, column: column, id: id, message: message, highlight: highlight, testFile: testFile, testLine: testLine) - if let expectedFixedSource = expectedFixedSource { - let fixedSource = FixItApplier.applyFixes(in: diags, to: tree).description - AssertStringsEqualWithDiff(fixedSource, expectedFixedSource, file: testFile, line: testLine) - } -} diff --git a/Tests/SwiftParserTest/DiagnosticTests.swift b/Tests/SwiftParserTest/DiagnosticTests.swift index cdb6b57107f..8dc0f09c32e 100644 --- a/Tests/SwiftParserTest/DiagnosticTests.swift +++ b/Tests/SwiftParserTest/DiagnosticTests.swift @@ -4,105 +4,100 @@ import XCTest import _SwiftSyntaxTestSupport public class DiagnosticTests: XCTestCase { - public func testMissingTokenDiags() throws { - let source = """ - (first second Int) - """ - let signature = withParser(source: source) { Syntax(raw: $0.parseFunctionSignature().raw) } - - XCTAssertSingleDiagnostic(in: signature, line: 1, column: 15, id: MissingTokenError.diagnosticID, message: "Expected ':' in function parameter") + public func testMissingTokenDiags() { + AssertParse( + "(first second #^DIAG^#Int)", + { $0.parseFunctionSignature() }, + diagnostics: [ + DiagnosticSpec(message: "Expected ':' in function parameter") + ] + ) } - public func testUnexpectedDiags() throws { - let source = """ - (first second third fourth: Int) - """ - let signature = withParser(source: source) { Syntax(raw: $0.parseFunctionSignature().raw) } - - XCTAssertSingleDiagnostic(in: signature, line: 1, column: 15, message: "Unexpected text 'third fourth' found in function parameter") + public func testUnexpectedDiags() { + AssertParse( + "(first second #^DIAG^#third fourth: Int)", + { $0.parseFunctionSignature() }, + diagnostics: [ + DiagnosticSpec(message: "Unexpected text 'third fourth' found in function parameter") + ] + ) } - public func testCStyleForLoop() throws { - let source = """ - for let x = 0; x < 10; x += 1, y += 1 { - } - """ - let loop = withParser(source: source) { - Syntax(raw: $0.parseForEachStatement().raw).as(ForInStmtSyntax.self)! - } - - XCTAssertSingleDiagnostic( - in: loop, - line: 1, column: 1, - message: "C-style for statement has been removed in Swift 3", - highlight: "let x = 0; x < 10; x += 1, y += 1 " + public func testCStyleForLoop() { + AssertParse( + """ + #^DIAG^#for let x = 0; x < 10; x += 1, y += 1 { + } + """, + diagnostics: [ + DiagnosticSpec(message: "C-style for statement has been removed in Swift 3", highlight: "let x = 0; x < 10; x += 1, y += 1 ") + ] ) } - public func testMissingClosingParen() throws { - let source = """ - (first second: Int - """ - let signature = withParser(source: source) { - Syntax(raw: $0.parseFunctionSignature().raw).as(FunctionSignatureSyntax.self)! - } - - XCTAssertSingleDiagnostic(in: signature, line: 1, column: 19, message: "Expected ')' to end parameter clause") + public func testMissingClosingParen() { + AssertParse( + "(first second: Int#^DIAG^#", + { $0.parseFunctionSignature() }, + diagnostics: [ + DiagnosticSpec(message: "Expected ')' to end parameter clause") + ] + ) } - public func testMissingOpeningParen() throws { - let source = """ - first second: Int) - """ - let signature = withParser(source: source) { - Syntax(raw: $0.parseFunctionSignature().raw).as(FunctionSignatureSyntax.self)! - } - - XCTAssertSingleDiagnostic(in: signature, line: 1, column: 1, message: "Expected '(' to start parameter clause") + public func testMissingOpeningParen() { + AssertParse( + "#^DIAG^#first second: Int)", + { $0.parseFunctionSignature() }, + diagnostics: [ + DiagnosticSpec(message: "Expected '(' to start parameter clause") + ] + ) } - public func testThrowsInWrongLocation() throws { - let source = """ - () -> throws Int - """ - - let signature = withParser(source: source) { - Syntax(raw: $0.parseFunctionSignature().raw).as(FunctionSignatureSyntax.self)! - } - - XCTAssertSingleDiagnostic( - in: signature, - line: 1, column: 7, - message: "'throws' may only occur before '->'", - expectedFixedSource: "() throws -> Int" + public func testThrowsInWrongLocation() { + AssertParse( + "() -> #^DIAG^#throws Int", + { $0.parseFunctionSignature() }, + diagnostics: [ + DiagnosticSpec(message: "'throws' may only occur before '->'", fixIts: ["Move 'throws' before '->'"]) + ], + fixedSource: "() throws -> Int" ) } - public func testNoParamsForFunction() throws { - let source = """ - class MyClass { - func withoutParameters - - func withParameters() {} - } - """ - - let classDecl = withParser(source: source) { - Syntax(raw: $0.parseDeclaration().raw).as(ClassDeclSyntax.self)! - } - - XCTAssertSingleDiagnostic(in: classDecl, line: 2, column: 25, message: "Expected argument list in function declaration") + public func testNoParamsForFunction() { + AssertParse( + """ + class MyClass { + func withoutParameters#^DIAG^# + + func withParameters() {} + } + """, + diagnostics: [ + DiagnosticSpec(message: "Expected argument list in function declaration") + ] + ) } - func testMissingColonInTernary() throws { - let source = """ - foo ? 1 - """ + func testMissingColonInTernary() { + AssertParse( + "foo ? 1#^DIAG^#", + diagnostics: [ + DiagnosticSpec(message: "Expected ':' after '? ...' in ternary expression") + ] + ) + } - let node = withParser(source: source) { - Syntax(raw: $0.parseExpression().raw) - } - XCTAssertSingleDiagnostic(in: node, line: 1, column: 8, message: "Expected ':' after '? ...' in ternary expression") + public func testUnterminatedPoundIf() { + AssertParse( + "#if test#^DIAG^#", + diagnostics: [ + DiagnosticSpec(message: "Expected '#endif' in declaration") + ] + ) } } diff --git a/Tests/SwiftParserTest/Directives.swift b/Tests/SwiftParserTest/Directives.swift index ce7356c589b..39d1a0ba29f 100644 --- a/Tests/SwiftParserTest/Directives.swift +++ b/Tests/SwiftParserTest/Directives.swift @@ -3,8 +3,8 @@ import XCTest final class DirectiveTests: XCTestCase { - func testSwitchIfConfig() throws { - try AssertParse({ $0.parseStatement() }) { + func testSwitchIfConfig() { + AssertParse( """ switch x { case 1: fallthrough @@ -25,11 +25,11 @@ final class DirectiveTests: XCTestCase { case 10: print(10) } """ - } + ) } - func testPostfixIfConfigExpression() throws { - try AssertParse({ $0.parseExpression() }) { + func testPostfixIfConfigExpression() { + AssertParse( """ foo .bar() @@ -54,20 +54,20 @@ final class DirectiveTests: XCTestCase { #endif #endif """ - } + ) } - func testSourceLocation() throws { - try AssertParse({ $0.parsePoundSourceLocationDirective() }) { + func testSourceLocation() { + AssertParse( """ #sourceLocation() """ - } + ) - try AssertParse({ $0.parsePoundSourceLocationDirective() }) { + AssertParse( """ #sourceLocation(file: "foo", line: 42) """ - } + ) } } diff --git a/Tests/SwiftParserTest/Expressions.swift b/Tests/SwiftParserTest/Expressions.swift index 62c97bd29b1..ad644896d59 100644 --- a/Tests/SwiftParserTest/Expressions.swift +++ b/Tests/SwiftParserTest/Expressions.swift @@ -3,49 +3,37 @@ import XCTest final class ExpressionTests: XCTestCase { - func testTernary() throws { - try AssertParse({ $0.parseSourceFile() }) { - "let a =" - } - - try AssertParse({ $0.parseExpression() }, allowErrors: false) { - """ - a ? b : c ? d : e - """ - } - try AssertParse({ $0.parseExpression() }) { - """ - a ? b : - """ - } + func testTernary() { + AssertParse("let a =") + + AssertParse("a ? b : c ? d : e") + AssertParse("a ? b :") } - func testSequence() throws { - try AssertParse({ $0.parseExpression() }, allowErrors: false) { - """ - A as? B + C -> D is E as! F ? G = 42 : H - """ - } + func testSequence() { + AssertParse( + "A as? B + C -> D is E as! F ? G = 42 : H" + ) } - func testClosureLiterals() throws { - try AssertParse({ $0.parseClosureExpression() }) { + func testClosureLiterals() { + AssertParse( #""" { @MainActor (a: Int) async -> Int in print("hi") } """# - } + ) - try AssertParse({ $0.parseClosureExpression() }) { + AssertParse( """ { [weak self, weak weakB = b] foo in return 0 } """ - } + ) } - func testTrailingClosures() throws { - try AssertParse({ $0.parseSourceFile() }) { + func testTrailingClosures() { + AssertParse( """ var button = View.Button[5, 4, 3 ] { @@ -53,61 +41,49 @@ final class ExpressionTests: XCTestCase { Text("ABC") } """ - } + ) - try AssertParse({ $0.parseExpression() }) { - """ - compactMap { (parserDiag) in } - """ - } + AssertParse("compactMap { (parserDiag) in }") } - func testSequenceExpressions() throws { - try AssertParse({ $0.parseSequenceExpressionElement(.basic) }) { - """ - await a() - """ - } + func testSequenceExpressions() { + AssertParse("await a()") } - func testNestedTypeSpecialization() throws { - try AssertParse({ $0.parseExpression() }) { - """ - Swift.Array>() - """ - } + func testNestedTypeSpecialization() { + AssertParse("Swift.Array>()") } - func testObjectLiterals() throws { - try AssertParse({ $0.parseSourceFile() }) { + func testObjectLiterals() { + AssertParse( """ #colorLiteral() #colorLiteral(red: 1.0) #colorLiteral(red: 1.0, green: 1.0) #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0) """ - } + ) - try AssertParse({ $0.parseSourceFile() }) { + AssertParse( """ #imageLiteral() #imageLiteral(resourceName: "foo.png") #imageLiteral(resourceName: "foo/bar/baz/qux.png") #imageLiteral(resourceName: "foo/bar/baz/quux.png") """ - } + ) } - func testKeypathExpression() throws { - try AssertParse({ $0.parseExpression() }) { + func testKeypathExpression() { + AssertParse( #""" children.filter(\.type.defaultInitialization.isEmpty) """# - } + ) } - func testBasicLiterals() throws { - try AssertParse({ $0.parseSourceFile() }) { + func testBasicLiterals() { + AssertParse( """ #file (#line) @@ -120,88 +96,72 @@ final class ExpressionTests: XCTestCase { __FUNCTION__ __DSO_HANDLE__ """ - } + ) } - func testRegexLiteral() throws { - try AssertParse({ $0.parseExpression() }) { - #""" - /(?[[:alpha:]]\w*) = (?[0-9A-F]+)/ - """# - } + func testRegexLiteral() { + AssertParse( + #""" + /(?[[:alpha:]]\w*) = (?[0-9A-F]+)/ + """# + ) } - func testInitializerExpression() throws { - try AssertParse({ $0.parseExpression() }) { - """ - Lexer.Cursor(input: input, previous: 0) - """ - } + func testInitializerExpression() { + AssertParse("Lexer.Cursor(input: input, previous: 0)") } - func testCollectionLiterals() throws { - try AssertParse({ $0.parseExpression() }) { - "[Dictionary: Int]()" - } - - try AssertParse({ $0.parseExpression() }) { - "[(Int, Double) -> Bool]()" - } - - try AssertParse({ $0.parseExpression() }) { - "[(Int, Double) throws -> Bool]()" - } + func testCollectionLiterals() { + AssertParse("[Dictionary: Int]()") + AssertParse("[(Int, Double) -> Bool]()") + AssertParse("[(Int, Double) -> Bool]()") + AssertParse("_ = [@convention(block) () -> Int]().count") + AssertParse("A<@convention(c) () -> Int32>.c()") + AssertParse("A<(@autoclosure @escaping () -> Int, Int) -> Void>.c()") + AssertParse("_ = [String: (@escaping (A) -> Int) -> Void]().keys") - try AssertParse({ $0.parseExpression() }) { - "_ = [@convention(block) () -> Int]().count" - } - - try AssertParse({ $0.parseExpression() }) { - "A<@convention(c) () -> Int32>.c()" - } - - try AssertParse({ $0.parseExpression() }) { - "A<(@autoclosure @escaping () -> Int, Int) -> Void>.c()" - } - - try AssertParse({ $0.parseExpression() }) { - "_ = [String: (@escaping (A) -> Int) -> Void]().keys" - } - - try AssertParse({ $0.parseExpression() }) { + AssertParse( """ [ condition ? firstOption : secondOption, bar(), ] """ - } + ) - try AssertParse({ $0.parseExpression() }, allowErrors: true) { - "[," - } + AssertParse( + "[,#^DIAG^#", + diagnostics: [ + DiagnosticSpec(message: "Expected ']' to end expression") + ] + ) - try AssertParse({ $0.parseExpression() }, allowErrors: true) { - """ - ([1:) + AssertParse( """ - } + ([1:#^DIAG^#) + """, + diagnostics: [ + // FIXME: Why is this diagnostic produced? + DiagnosticSpec(message: "Expected ':'"), + DiagnosticSpec(message: "Expected ']' to end expression"), + ] + ) } - func testInterpolatedStringLiterals() throws { - try AssertParse({ $0.parseSourceFile() }) { + func testInterpolatedStringLiterals() { + AssertParse( #""" return "Fixit: \(range.debugDescription) Text: \"\(text)\"" """# - } + ) - try AssertParse({ $0.parseExpression() }) { + AssertParse( #""" "text \(array.map({ "\($0)" }).joined(separator: ",")) text" """# - } + ) - try AssertParse({ $0.parseExpression() }) { + AssertParse( #""" """ \(gen(xx) { (x) in @@ -211,28 +171,33 @@ final class ExpressionTests: XCTestCase { }) """ """# - } + ) } - func testStringLiterals() throws { - try AssertParse({ $0.parseExpression() }) { + func testStringLiterals() { + AssertParse( #""" "" """# - } + ) - try AssertParse({ $0.parseExpression() }) { + AssertParse( #""" """ """ """# - } + ) - try AssertParse({ $0.parseExpression() }, allowErrors: true) { - #"" >> \( abc } ) << ""# - } + AssertParse( + #""" + " >> \( abc #^DIAG^#} ) << " + """#, + diagnostics: [ + DiagnosticSpec(message: "Unexpected text '} '") + ] + ) - try AssertParse({ $0.parseSourceFile() }) { + AssertParse( ##""" @@ -242,21 +207,27 @@ final class ExpressionTests: XCTestCase { """## - } + ) - try AssertParse({ $0.parseExpression() }, allowErrors: true) { - #""\","# - } + AssertParse( + #""" + "\",#^DIAG^# + """#, + diagnostics: [ + // FIXME: Should be Expected '"' in string literal + DiagnosticSpec(message: #"Expected '"' in expression"#) + ] + ) - try AssertParse({ $0.parseExpression() }) { + AssertParse( #""" "(?i)\\b((?:[a-z][\\w-]+:(?:/{1,3}|[a-z0-9%])|www\\d{0,3}[.]|[a-z0-9.\\-]+[.][a-z]{2,4}/)" + "(?:[^\\s()<>]+|\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\))+(?:\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*" + "\\)|[^\\s`!()\\[\\]{};:'\".,<>?«»“”‘’]))" """# - } + ) - try AssertParse({ $0.parseExpression() }) { + AssertParse( #""" """ Custom(custom: \(interval),\ @@ -265,53 +236,53 @@ final class ExpressionTests: XCTestCase { Plain: \(units))" """ """# - } + ) - try AssertParse({ $0.parseExpression() }) { + AssertParse( #""" "Founded: \(Date.appleFounding, format: 📆)" """# - } + ) - try AssertParse({ $0.parseExpression()}) { + AssertParse( """ "" """ - } + ) - try AssertParse({ $0.parseExpression() }) { + AssertParse( ##""" #"""# """## - } + ) - try AssertParse({ $0.parseExpression() }) { + AssertParse( ##""" #"""""# """## - } + ) - try AssertParse({ $0.parseExpression() }) { + AssertParse( ##""" #""" multiline raw """# """## - } + ) - try AssertParse({ $0.parseExpression() }) { + AssertParse( #""" "\(x)" """# - } + ) } - func testRangeSubscript() throws { - try AssertParse({ $0.parseExpression() }) { + func testRangeSubscript() { + AssertParse( """ text[...] """ - } + ) } } diff --git a/Tests/SwiftParserTest/RecoveryTests.swift b/Tests/SwiftParserTest/RecoveryTests.swift index 293f06ec5ca..a12715ae0a6 100644 --- a/Tests/SwiftParserTest/RecoveryTests.swift +++ b/Tests/SwiftParserTest/RecoveryTests.swift @@ -4,72 +4,84 @@ import XCTest import _SwiftSyntaxTestSupport public class RecoveryTests: XCTestCase { - func testTopLevelCaseRecovery() throws { - try AssertParse({ $0.parseSourceFile() }) { + func testTopLevelCaseRecovery() { + AssertParse( "/*#-editable-code Swift Platground editable area*/default/*#-end-editable-code*/" - } + ) - try AssertParse({ $0.parseSourceFile() }) { - "case:" - } + AssertParse("case:") - try AssertParse({ $0.parseSourceFile() }) { - #"case: { ("Hello World") }"# - } + AssertParse( + #""" + case: { ("Hello World") } + """# + ) } - func testBogusKeypathBaseRecovery() throws { - try AssertParse({ $0.parseSourceFile() }, allowErrors: true) { - #"func nestThoseIfs() {\n if false != true {\n print "\(i)\"\n"# - } + func testBogusKeypathBaseRecovery() { + AssertParse( + #""" + func nestThoseIfs() {\n if false != true {\n print "\(i)\"\n#^DIAG^# + """#, + diagnostics: [ + DiagnosticSpec(message: #"Expected '"' in expression"#), + DiagnosticSpec(message: "Expected '}'"), + DiagnosticSpec(message: "Expected '}'"), + ] + ) } - func testExtraneousRightBraceRecovery() throws { - try AssertParse({ $0.parseSourceFile() }) { - "class ABC { let def = ghi(jkl: mno) } }" - } + func testExtraneousRightBraceRecovery() { + AssertParse("class ABC { let def = ghi(jkl: mno) } }") } - func testMissingIfClauseIntroducer() throws { - try AssertParse({ $0.parseSourceFile() }) { - "if _ = 42 {}" - } + func testMissingIfClauseIntroducer() { + AssertParse("if _ = 42 {}") } - func testMissingSubscriptReturnClause() throws { - try AssertParse({ $0.parseSourceFile() }, allowErrors: true) { + func testMissingSubscriptReturnClause() { + AssertParse( """ struct Foo { - subscript(x: String) {} + subscript(x: String) #^DIAG^#{} } - """ - } + """, + diagnostics: [ + DiagnosticSpec(message: "Expected '->'") + ] + ) } - func testSingleQuoteStringLiteral() throws { - try AssertParse({ $0.parseExpression() }) { + func testSingleQuoteStringLiteral() { + AssertParse( #""" 'red' """# - } + ) } - func testClassWithLeadingNumber() throws { - try AssertParse({ $0.parseSourceFile() }, allowErrors: true) { + func testClassWithLeadingNumber() { + AssertParse( """ - class 23class { + class #^DIAG^#23class { // expected-error@-1 {{class name can only start with a letter or underscore, not a number}} // expected-error@-2 {{'c' is not a valid digit in integer literal}} func 24method() {} // expected-error@-1 {{function name can only start with a letter or underscore, not a number}} // expected-error@-2 {{'m' is not a valid digit in integer literal}} } - """ - } + """, + // FIXME: These are simply bad diagnostics. We should be complaining that identifiers cannot start with digits. + diagnostics: [ + DiagnosticSpec(message: "Expected '' in declaration"), + DiagnosticSpec(message: "Expected '{'"), + DiagnosticSpec(message: "Expected '}'"), + ] + ) } - func testAttributesOnStatements() throws { - try AssertParse({ $0.parseSourceFile() }) { + func testAttributesOnStatements() { + AssertParse( """ func test1() { @s return @@ -78,20 +90,28 @@ public class RecoveryTests: XCTestCase { @unknown return } """ - } + ) } - func testMissingArrowInArrowExpr() throws { - try AssertParse({ $0.parseSourceFile() }, allowErrors: true) { - """ - [(Int) -> throws Int]() - let _ = [Int throws Int]() - """ - } + func testMissingArrowInArrowExpr() { + AssertParse( + "[(Int) -> #^DIAG^#throws Int]()", + diagnostics: [ + // FIXME: We should suggest to move 'throws' in front of '->' + DiagnosticSpec(message: "Unexpected text 'throws Int' found in expression") + ] + ) + + AssertParse( + "let _ = [Int throws #^DIAG^#Int]()", + diagnostics: [ + DiagnosticSpec(message: "Expected '->' in expression") + ] + ) } - func testBogusSwitchStatement() throws { - try AssertParse({ $0.parseStatement() }) { + func testBogusSwitchStatement() { + AssertParse( """ switch x { print() @@ -102,9 +122,9 @@ public class RecoveryTests: XCTestCase { break } """ - } + ) - try AssertParse({ $0.parseStatement() }) { + AssertParse( """ switch x { print() @@ -118,102 +138,126 @@ public class RecoveryTests: XCTestCase { break } """ - } + ) } - func testBogusLineLabel() throws { - try AssertParse({ $0.parseSourceFile() }) { + func testBogusLineLabel() { + AssertParse( """ LABEL: """ - } + ) } - func testStringBogusClosingDelimiters() throws { - try AssertParse({ $0.parseSourceFile() }, allowErrors: true) { - #"\\("# - } + func testStringBogusClosingDelimiters() { + AssertParse( + #"\\(#^DIAG^#"#, + diagnostics: [ + DiagnosticSpec(message: "Expected ')' to end expression") + ] + ) - try AssertParse({ $0.parseExpression() }) { + AssertParse( ##""" #"\\("# """## - } + ) - try AssertParse({ $0.parseStringLiteral() }, allowErrors: true) { + AssertParse( #""" - " - """# - } + "#^DIAG^# + """#, + diagnostics: [ + DiagnosticSpec(message: #"Expected '"' in expression"#) + ] + ) - try AssertParse({ $0.parseStringLiteral() }, allowErrors: true) { + AssertParse( #""" - "' - """# - } + "'#^DIAG^# + """#, + diagnostics: [ + DiagnosticSpec(message: #"Expected '"' in expression"#) + ] + ) } - func testMissingArgumentToAttribute() throws { - try AssertParse({ $0.parseSourceFile() }, allowErrors: true) { + func testMissingArgumentToAttribute() { + AssertParse( """ - @_dynamicReplacement( - func test_dynamic_replacement_for2() { + @_dynamicReplacement(#^DIAG_1^# + func #^DIAG_2^#test_dynamic_replacement_for2() { } - """ - } + """, + diagnostics: [ + // FIXME: We should be complaining about the missing ')' for the attribute + DiagnosticSpec(locationMarker: "DIAG_1", message: "Expected 'for'"), + DiagnosticSpec(locationMarker: "DIAG_1", message: "Expected ':'"), + DiagnosticSpec(locationMarker: "DIAG_2", message: "Expected ')'"), + ] + ) } - func testBogusThrowingTernary() throws { - try AssertParse({ $0.parseStatement() }) { + func testBogusThrowingTernary() { + AssertParse( """ do { true ? () : throw opaque_error() } catch _ { } """ - } + ) } - func testAccessors() throws { - try AssertParse({ $0.parseDeclaration() }) { + func testAccessors() { + AssertParse( """ var bad1 : Int { _read async { 0 } } """ - } + ) - try AssertParse({ $0.parseDeclaration() }) { + AssertParse( """ var bad2 : Int { get reasync { 0 } } """ - } + ) } - func testExpressionMember() throws { - try AssertParse({ $0.parseSourceFile() }, allowErrors: true) { + func testExpressionMember() { + AssertParse( """ struct S { - / ###line 25 "line-directive.swift" + #^DIAG^#/ ###line 25 "line-directive.swift" } - """ - } + """, + diagnostics: [ + // FIXME: The diagnostic should not contain a newline. + DiagnosticSpec( + message: """ + Unexpected text ' + / ###line 25 "line-directive.swift"' + """ + ) + ] + ) } - func testBogusProtocolRequirements() throws { - try AssertParse({ $0.parseDeclaration() }) { + func testBogusProtocolRequirements() { + AssertParse( """ protocol P { var prop : Int { get bogus rethrows set } } """ - } + ) } - func testExtraSyntaxInDirective() throws { - try AssertParse({ $0.parseDeclaration() }, allowErrors: true) { + func testExtraSyntaxInDirective() { + AssertParse( """ #if os(iOS) func foo() {} @@ -224,7 +268,7 @@ public class RecoveryTests: XCTestCase { } // expected-error{{unexpected '}' in conditional compilation block}} #endif """ - } + ) } func testRecoverOneExtraLabel() throws { @@ -263,7 +307,7 @@ public class RecoveryTests: XCTestCase { ) } - func testDontRecoverFromDeclKeyword() throws { + func testDontRecoverFromDeclKeyword() { var source = """ (first second third struct: Int) """ @@ -330,7 +374,7 @@ public class RecoveryTests: XCTestCase { } } - func testDontRecoverIfNewlineIsBeforeColon() throws { + func testDontRecoverIfNewlineIsBeforeColon() { var source = """ (first second third : Int) @@ -344,12 +388,16 @@ public class RecoveryTests: XCTestCase { XCTAssertEqual(currentToken.tokenKind, .colon) } - func testTextRecovery() throws { - try AssertParse({ $0.parseSourceFile() }, allowErrors: true) { - """ - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + func testTextRecovery() { + AssertParse( """ - } + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do #^DIAG_1^#eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.#^DIAG_2^# + """, + diagnostics: [ + DiagnosticSpec(locationMarker: "DIAG_1", message: "Expected '{'"), + DiagnosticSpec(locationMarker: "DIAG_2", message: "Expected '}'"), + ] + ) } public func testNoParamsForFunction() throws { diff --git a/Tests/SwiftParserTest/Statements.swift b/Tests/SwiftParserTest/Statements.swift index 766f623325c..4f888a9be7b 100644 --- a/Tests/SwiftParserTest/Statements.swift +++ b/Tests/SwiftParserTest/Statements.swift @@ -3,53 +3,51 @@ import XCTest final class StatementTests: XCTestCase { - func testIf() throws { - try AssertParse({ $0.parseIfStatement() }) { - """ - if let x { } - """ - } + func testIf() { + AssertParse("if let x { }") - try AssertParse({ $0.parseIfStatement() }, allowErrors: true) { + AssertParse( """ - if case* ! = x { + if case#^DIAG^#* ! = x { bar() } - """ - } + """, + diagnostics: [ + DiagnosticSpec(message: "Expected '='"), + DiagnosticSpec(message: "Unexpected text '* ! = x '"), + ] + ) } - func testNestedIfs() throws { - try AssertParse({ $0.parseDeclaration() }) { - let nest = 22 - var example = "func nestThoseIfs() {\n" - for index in (0...nest) { - let indent = String(repeating: " ", count: index + 1) - example += indent + "if false != true {\n" - example += indent + " print \"\\(i)\"\n" - } + func testNestedIfs() { + let nest = 22 + var source = "func nestThoseIfs() {\n" + for index in (0...nest) { + let indent = String(repeating: " ", count: index + 1) + source += indent + "if false != true {\n" + source += indent + " print \"\\(i)\"\n" + } - for index in (0...nest).reversed() { - let indent = String(repeating: " ", count: index + 1) - example += indent + "}\n" - } - example += "}" - return example + for index in (0...nest).reversed() { + let indent = String(repeating: " ", count: index + 1) + source += indent + "}\n" } + source += "}" + AssertParse(source) } - func testDo() throws { - try AssertParse({ $0.parseDoStatement() }) { + func testDo() { + AssertParse( """ do { } """ - } + ) } - func testDoCatch() throws { - try AssertParse({ $0.parseDoStatement() }) { + func testDoCatch() { + AssertParse( """ do { @@ -57,45 +55,41 @@ final class StatementTests: XCTestCase { } """ - } + ) } - func testReturn() throws { - try AssertParse({ $0.parseReturnStatement() }) { - "return" - } + func testReturn() { + AssertParse("return") - try AssertParse({ $0.parseReturnStatement() }) { + AssertParse( #""" return "assert(\(assertChoices.joined(separator: " || ")))" """# - } + ) - try AssertParse({ $0.parseReturnStatement() }) { - "return true ? nil : nil" - } + AssertParse("return true ? nil : nil") } - func testSwitch() throws { - try AssertParse({ $0.parseStatement() }) { + func testSwitch() { + AssertParse( """ switch x { case .A, .B: break } """ - } + ) - try AssertParse({ $0.parseStatement() }) { + AssertParse( """ switch 0 { @$dollar case _: break } """ - } + ) - try AssertParse({ $0.parseStatement() }) { + AssertParse( """ switch x { case .A: @@ -109,6 +103,6 @@ final class StatementTests: XCTestCase { #endif } """ - } + ) } } diff --git a/Tests/SwiftParserTest/Types.swift b/Tests/SwiftParserTest/Types.swift index 815d11e7bbe..11b18cd8e8d 100644 --- a/Tests/SwiftParserTest/Types.swift +++ b/Tests/SwiftParserTest/Types.swift @@ -4,23 +4,26 @@ import XCTest final class TypeTests: XCTestCase { func testClosureParsing() throws { - try AssertParse({ $0.parseType() }) { - "(a, b) -> c" - } + AssertParse( + "(a, b) -> c", + { $0.parseType() } + ) - try AssertParse({ $0.parseType() }) { - "@MainActor (a, b) async throws -> c" - } + AssertParse( + "@MainActor (a, b) async throws -> c", + { $0.parseType() } + ) } func testGenericTypeWithTrivia() throws { // N.B. Whitespace is significant here. - try AssertParse({ $0.parseType() }) { + AssertParse( """ Foo >> - """ - } + """, + { $0.parseType() } + ) } } From 588e3812f52cbeebbfc9207ed18635cc50082327 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Fri, 26 Aug 2022 15:34:56 +0200 Subject: [PATCH 3/6] Move tests from DiagnosticTests to other test files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now that `AssertParse` is able to assert for diagnostics, it doesn’t make sense to keep the diagnostic test cases in a separate test file. --- .../SwiftParser.docc/FixingBugs.md | 2 +- Tests/SwiftParserTest/Declarations.swift | 66 +++++++++++ Tests/SwiftParserTest/DiagnosticTests.swift | 103 ------------------ Tests/SwiftParserTest/Directives.swift | 9 ++ Tests/SwiftParserTest/Expressions.swift | 9 ++ Tests/SwiftParserTest/Statements.swift | 12 ++ 6 files changed, 97 insertions(+), 104 deletions(-) delete mode 100644 Tests/SwiftParserTest/DiagnosticTests.swift diff --git a/Sources/SwiftParser/SwiftParser.docc/FixingBugs.md b/Sources/SwiftParser/SwiftParser.docc/FixingBugs.md index 51a632996e1..86da9e4c1c0 100644 --- a/Sources/SwiftParser/SwiftParser.docc/FixingBugs.md +++ b/Sources/SwiftParser/SwiftParser.docc/FixingBugs.md @@ -57,7 +57,7 @@ Fixing the first case where the parser does not recover according to the user’ To add a new, more contextual diagnostic, perform the following steps. -1. Add a test case to `DiagnosticTests.swift` like the following: +1. Add a test case in `SwiftParserTest` that looks like the following ```swift AssertParse( diff --git a/Tests/SwiftParserTest/Declarations.swift b/Tests/SwiftParserTest/Declarations.swift index 9cfc79fab63..fea910e4876 100644 --- a/Tests/SwiftParserTest/Declarations.swift +++ b/Tests/SwiftParserTest/Declarations.swift @@ -351,6 +351,72 @@ final class DeclarationTests: XCTestCase { """ ) } + + func testMissingColonInFunctionSignature() { + AssertParse( + "(first second #^DIAG^#Int)", + { $0.parseFunctionSignature() }, + diagnostics: [ + DiagnosticSpec(message: "Expected ':' in function parameter") + ] + ) + } + + func testExtraArgumentLabelsInFunctionSignature() { + AssertParse( + "(first second #^DIAG^#third fourth: Int)", + { $0.parseFunctionSignature() }, + diagnostics: [ + DiagnosticSpec(message: "Unexpected text 'third fourth' found in function parameter") + ] + ) + } + + func testMissingClosingParenInFunctionSignature() { + AssertParse( + "(first second: Int#^DIAG^#", + { $0.parseFunctionSignature() }, + diagnostics: [ + DiagnosticSpec(message: "Expected ')' to end parameter clause") + ] + ) + } + + func testMissingOpeningParenInFunctionSignature() { + AssertParse( + "#^DIAG^#first second: Int)", + { $0.parseFunctionSignature() }, + diagnostics: [ + DiagnosticSpec(message: "Expected '(' to start parameter clause") + ] + ) + } + + func testNoParamsForFunction() { + AssertParse( + """ + class MyClass { + func withoutParameters#^DIAG^# + + func withParameters() {} + } + """, + diagnostics: [ + DiagnosticSpec(message: "Expected argument list in function declaration") + ] + ) + } + + func testThrowsInWrongLocation() { + AssertParse( + "() -> #^DIAG^#throws Int", + { $0.parseFunctionSignature() }, + diagnostics: [ + DiagnosticSpec(message: "'throws' may only occur before '->'", fixIts: ["Move 'throws' before '->'"]) + ], + fixedSource: "() throws -> Int" + ) + } } extension Parser.DeclAttributes { diff --git a/Tests/SwiftParserTest/DiagnosticTests.swift b/Tests/SwiftParserTest/DiagnosticTests.swift deleted file mode 100644 index 8dc0f09c32e..00000000000 --- a/Tests/SwiftParserTest/DiagnosticTests.swift +++ /dev/null @@ -1,103 +0,0 @@ -import XCTest -@_spi(RawSyntax) import SwiftSyntax -@_spi(Testing) @_spi(RawSyntax) import SwiftParser -import _SwiftSyntaxTestSupport - -public class DiagnosticTests: XCTestCase { - public func testMissingTokenDiags() { - AssertParse( - "(first second #^DIAG^#Int)", - { $0.parseFunctionSignature() }, - diagnostics: [ - DiagnosticSpec(message: "Expected ':' in function parameter") - ] - ) - } - - public func testUnexpectedDiags() { - AssertParse( - "(first second #^DIAG^#third fourth: Int)", - { $0.parseFunctionSignature() }, - diagnostics: [ - DiagnosticSpec(message: "Unexpected text 'third fourth' found in function parameter") - ] - ) - } - - public func testCStyleForLoop() { - AssertParse( - """ - #^DIAG^#for let x = 0; x < 10; x += 1, y += 1 { - } - """, - diagnostics: [ - DiagnosticSpec(message: "C-style for statement has been removed in Swift 3", highlight: "let x = 0; x < 10; x += 1, y += 1 ") - ] - ) - } - - public func testMissingClosingParen() { - AssertParse( - "(first second: Int#^DIAG^#", - { $0.parseFunctionSignature() }, - diagnostics: [ - DiagnosticSpec(message: "Expected ')' to end parameter clause") - ] - ) - } - - public func testMissingOpeningParen() { - AssertParse( - "#^DIAG^#first second: Int)", - { $0.parseFunctionSignature() }, - diagnostics: [ - DiagnosticSpec(message: "Expected '(' to start parameter clause") - ] - ) - } - - public func testThrowsInWrongLocation() { - AssertParse( - "() -> #^DIAG^#throws Int", - { $0.parseFunctionSignature() }, - diagnostics: [ - DiagnosticSpec(message: "'throws' may only occur before '->'", fixIts: ["Move 'throws' before '->'"]) - ], - fixedSource: "() throws -> Int" - ) - } - - public func testNoParamsForFunction() { - AssertParse( - """ - class MyClass { - func withoutParameters#^DIAG^# - - func withParameters() {} - } - """, - diagnostics: [ - DiagnosticSpec(message: "Expected argument list in function declaration") - ] - ) - } - - func testMissingColonInTernary() { - AssertParse( - "foo ? 1#^DIAG^#", - diagnostics: [ - DiagnosticSpec(message: "Expected ':' after '? ...' in ternary expression") - ] - ) - } - - - public func testUnterminatedPoundIf() { - AssertParse( - "#if test#^DIAG^#", - diagnostics: [ - DiagnosticSpec(message: "Expected '#endif' in declaration") - ] - ) - } -} diff --git a/Tests/SwiftParserTest/Directives.swift b/Tests/SwiftParserTest/Directives.swift index 39d1a0ba29f..cbc2f3b5252 100644 --- a/Tests/SwiftParserTest/Directives.swift +++ b/Tests/SwiftParserTest/Directives.swift @@ -70,4 +70,13 @@ final class DirectiveTests: XCTestCase { """ ) } + + public func testUnterminatedPoundIf() { + AssertParse( + "#if test#^DIAG^#", + diagnostics: [ + DiagnosticSpec(message: "Expected '#endif' in declaration") + ] + ) + } } diff --git a/Tests/SwiftParserTest/Expressions.swift b/Tests/SwiftParserTest/Expressions.swift index ad644896d59..9b1cb35a761 100644 --- a/Tests/SwiftParserTest/Expressions.swift +++ b/Tests/SwiftParserTest/Expressions.swift @@ -285,4 +285,13 @@ final class ExpressionTests: XCTestCase { """ ) } + + func testMissingColonInTernary() { + AssertParse( + "foo ? 1#^DIAG^#", + diagnostics: [ + DiagnosticSpec(message: "Expected ':' after '? ...' in ternary expression") + ] + ) + } } diff --git a/Tests/SwiftParserTest/Statements.swift b/Tests/SwiftParserTest/Statements.swift index 4f888a9be7b..cb385ea9575 100644 --- a/Tests/SwiftParserTest/Statements.swift +++ b/Tests/SwiftParserTest/Statements.swift @@ -105,4 +105,16 @@ final class StatementTests: XCTestCase { """ ) } + + func testCStyleForLoop() { + AssertParse( + """ + #^DIAG^#for let x = 0; x < 10; x += 1, y += 1 { + } + """, + diagnostics: [ + DiagnosticSpec(message: "C-style for statement has been removed in Swift 3", highlight: "let x = 0; x < 10; x += 1, y += 1 ") + ] + ) + } } From da0ffb94978829bb5aaf7c44b27035f6c14f0d54 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Fri, 26 Aug 2022 15:56:12 +0200 Subject: [PATCH 4/6] Move `AssertParse` tests from RecoveryTests.swift to other test files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The goal is to get rid of RecoveryTests.swift altogether once we’ve merged `XCTAssertHasSubstructure` with `AssertParse`. --- Tests/SwiftParserTest/Attributes.swift | 21 ++ Tests/SwiftParserTest/Declarations.swift | 99 ++++++++ Tests/SwiftParserTest/Directives.swift | 18 ++ Tests/SwiftParserTest/Expressions.swift | 84 +++++++ Tests/SwiftParserTest/RecoveryTests.swift | 279 ---------------------- Tests/SwiftParserTest/Statements.swift | 75 ++++++ 6 files changed, 297 insertions(+), 279 deletions(-) create mode 100644 Tests/SwiftParserTest/Attributes.swift diff --git a/Tests/SwiftParserTest/Attributes.swift b/Tests/SwiftParserTest/Attributes.swift new file mode 100644 index 00000000000..ddee6ef0fe6 --- /dev/null +++ b/Tests/SwiftParserTest/Attributes.swift @@ -0,0 +1,21 @@ +@_spi(RawSyntax) import SwiftSyntax +@_spi(RawSyntax) import SwiftParser +import XCTest + +final class AttributeTests: XCTestCase { + func testMissingArgumentToAttribute() { + AssertParse( + """ + @_dynamicReplacement(#^DIAG_1^# + func #^DIAG_2^#test_dynamic_replacement_for2() { + } + """, + diagnostics: [ + // FIXME: We should be complaining about the missing ')' for the attribute + DiagnosticSpec(locationMarker: "DIAG_1", message: "Expected 'for'"), + DiagnosticSpec(locationMarker: "DIAG_1", message: "Expected ':'"), + DiagnosticSpec(locationMarker: "DIAG_2", message: "Expected ')'"), + ] + ) + } +} diff --git a/Tests/SwiftParserTest/Declarations.swift b/Tests/SwiftParserTest/Declarations.swift index fea910e4876..70e6792fa98 100644 --- a/Tests/SwiftParserTest/Declarations.swift +++ b/Tests/SwiftParserTest/Declarations.swift @@ -417,6 +417,105 @@ final class DeclarationTests: XCTestCase { fixedSource: "() throws -> Int" ) } + + func testExtraneousRightBraceRecovery() { + // FIXME: This test case should produce a diagnostics + AssertParse("class ABC { let def = ghi(jkl: mno) } }") + } + + func testMissingSubscriptReturnClause() { + AssertParse( + """ + struct Foo { + subscript(x: String) #^DIAG^#{} + } + """, + diagnostics: [ + // FIXME: This diagnostic should be more contextual + DiagnosticSpec(message: "Expected '->'") + ] + ) + } + + func testClassWithLeadingNumber() { + AssertParse( + """ + class #^DIAG^#23class { + // expected-error@-1 {{class name can only start with a letter or underscore, not a number}} + // expected-error@-2 {{'c' is not a valid digit in integer literal}} + func 24method() {} + // expected-error@-1 {{function name can only start with a letter or underscore, not a number}} + // expected-error@-2 {{'m' is not a valid digit in integer literal}} + } + """, + // FIXME: These are simply bad diagnostics. We should be complaining that identifiers cannot start with digits. + diagnostics: [ + DiagnosticSpec(message: "Expected '' in declaration"), + DiagnosticSpec(message: "Expected '{'"), + DiagnosticSpec(message: "Expected '}'"), + ] + ) + } + + func testAccessors() { + AssertParse( + """ + var bad1 : Int { + _read async { 0 } + } + """ + ) + + AssertParse( + """ + var bad2 : Int { + get reasync { 0 } + } + """ + ) + } + + func testExpressionMember() { + AssertParse( + """ + struct S { + #^DIAG^#/ ###line 25 "line-directive.swift" + } + """, + diagnostics: [ + // FIXME: The diagnostic should not contain a newline. + DiagnosticSpec( + message: """ + Unexpected text ' + / ###line 25 "line-directive.swift"' + """ + ) + ] + ) + } + + func testBogusProtocolRequirements() { + // FIXME: This test case should produce a diagnostics + AssertParse( + """ + protocol P { + var prop : Int { get bogus rethrows set } + } + """ + ) + } + + func testTextRecovery() { + AssertParse( + """ + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do #^DIAG_1^#eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.#^DIAG_2^# + """, + diagnostics: [ + DiagnosticSpec(locationMarker: "DIAG_1", message: "Expected '{'"), + DiagnosticSpec(locationMarker: "DIAG_2", message: "Expected '}'"), + ] + ) + } } extension Parser.DeclAttributes { diff --git a/Tests/SwiftParserTest/Directives.swift b/Tests/SwiftParserTest/Directives.swift index cbc2f3b5252..0aed319b310 100644 --- a/Tests/SwiftParserTest/Directives.swift +++ b/Tests/SwiftParserTest/Directives.swift @@ -79,4 +79,22 @@ final class DirectiveTests: XCTestCase { ] ) } + + func testExtraSyntaxInDirective() { + // FIXME: This test case should produce a diagnostics + + AssertParse( + """ + #if os(iOS) + func foo() {} + } // expected-error{{unexpected '}' in conditional compilation block}} + #else + func bar() {} + func baz() {} + } // expected-error{{unexpected '}' in conditional compilation block}} + #endif + """ + ) + } + } diff --git a/Tests/SwiftParserTest/Expressions.swift b/Tests/SwiftParserTest/Expressions.swift index 9b1cb35a761..410e5e0a4f2 100644 --- a/Tests/SwiftParserTest/Expressions.swift +++ b/Tests/SwiftParserTest/Expressions.swift @@ -278,6 +278,48 @@ final class ExpressionTests: XCTestCase { ) } + func testSingleQuoteStringLiteral() { + // FIXME: This test case should produce a diagnostics + AssertParse( + #""" + 'red' + """# + ) + } + + func testStringBogusClosingDelimiters() { + AssertParse( + #"\\(#^DIAG^#"#, + diagnostics: [ + DiagnosticSpec(message: "Expected ')' to end expression") + ] + ) + + AssertParse( + ##""" + #"\\("# + """## + ) + + AssertParse( + #""" + "#^DIAG^# + """#, + diagnostics: [ + DiagnosticSpec(message: #"Expected '"' in expression"#) + ] + ) + + AssertParse( + #""" + "'#^DIAG^# + """#, + diagnostics: [ + DiagnosticSpec(message: #"Expected '"' in expression"#) + ] + ) + } + func testRangeSubscript() { AssertParse( """ @@ -294,4 +336,46 @@ final class ExpressionTests: XCTestCase { ] ) } + + func testBogusKeypathBaseRecovery() { + AssertParse( + #""" + func nestThoseIfs() {\n if false != true {\n print "\(i)\"\n#^DIAG^# + """#, + diagnostics: [ + DiagnosticSpec(message: #"Expected '"' in expression"#), + DiagnosticSpec(message: "Expected '}'"), + DiagnosticSpec(message: "Expected '}'"), + ] + ) + } + + func testMissingArrowInArrowExpr() { + AssertParse( + "[(Int) -> #^DIAG^#throws Int]()", + diagnostics: [ + // FIXME: We should suggest to move 'throws' in front of '->' + DiagnosticSpec(message: "Unexpected text 'throws Int' found in expression") + ] + ) + + AssertParse( + "let _ = [Int throws #^DIAG^#Int]()", + diagnostics: [ + DiagnosticSpec(message: "Expected '->' in expression") + ] + ) + } + + func testBogusThrowingTernary() { + // FIXME: This test case should produce a diagnostics + AssertParse( + """ + do { + true ? () : throw opaque_error() + } catch _ { + } + """ + ) + } } diff --git a/Tests/SwiftParserTest/RecoveryTests.swift b/Tests/SwiftParserTest/RecoveryTests.swift index a12715ae0a6..df9858af140 100644 --- a/Tests/SwiftParserTest/RecoveryTests.swift +++ b/Tests/SwiftParserTest/RecoveryTests.swift @@ -4,273 +4,6 @@ import XCTest import _SwiftSyntaxTestSupport public class RecoveryTests: XCTestCase { - func testTopLevelCaseRecovery() { - AssertParse( - "/*#-editable-code Swift Platground editable area*/default/*#-end-editable-code*/" - ) - - AssertParse("case:") - - AssertParse( - #""" - case: { ("Hello World") } - """# - ) - } - - func testBogusKeypathBaseRecovery() { - AssertParse( - #""" - func nestThoseIfs() {\n if false != true {\n print "\(i)\"\n#^DIAG^# - """#, - diagnostics: [ - DiagnosticSpec(message: #"Expected '"' in expression"#), - DiagnosticSpec(message: "Expected '}'"), - DiagnosticSpec(message: "Expected '}'"), - ] - ) - } - - func testExtraneousRightBraceRecovery() { - AssertParse("class ABC { let def = ghi(jkl: mno) } }") - } - - func testMissingIfClauseIntroducer() { - AssertParse("if _ = 42 {}") - } - - func testMissingSubscriptReturnClause() { - AssertParse( - """ - struct Foo { - subscript(x: String) #^DIAG^#{} - } - """, - diagnostics: [ - DiagnosticSpec(message: "Expected '->'") - ] - ) - } - - func testSingleQuoteStringLiteral() { - AssertParse( - #""" - 'red' - """# - ) - } - - func testClassWithLeadingNumber() { - AssertParse( - """ - class #^DIAG^#23class { - // expected-error@-1 {{class name can only start with a letter or underscore, not a number}} - // expected-error@-2 {{'c' is not a valid digit in integer literal}} - func 24method() {} - // expected-error@-1 {{function name can only start with a letter or underscore, not a number}} - // expected-error@-2 {{'m' is not a valid digit in integer literal}} - } - """, - // FIXME: These are simply bad diagnostics. We should be complaining that identifiers cannot start with digits. - diagnostics: [ - DiagnosticSpec(message: "Expected '' in declaration"), - DiagnosticSpec(message: "Expected '{'"), - DiagnosticSpec(message: "Expected '}'"), - ] - ) - } - - func testAttributesOnStatements() { - AssertParse( - """ - func test1() { - @s return - } - func test2() { - @unknown return - } - """ - ) - } - - func testMissingArrowInArrowExpr() { - AssertParse( - "[(Int) -> #^DIAG^#throws Int]()", - diagnostics: [ - // FIXME: We should suggest to move 'throws' in front of '->' - DiagnosticSpec(message: "Unexpected text 'throws Int' found in expression") - ] - ) - - AssertParse( - "let _ = [Int throws #^DIAG^#Int]()", - diagnostics: [ - DiagnosticSpec(message: "Expected '->' in expression") - ] - ) - } - - func testBogusSwitchStatement() { - AssertParse( - """ - switch x { - print() - #if true - print() - #endif - case .A, .B: - break - } - """ - ) - - AssertParse( - """ - switch x { - print() - #if ENABLE_C - case .NOT_EXIST: - break - case .C: - break - #endif - case .A, .B: - break - } - """ - ) - } - - func testBogusLineLabel() { - AssertParse( - """ - LABEL: - """ - ) - } - - func testStringBogusClosingDelimiters() { - AssertParse( - #"\\(#^DIAG^#"#, - diagnostics: [ - DiagnosticSpec(message: "Expected ')' to end expression") - ] - ) - - AssertParse( - ##""" - #"\\("# - """## - ) - - AssertParse( - #""" - "#^DIAG^# - """#, - diagnostics: [ - DiagnosticSpec(message: #"Expected '"' in expression"#) - ] - ) - - AssertParse( - #""" - "'#^DIAG^# - """#, - diagnostics: [ - DiagnosticSpec(message: #"Expected '"' in expression"#) - ] - ) - } - - func testMissingArgumentToAttribute() { - AssertParse( - """ - @_dynamicReplacement(#^DIAG_1^# - func #^DIAG_2^#test_dynamic_replacement_for2() { - } - """, - diagnostics: [ - // FIXME: We should be complaining about the missing ')' for the attribute - DiagnosticSpec(locationMarker: "DIAG_1", message: "Expected 'for'"), - DiagnosticSpec(locationMarker: "DIAG_1", message: "Expected ':'"), - DiagnosticSpec(locationMarker: "DIAG_2", message: "Expected ')'"), - ] - ) - } - - func testBogusThrowingTernary() { - AssertParse( - """ - do { - true ? () : throw opaque_error() - } catch _ { - } - """ - ) - } - - func testAccessors() { - AssertParse( - """ - var bad1 : Int { - _read async { 0 } - } - """ - ) - - AssertParse( - """ - var bad2 : Int { - get reasync { 0 } - } - """ - ) - } - - func testExpressionMember() { - AssertParse( - """ - struct S { - #^DIAG^#/ ###line 25 "line-directive.swift" - } - """, - diagnostics: [ - // FIXME: The diagnostic should not contain a newline. - DiagnosticSpec( - message: """ - Unexpected text ' - / ###line 25 "line-directive.swift"' - """ - ) - ] - ) - } - - func testBogusProtocolRequirements() { - AssertParse( - """ - protocol P { - var prop : Int { get bogus rethrows set } - } - """ - ) - } - - func testExtraSyntaxInDirective() { - AssertParse( - """ - #if os(iOS) - func foo() {} - } // expected-error{{unexpected '}' in conditional compilation block}} - #else - func bar() {} - func baz() {} - } // expected-error{{unexpected '}' in conditional compilation block}} - #endif - """ - ) - } - func testRecoverOneExtraLabel() throws { try XCTAssertHasSubstructure( "(first second third: Int)", @@ -388,18 +121,6 @@ public class RecoveryTests: XCTestCase { XCTAssertEqual(currentToken.tokenKind, .colon) } - func testTextRecovery() { - AssertParse( - """ - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do #^DIAG_1^#eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.#^DIAG_2^# - """, - diagnostics: [ - DiagnosticSpec(locationMarker: "DIAG_1", message: "Expected '{'"), - DiagnosticSpec(locationMarker: "DIAG_2", message: "Expected '}'"), - ] - ) - } - public func testNoParamsForFunction() throws { let source = """ class MyClass { diff --git a/Tests/SwiftParserTest/Statements.swift b/Tests/SwiftParserTest/Statements.swift index cb385ea9575..96e8f9fadba 100644 --- a/Tests/SwiftParserTest/Statements.swift +++ b/Tests/SwiftParserTest/Statements.swift @@ -117,4 +117,79 @@ final class StatementTests: XCTestCase { ] ) } + + func testTopLevelCaseRecovery() { + // FIXME: These test cases should produce diagnostics + AssertParse( + "/*#-editable-code Swift Platground editable area*/default/*#-end-editable-code*/" + ) + + AssertParse("case:") + + AssertParse( + #""" + case: { ("Hello World") } + """# + ) + } + + func testMissingIfClauseIntroducer() { + // FIXME: This test case should produce a diagnostics + AssertParse("if _ = 42 {}") + } + + func testAttributesOnStatements() { + // FIXME: This test case should produce a diagnostics + AssertParse( + """ + func test1() { + @s return + } + func test2() { + @unknown return + } + """ + ) + } + + func testBogusSwitchStatement() { + // FIXME: This test case should produce a diagnostics + AssertParse( + """ + switch x { + print() + #if true + print() + #endif + case .A, .B: + break + } + """ + ) + + AssertParse( + """ + switch x { + print() + #if ENABLE_C + case .NOT_EXIST: + break + case .C: + break + #endif + case .A, .B: + break + } + """ + ) + } + + // FIXME: This test case should produce a diagnostic + func testBogusLineLabel() { + AssertParse( + """ + LABEL: + """ + ) + } } From b4f512492c216183895740d0baaf0e126c2d64fa Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Sun, 28 Aug 2022 08:47:02 +0200 Subject: [PATCH 5/6] In `AssertParse`, allow asserting for a substructure that should occur in a syntax tree --- .../SwiftParser.docc/FixingBugs.md | 18 +- .../Syntax+Assertions.swift | 62 +----- Tests/SwiftParserTest/Assertions.swift | 18 ++ Tests/SwiftParserTest/RecoveryTests.swift | 187 ++++++++++-------- 4 files changed, 132 insertions(+), 153 deletions(-) diff --git a/Sources/SwiftParser/SwiftParser.docc/FixingBugs.md b/Sources/SwiftParser/SwiftParser.docc/FixingBugs.md index 86da9e4c1c0..98a3df79b38 100644 --- a/Sources/SwiftParser/SwiftParser.docc/FixingBugs.md +++ b/Sources/SwiftParser/SwiftParser.docc/FixingBugs.md @@ -27,20 +27,14 @@ Diagnostics are produced when the parsed syntax tree contains missing or unexpec 1. Add a test case in `SwiftParserTest` that looks like the following ```swift - let source = """ - <#your code that produces an invalid syntax tree#> - """ - - let tree = withParser(source: source) { - Syntax(raw: $0.parseSourceFile().raw) - } - XCTAssertHasSubstructure( - tree, - <#create a syntax node that you expect the tree to have#> + AssertParse( + """ + <#your code that produces an invalid syntax tree#> + """, + substructure: <#create a syntax node that you expect the tree to have#> ) ``` -2. Optional: Reduce the test case even further by deleting more source code and calling into a specific production of the parser instead of `Parser.parseSourceFile` -3. Run the test case and navigate the debugger to the place that produced the invalid syntax node. +2. Run the test case and navigate the debugger to the place that produced the invalid syntax node. ## Unhelpful Diagnostic Produced diff --git a/Sources/_SwiftSyntaxTestSupport/Syntax+Assertions.swift b/Sources/_SwiftSyntaxTestSupport/Syntax+Assertions.swift index e9b9c99aecc..90d7c42d0c7 100644 --- a/Sources/_SwiftSyntaxTestSupport/Syntax+Assertions.swift +++ b/Sources/_SwiftSyntaxTestSupport/Syntax+Assertions.swift @@ -33,60 +33,6 @@ public func XCTAssertNextIsNil(_ iterator: inout Ite XCTAssertNil(iterator.next()) } -/// Verifies that the tree parsed from `actual` has the same structure as -/// `expected` when parsed with `parse`, ie. it has the same structure and -/// optionally the same trivia (if `includeTrivia` is set). -public func XCTAssertSameStructure( - _ actual: String, - parse: (String) throws -> Syntax, - _ expected: Syntax, - includeTrivia: Bool = false, - file: StaticString = #filePath, line: UInt = #line -) throws { - let actualTree = try parse(actual) - XCTAssertSameStructure(actualTree, expected, includeTrivia: includeTrivia, file: file, line: line) -} - -/// Verifies that two trees are equivalent, ie. they have the same structure -/// and optionally the same trivia if `includeTrivia` is set. -public func XCTAssertSameStructure( - _ actual: ActualTree, - _ expected: ExpectedTree, - includeTrivia: Bool = false, - file: StaticString = #filePath, line: UInt = #line -) - where ActualTree: SyntaxProtocol, ExpectedTree: SyntaxProtocol -{ - let diff = actual.findFirstDifference(baseline: expected, includeTrivia: includeTrivia) - XCTAssertNil(diff, diff!.debugDescription, file: file, line: line) -} - -/// See `SubtreeMatcher.assertSameStructure`. -public func XCTAssertHasSubstructure( - _ markedText: String, - parse: (String) throws -> Syntax, - afterMarker: String? = nil, - _ expected: ExpectedTree, - includeTrivia: Bool = false, - file: StaticString = #filePath, line: UInt = #line -) throws { - let subtreeMatcher = try SubtreeMatcher(markedText, parse: parse) - try subtreeMatcher.assertSameStructure(afterMarker: afterMarker, Syntax(expected), file: file, line: line) -} - -/// See `SubtreeMatcher.assertSameStructure`. -public func XCTAssertHasSubstructure( - _ actualTree: ActualTree, - _ expected: ExpectedTree, - includeTrivia: Bool = false, - file: StaticString = #filePath, line: UInt = #line -) throws - where ActualTree: SyntaxProtocol, ExpectedTree: SyntaxProtocol -{ - let subtreeMatcher = SubtreeMatcher(Syntax(actualTree)) - try subtreeMatcher.assertSameStructure(Syntax(expected), file: file, line: line) -} - /// Allows matching a subtrees of the given `markedText` against /// `baseline`/`expected` trees, where a combination of markers and the type /// of the `expected` tree is used to first find the subtree to match. Note @@ -141,8 +87,8 @@ public struct SubtreeMatcher { self.actualTree = try parse(text) } - public init(_ actualTree: Syntax) { - self.markers = ["DEFAULT": 0] + public init(_ actualTree: Syntax, markers: [String: Int]) { + self.markers = markers.isEmpty ? ["DEFAULT": 0] : markers self.actualTree = actualTree } @@ -162,8 +108,8 @@ public struct SubtreeMatcher { return subtree.findFirstDifference(baseline: baseline, includeTrivia: includeTrivia) } - /// Same as `XCTAssertSameStructure`, but uses the subtree found from parsing - /// the text passed into `init(markedText:)` as the `actual` tree. + /// Verifies that the the subtree found from parsing the text passed into + /// `init(markedText:)` has the same structure as `expected`. public func assertSameStructure(afterMarker: String? = nil, _ expected: Syntax, includeTrivia: Bool = false, file: StaticString = #filePath, line: UInt = #line) throws { let diff = try findFirstDifference(afterMarker: afterMarker, baseline: expected, includeTrivia: includeTrivia) diff --git a/Tests/SwiftParserTest/Assertions.swift b/Tests/SwiftParserTest/Assertions.swift index 4629509457d..b1f756eec65 100644 --- a/Tests/SwiftParserTest/Assertions.swift +++ b/Tests/SwiftParserTest/Assertions.swift @@ -160,9 +160,11 @@ func AssertDiagnostic( /// These markers are removed before parsing the source file. /// By default, `DiagnosticSpec` asserts that the diagnostics is produced at a location marked by `#^DIAG^#`. /// `parseSyntax` can be used to adjust the production that should be used as the entry point to parse the source code. +/// If `substructure` is not `nil`, asserts that the parsed syntax tree contains this substructure. func AssertParse( _ markedSource: String, _ parseSyntax: (inout Parser) -> Node = { $0.parseSourceFile() }, + substructure expectedSubstructure: Syntax? = nil, diagnostics expectedDiagnostics: [DiagnosticSpec] = [], fixedSource expectedFixedSource: String? = nil, file: StaticString = #file, @@ -175,12 +177,26 @@ func AssertParse( var parser = Parser(buf) withExtendedLifetime(parser) { let tree = Syntax(raw: parseSyntax(&parser).raw) + + // Round-trip AssertStringsEqualWithDiff("\(tree)", source, additionalInfo: """ Source failed to round-trip. Actual syntax tree: \(tree.recursiveDescription) """, file: file, line: line) + + // Substructure + if let expectedSubstructure = expectedSubstructure { + let subtreeMatcher = SubtreeMatcher(Syntax(tree), markers: [:]) + do { + try subtreeMatcher.assertSameStructure(Syntax(expectedSubstructure), file: file, line: line) + } catch { + XCTFail("Matching for a subtree failed with error: \(error)", file: file, line: line) + } + } + + // Diagnostics let diags = ParseDiagnosticsGenerator.diagnostics(for: tree) XCTAssertEqual(diags.count, expectedDiagnostics.count, """ Expected \(expectedDiagnostics.count) diagnostics but received \(diags.count): @@ -189,6 +205,8 @@ func AssertParse( for (diag, expectedDiag) in zip(diags, expectedDiagnostics) { AssertDiagnostic(diag, in: tree, markerLocations: markerLocations, expected: expectedDiag, file: file, line: line) } + + // Applying Fix-Its if let expectedFixedSource = expectedFixedSource { let fixedSource = FixItApplier.applyFixes(in: diags, to: tree).description AssertStringsEqualWithDiff(fixedSource, expectedFixedSource, file: file, line: line) diff --git a/Tests/SwiftParserTest/RecoveryTests.swift b/Tests/SwiftParserTest/RecoveryTests.swift index df9858af140..4b80f5b9632 100644 --- a/Tests/SwiftParserTest/RecoveryTests.swift +++ b/Tests/SwiftParserTest/RecoveryTests.swift @@ -4,11 +4,11 @@ import XCTest import _SwiftSyntaxTestSupport public class RecoveryTests: XCTestCase { - func testRecoverOneExtraLabel() throws { - try XCTAssertHasSubstructure( - "(first second third: Int)", - parse: { withParser(source: $0) { Syntax(raw: $0.parseFunctionSignature().raw) } }, - FunctionParameterSyntax( + func testRecoverOneExtraLabel() { + AssertParse( + "(first second #^DIAG^#third: Int)", + { $0.parseFunctionSignature() }, + substructure: Syntax(FunctionParameterSyntax( attributes: nil, firstName: TokenSyntax.identifier("first"), secondName: TokenSyntax.identifier("second"), @@ -18,15 +18,18 @@ public class RecoveryTests: XCTestCase { ellipsis: nil, defaultArgument: nil, trailingComma: nil - ) + )), + diagnostics: [ + DiagnosticSpec(message: "Unexpected text 'third' found in function parameter") + ] ) } - func testRecoverTwoExtraLabels() throws { - try XCTAssertHasSubstructure( - "(first second third fourth: Int)", - parse: { withParser(source: $0) { Syntax(raw: $0.parseFunctionSignature().raw) } }, - FunctionParameterSyntax( + func testRecoverTwoExtraLabels() { + AssertParse( + "(first second #^DIAG^#third fourth: Int)", + { $0.parseFunctionSignature() }, + substructure: Syntax(FunctionParameterSyntax( attributes: nil, firstName: TokenSyntax.identifier("first"), secondName: TokenSyntax.identifier("second"), @@ -36,30 +39,42 @@ public class RecoveryTests: XCTestCase { ellipsis: nil, defaultArgument: nil, trailingComma: nil - ) + )), + diagnostics: [ + DiagnosticSpec(message: "Unexpected text 'third fourth' found in function parameter") + ] ) } func testDontRecoverFromDeclKeyword() { - var source = """ - (first second third struct: Int) - """ - let (_, currentToken): (RawFunctionSignatureSyntax, Lexer.Lexeme) = - source.withUTF8 { buffer in - var parser = Parser(buffer) - return (parser.parseFunctionSignature(), parser.currentToken) - } - - // The 'struct' keyword should be taken as an indicator that a new decl - // starts here, so `parseFunctionSignature` shouldn't eat it. - XCTAssertEqual(currentToken.tokenKind, .structKeyword) + AssertParse( + "func foo(first second #^MISSING_COLON^#third #^MISSING_RPAREN^#struct#^MISSING_IDENTIFIER^##^BRACES^#: Int) {}", + substructure: Syntax(FunctionParameterSyntax( + attributes: nil, + firstName: .identifier("first"), + secondName: .identifier("second"), + colon: .colonToken(presence: .missing), + type: TypeSyntax(SimpleTypeIdentifierSyntax(name: .identifier("third"), genericArgumentClause: nil)), + ellipsis: nil, + defaultArgument: nil, + trailingComma: nil + )), + diagnostics: [ + DiagnosticSpec(locationMarker: "MISSING_COLON", message: "Expected ':' in function parameter"), + DiagnosticSpec(locationMarker: "MISSING_RPAREN", message: "Expected ')' to end parameter clause"), + // FIXME: We should issues something like "Expected identifier in declaration" + DiagnosticSpec(locationMarker: "MISSING_IDENTIFIER", message: "Expected '' in declaration"), + DiagnosticSpec(locationMarker: "BRACES", message: "Expected '{'"), + DiagnosticSpec(locationMarker: "BRACES", message: "Expected '}'"), + ] + ) } - func testRecoverFromParens() throws { - try XCTAssertHasSubstructure( - "(first second [third fourth]: Int)", - parse: { withParser(source: $0) { Syntax(raw: $0.parseFunctionSignature().raw) } }, - FunctionParameterSyntax( + func testRecoverFromParens() { + AssertParse( + "(first second #^DIAG^#[third fourth]: Int)", + { $0.parseFunctionSignature() }, + substructure: Syntax(FunctionParameterSyntax( attributes: nil, firstName: TokenSyntax.identifier("first"), secondName: TokenSyntax.identifier("second"), @@ -74,68 +89,71 @@ public class RecoveryTests: XCTestCase { ellipsis: nil, defaultArgument: nil, trailingComma: nil - ) + )), + diagnostics: [ + DiagnosticSpec(message: "Unexpected text '[third fourth]' found in function parameter") + ] ) } - func testDontRecoverFromUnbalancedParens() throws { - let source = """ - (first second [third fourth: Int) - """ - try withParser(source: source) { parser in - let signature = Syntax(raw: parser.parseFunctionSignature().raw) - let currentToken = parser.currentToken - XCTAssertEqual(currentToken.tokenKind, .identifier) - XCTAssertEqual(currentToken.tokenText, "fourth") - try XCTAssertHasSubstructure( - signature, - FunctionParameterSyntax( - attributes: nil, - firstName: TokenSyntax.identifier("first"), - secondName: TokenSyntax.identifier("second"), - colon: TokenSyntax(.colon, presence: .missing), - type: TypeSyntax(ArrayTypeSyntax( - leftSquareBracket: TokenSyntax.leftSquareBracketToken(), - elementType: TypeSyntax(SimpleTypeIdentifierSyntax(name: TokenSyntax.identifier("third"), genericArgumentClause: nil)), - rightSquareBracket: TokenSyntax(.rightSquareBracket, presence: .missing) - )), - ellipsis: nil, - defaultArgument: nil, - trailingComma: nil - ) - ) - } + func testDontRecoverFromUnbalancedParens() { + AssertParse( + "func foo(first second #^COLON^#[third #^RSQUARE_COLON^#fourth: Int) {}", + substructure: Syntax(FunctionParameterSyntax( + attributes: nil, + firstName: TokenSyntax.identifier("first"), + secondName: TokenSyntax.identifier("second"), + colon: TokenSyntax(.colon, presence: .missing), + type: TypeSyntax(ArrayTypeSyntax( + leftSquareBracket: TokenSyntax.leftSquareBracketToken(), + elementType: TypeSyntax(SimpleTypeIdentifierSyntax(name: TokenSyntax.identifier("third"), genericArgumentClause: nil)), + rightSquareBracket: TokenSyntax(.rightSquareBracket, presence: .missing) + )), + ellipsis: nil, + defaultArgument: nil, + trailingComma: nil + )), + diagnostics: [ + DiagnosticSpec(locationMarker: "COLON", message: "Expected ':' in function parameter"), + DiagnosticSpec(locationMarker: "RSQUARE_COLON" , message: "Expected ']' to end type"), + DiagnosticSpec(locationMarker: "RSQUARE_COLON", message: "Expected ')' to end parameter clause"), + ] + ) } func testDontRecoverIfNewlineIsBeforeColon() { - var source = """ - (first second third - : Int) - """ - let (_, currentToken): (RawFunctionSignatureSyntax, Lexer.Lexeme) = - source.withUTF8 { buffer in - var parser = Parser(buffer) - return (parser.parseFunctionSignature(), parser.currentToken) - } - - XCTAssertEqual(currentToken.tokenKind, .colon) + AssertParse( + """ + func foo(first second #^COLON^#third#^RPAREN^# + : Int) {} + """, + substructure: Syntax(FunctionParameterSyntax( + attributes: nil, + firstName: TokenSyntax.identifier("first"), + secondName: TokenSyntax.identifier("second"), + colon: TokenSyntax(.colon, presence: .missing), + type: TypeSyntax(SimpleTypeIdentifierSyntax(name: TokenSyntax.identifier("third"), genericArgumentClause: nil)), + ellipsis: nil, + defaultArgument: nil, + trailingComma: nil + )), + diagnostics: [ + DiagnosticSpec(locationMarker: "COLON", message: "Expected ':' in function parameter"), + DiagnosticSpec(locationMarker: "RPAREN", message: "Expected ')' to end parameter clause"), + ] + ) } - public func testNoParamsForFunction() throws { - let source = """ - class MyClass { - func withoutParameters - - func withParameters() {} - } - """ + public func testNoParamsForFunction() { + AssertParse( + """ + class MyClass { + func withoutParameters#^DIAG^# - let classDecl = withParser(source: source) { - Syntax(raw: $0.parseDeclaration().raw) - } - try XCTAssertHasSubstructure( - classDecl, - FunctionDeclSyntax( + func withParameters() {} + } + """, + substructure: Syntax(FunctionDeclSyntax( attributes: nil, modifiers: nil, funcKeyword: .funcKeyword(), @@ -153,7 +171,10 @@ public class RecoveryTests: XCTestCase { ), genericWhereClause: nil, body: nil - ) + )), + diagnostics: [ + DiagnosticSpec(message: "Expected argument list in function declaration") + ] ) } } From 459da602dc0752e7e96bea134fff7c1d85dd3fc3 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Fri, 26 Aug 2022 16:33:59 +0200 Subject: [PATCH 6/6] Move test cases from RecoveryTests.swift to Declaration.swift These tests now also use `AssertParse` and should thus live next to all the other declaration parsing tests. --- Tests/SwiftParserTest/Declarations.swift | 159 +++++++++++++++++++ Tests/SwiftParserTest/RecoveryTests.swift | 180 ---------------------- 2 files changed, 159 insertions(+), 180 deletions(-) delete mode 100644 Tests/SwiftParserTest/RecoveryTests.swift diff --git a/Tests/SwiftParserTest/Declarations.swift b/Tests/SwiftParserTest/Declarations.swift index 70e6792fa98..96e2f5a0675 100644 --- a/Tests/SwiftParserTest/Declarations.swift +++ b/Tests/SwiftParserTest/Declarations.swift @@ -401,6 +401,25 @@ final class DeclarationTests: XCTestCase { func withParameters() {} } """, + substructure: Syntax(FunctionDeclSyntax( + attributes: nil, + modifiers: nil, + funcKeyword: .funcKeyword(), + identifier: .identifier("withoutParameters"), + genericParameterClause: nil, + signature: FunctionSignatureSyntax( + input: ParameterClauseSyntax( + leftParen: .leftParenToken(presence: .missing), + parameterList: FunctionParameterListSyntax([]), + rightParen: .rightParenToken(presence: .missing) + ), + asyncOrReasyncKeyword: nil, + throwsOrRethrowsKeyword: nil, + output: nil + ), + genericWhereClause: nil, + body: nil + )), diagnostics: [ DiagnosticSpec(message: "Expected argument list in function declaration") ] @@ -516,6 +535,146 @@ final class DeclarationTests: XCTestCase { ] ) } + + func testRecoverOneExtraLabel() { + AssertParse( + "(first second #^DIAG^#third: Int)", + { $0.parseFunctionSignature() }, + substructure: Syntax(FunctionParameterSyntax( + attributes: nil, + firstName: TokenSyntax.identifier("first"), + secondName: TokenSyntax.identifier("second"), + UnexpectedNodesSyntax([Syntax(TokenSyntax.identifier("third"))]), + colon: TokenSyntax.colonToken(), + type: TypeSyntax(SimpleTypeIdentifierSyntax(name: TokenSyntax.identifier("Int"), genericArgumentClause: nil)), + ellipsis: nil, + defaultArgument: nil, + trailingComma: nil + )), + diagnostics: [ + DiagnosticSpec(message: "Unexpected text 'third' found in function parameter") + ] + ) + } + + func testRecoverTwoExtraLabels() { + AssertParse( + "(first second #^DIAG^#third fourth: Int)", + { $0.parseFunctionSignature() }, + substructure: Syntax(FunctionParameterSyntax( + attributes: nil, + firstName: TokenSyntax.identifier("first"), + secondName: TokenSyntax.identifier("second"), + UnexpectedNodesSyntax([Syntax(TokenSyntax.identifier("third")), Syntax(TokenSyntax.identifier("fourth"))]), + colon: TokenSyntax.colonToken(), + type: TypeSyntax(SimpleTypeIdentifierSyntax(name: TokenSyntax.identifier("Int"), genericArgumentClause: nil)), + ellipsis: nil, + defaultArgument: nil, + trailingComma: nil + )), + diagnostics: [ + DiagnosticSpec(message: "Unexpected text 'third fourth' found in function parameter") + ] + ) + } + + func testDontRecoverFromDeclKeyword() { + AssertParse( + "func foo(first second #^MISSING_COLON^#third #^MISSING_RPAREN^#struct#^MISSING_IDENTIFIER^##^BRACES^#: Int) {}", + substructure: Syntax(FunctionParameterSyntax( + attributes: nil, + firstName: .identifier("first"), + secondName: .identifier("second"), + colon: .colonToken(presence: .missing), + type: TypeSyntax(SimpleTypeIdentifierSyntax(name: .identifier("third"), genericArgumentClause: nil)), + ellipsis: nil, + defaultArgument: nil, + trailingComma: nil + )), + diagnostics: [ + DiagnosticSpec(locationMarker: "MISSING_COLON", message: "Expected ':' in function parameter"), + DiagnosticSpec(locationMarker: "MISSING_RPAREN", message: "Expected ')' to end parameter clause"), + // FIXME: We should issues something like "Expected identifier in declaration" + DiagnosticSpec(locationMarker: "MISSING_IDENTIFIER", message: "Expected '' in declaration"), + DiagnosticSpec(locationMarker: "BRACES", message: "Expected '{'"), + DiagnosticSpec(locationMarker: "BRACES", message: "Expected '}'"), + ] + ) + } + + func testRecoverFromParens() { + AssertParse( + "(first second #^DIAG^#[third fourth]: Int)", + { $0.parseFunctionSignature() }, + substructure: Syntax(FunctionParameterSyntax( + attributes: nil, + firstName: TokenSyntax.identifier("first"), + secondName: TokenSyntax.identifier("second"), + UnexpectedNodesSyntax([ + Syntax(TokenSyntax.leftSquareBracketToken()), + Syntax(TokenSyntax.identifier("third")), + Syntax(TokenSyntax.identifier("fourth")), + Syntax(TokenSyntax.rightSquareBracketToken()) + ]), + colon: TokenSyntax.colonToken(), + type: TypeSyntax(SimpleTypeIdentifierSyntax(name: TokenSyntax.identifier("Int"), genericArgumentClause: nil)), + ellipsis: nil, + defaultArgument: nil, + trailingComma: nil + )), + diagnostics: [ + DiagnosticSpec(message: "Unexpected text '[third fourth]' found in function parameter") + ] + ) + } + + func testDontRecoverFromUnbalancedParens() { + AssertParse( + "func foo(first second #^COLON^#[third #^RSQUARE_COLON^#fourth: Int) {}", + substructure: Syntax(FunctionParameterSyntax( + attributes: nil, + firstName: TokenSyntax.identifier("first"), + secondName: TokenSyntax.identifier("second"), + colon: TokenSyntax(.colon, presence: .missing), + type: TypeSyntax(ArrayTypeSyntax( + leftSquareBracket: TokenSyntax.leftSquareBracketToken(), + elementType: TypeSyntax(SimpleTypeIdentifierSyntax(name: TokenSyntax.identifier("third"), genericArgumentClause: nil)), + rightSquareBracket: TokenSyntax(.rightSquareBracket, presence: .missing) + )), + ellipsis: nil, + defaultArgument: nil, + trailingComma: nil + )), + diagnostics: [ + DiagnosticSpec(locationMarker: "COLON", message: "Expected ':' in function parameter"), + DiagnosticSpec(locationMarker: "RSQUARE_COLON" , message: "Expected ']' to end type"), + DiagnosticSpec(locationMarker: "RSQUARE_COLON", message: "Expected ')' to end parameter clause"), + ] + ) + } + + func testDontRecoverIfNewlineIsBeforeColon() { + AssertParse( + """ + func foo(first second #^COLON^#third#^RPAREN^# + : Int) {} + """, + substructure: Syntax(FunctionParameterSyntax( + attributes: nil, + firstName: TokenSyntax.identifier("first"), + secondName: TokenSyntax.identifier("second"), + colon: TokenSyntax(.colon, presence: .missing), + type: TypeSyntax(SimpleTypeIdentifierSyntax(name: TokenSyntax.identifier("third"), genericArgumentClause: nil)), + ellipsis: nil, + defaultArgument: nil, + trailingComma: nil + )), + diagnostics: [ + DiagnosticSpec(locationMarker: "COLON", message: "Expected ':' in function parameter"), + DiagnosticSpec(locationMarker: "RPAREN", message: "Expected ')' to end parameter clause"), + ] + ) + } } extension Parser.DeclAttributes { diff --git a/Tests/SwiftParserTest/RecoveryTests.swift b/Tests/SwiftParserTest/RecoveryTests.swift deleted file mode 100644 index 4b80f5b9632..00000000000 --- a/Tests/SwiftParserTest/RecoveryTests.swift +++ /dev/null @@ -1,180 +0,0 @@ -import XCTest -@_spi(RawSyntax) import SwiftSyntax -@_spi(Testing) @_spi(RawSyntax) import SwiftParser -import _SwiftSyntaxTestSupport - -public class RecoveryTests: XCTestCase { - func testRecoverOneExtraLabel() { - AssertParse( - "(first second #^DIAG^#third: Int)", - { $0.parseFunctionSignature() }, - substructure: Syntax(FunctionParameterSyntax( - attributes: nil, - firstName: TokenSyntax.identifier("first"), - secondName: TokenSyntax.identifier("second"), - UnexpectedNodesSyntax([Syntax(TokenSyntax.identifier("third"))]), - colon: TokenSyntax.colonToken(), - type: TypeSyntax(SimpleTypeIdentifierSyntax(name: TokenSyntax.identifier("Int"), genericArgumentClause: nil)), - ellipsis: nil, - defaultArgument: nil, - trailingComma: nil - )), - diagnostics: [ - DiagnosticSpec(message: "Unexpected text 'third' found in function parameter") - ] - ) - } - - func testRecoverTwoExtraLabels() { - AssertParse( - "(first second #^DIAG^#third fourth: Int)", - { $0.parseFunctionSignature() }, - substructure: Syntax(FunctionParameterSyntax( - attributes: nil, - firstName: TokenSyntax.identifier("first"), - secondName: TokenSyntax.identifier("second"), - UnexpectedNodesSyntax([Syntax(TokenSyntax.identifier("third")), Syntax(TokenSyntax.identifier("fourth"))]), - colon: TokenSyntax.colonToken(), - type: TypeSyntax(SimpleTypeIdentifierSyntax(name: TokenSyntax.identifier("Int"), genericArgumentClause: nil)), - ellipsis: nil, - defaultArgument: nil, - trailingComma: nil - )), - diagnostics: [ - DiagnosticSpec(message: "Unexpected text 'third fourth' found in function parameter") - ] - ) - } - - func testDontRecoverFromDeclKeyword() { - AssertParse( - "func foo(first second #^MISSING_COLON^#third #^MISSING_RPAREN^#struct#^MISSING_IDENTIFIER^##^BRACES^#: Int) {}", - substructure: Syntax(FunctionParameterSyntax( - attributes: nil, - firstName: .identifier("first"), - secondName: .identifier("second"), - colon: .colonToken(presence: .missing), - type: TypeSyntax(SimpleTypeIdentifierSyntax(name: .identifier("third"), genericArgumentClause: nil)), - ellipsis: nil, - defaultArgument: nil, - trailingComma: nil - )), - diagnostics: [ - DiagnosticSpec(locationMarker: "MISSING_COLON", message: "Expected ':' in function parameter"), - DiagnosticSpec(locationMarker: "MISSING_RPAREN", message: "Expected ')' to end parameter clause"), - // FIXME: We should issues something like "Expected identifier in declaration" - DiagnosticSpec(locationMarker: "MISSING_IDENTIFIER", message: "Expected '' in declaration"), - DiagnosticSpec(locationMarker: "BRACES", message: "Expected '{'"), - DiagnosticSpec(locationMarker: "BRACES", message: "Expected '}'"), - ] - ) - } - - func testRecoverFromParens() { - AssertParse( - "(first second #^DIAG^#[third fourth]: Int)", - { $0.parseFunctionSignature() }, - substructure: Syntax(FunctionParameterSyntax( - attributes: nil, - firstName: TokenSyntax.identifier("first"), - secondName: TokenSyntax.identifier("second"), - UnexpectedNodesSyntax([ - Syntax(TokenSyntax.leftSquareBracketToken()), - Syntax(TokenSyntax.identifier("third")), - Syntax(TokenSyntax.identifier("fourth")), - Syntax(TokenSyntax.rightSquareBracketToken()) - ]), - colon: TokenSyntax.colonToken(), - type: TypeSyntax(SimpleTypeIdentifierSyntax(name: TokenSyntax.identifier("Int"), genericArgumentClause: nil)), - ellipsis: nil, - defaultArgument: nil, - trailingComma: nil - )), - diagnostics: [ - DiagnosticSpec(message: "Unexpected text '[third fourth]' found in function parameter") - ] - ) - } - - func testDontRecoverFromUnbalancedParens() { - AssertParse( - "func foo(first second #^COLON^#[third #^RSQUARE_COLON^#fourth: Int) {}", - substructure: Syntax(FunctionParameterSyntax( - attributes: nil, - firstName: TokenSyntax.identifier("first"), - secondName: TokenSyntax.identifier("second"), - colon: TokenSyntax(.colon, presence: .missing), - type: TypeSyntax(ArrayTypeSyntax( - leftSquareBracket: TokenSyntax.leftSquareBracketToken(), - elementType: TypeSyntax(SimpleTypeIdentifierSyntax(name: TokenSyntax.identifier("third"), genericArgumentClause: nil)), - rightSquareBracket: TokenSyntax(.rightSquareBracket, presence: .missing) - )), - ellipsis: nil, - defaultArgument: nil, - trailingComma: nil - )), - diagnostics: [ - DiagnosticSpec(locationMarker: "COLON", message: "Expected ':' in function parameter"), - DiagnosticSpec(locationMarker: "RSQUARE_COLON" , message: "Expected ']' to end type"), - DiagnosticSpec(locationMarker: "RSQUARE_COLON", message: "Expected ')' to end parameter clause"), - ] - ) - } - - func testDontRecoverIfNewlineIsBeforeColon() { - AssertParse( - """ - func foo(first second #^COLON^#third#^RPAREN^# - : Int) {} - """, - substructure: Syntax(FunctionParameterSyntax( - attributes: nil, - firstName: TokenSyntax.identifier("first"), - secondName: TokenSyntax.identifier("second"), - colon: TokenSyntax(.colon, presence: .missing), - type: TypeSyntax(SimpleTypeIdentifierSyntax(name: TokenSyntax.identifier("third"), genericArgumentClause: nil)), - ellipsis: nil, - defaultArgument: nil, - trailingComma: nil - )), - diagnostics: [ - DiagnosticSpec(locationMarker: "COLON", message: "Expected ':' in function parameter"), - DiagnosticSpec(locationMarker: "RPAREN", message: "Expected ')' to end parameter clause"), - ] - ) - } - - public func testNoParamsForFunction() { - AssertParse( - """ - class MyClass { - func withoutParameters#^DIAG^# - - func withParameters() {} - } - """, - substructure: Syntax(FunctionDeclSyntax( - attributes: nil, - modifiers: nil, - funcKeyword: .funcKeyword(), - identifier: .identifier("withoutParameters"), - genericParameterClause: nil, - signature: FunctionSignatureSyntax( - input: ParameterClauseSyntax( - leftParen: .leftParenToken(presence: .missing), - parameterList: FunctionParameterListSyntax([]), - rightParen: .rightParenToken(presence: .missing) - ), - asyncOrReasyncKeyword: nil, - throwsOrRethrowsKeyword: nil, - output: nil - ), - genericWhereClause: nil, - body: nil - )), - diagnostics: [ - DiagnosticSpec(message: "Expected argument list in function declaration") - ] - ) - } -}