diff --git a/CodeGeneration/Sources/SyntaxSupport/CommonNodes.swift b/CodeGeneration/Sources/SyntaxSupport/CommonNodes.swift index 098a4af7877..90b08eb3cd4 100644 --- a/CodeGeneration/Sources/SyntaxSupport/CommonNodes.swift +++ b/CodeGeneration/Sources/SyntaxSupport/CommonNodes.swift @@ -26,6 +26,7 @@ public let COMMON_NODES: [Node] = [ nameForDiagnostics: nil, description: "A CodeBlockItem is any Syntax node that appears on its own line inside a CodeBlock.", kind: "Syntax", + parserFunction: "parseNonOptionalCodeBlockItem", children: [ Child( name: "Item", diff --git a/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftparser/ParserEntryFile.swift b/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftparser/ParserEntryFile.swift index 4480973a059..3b28b5b97dd 100644 --- a/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftparser/ParserEntryFile.swift +++ b/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftparser/ParserEntryFile.swift @@ -100,5 +100,22 @@ let parserEntryFile = SourceFileSyntax(leadingTrivia: copyrightHeader) { } """ ) + + DeclSyntax( + """ + mutating func parseNonOptionalCodeBlockItem() -> RawCodeBlockItemSyntax { + guard let node = self.parseCodeBlockItem(isAtTopLevel: false, allowInitDecl: true) else { + // The missing item is not neccessary to be a declaration, + // which is just a placeholder here + return RawCodeBlockItemSyntax( + item: .decl(RawDeclSyntax(RawMissingDeclSyntax(attributes: nil, modifiers: nil, arena: self.arena))), + semicolon: nil, + arena: self.arena + ) + } + return node + } + """ + ) } } diff --git a/Sources/SwiftParser/generated/Parser+Entry.swift b/Sources/SwiftParser/generated/Parser+Entry.swift index f6a9a97561c..6fce79c6e3d 100644 --- a/Sources/SwiftParser/generated/Parser+Entry.swift +++ b/Sources/SwiftParser/generated/Parser+Entry.swift @@ -73,6 +73,14 @@ extension ClosureParameterSyntax: SyntaxParseable { } } +extension CodeBlockItemSyntax: SyntaxParseable { + public static func parse(from parser: inout Parser) -> Self { + let node = parser.parseNonOptionalCodeBlockItem() + let raw = RawSyntax(parser.parseRemainder(into: node)) + return Syntax(raw: raw).cast(Self.self) + } +} + extension DeclSyntax: SyntaxParseable { public static func parse(from parser: inout Parser) -> Self { let node = parser.parseDeclaration() @@ -184,4 +192,17 @@ fileprivate extension Parser { let withUnexpected = layout.replacingChild(at: layout.children.count - 1, with: unexpected.raw, arena: self.arena) return R.init(withUnexpected)! } + + mutating func parseNonOptionalCodeBlockItem() -> RawCodeBlockItemSyntax { + guard let node = self.parseCodeBlockItem(isAtTopLevel: false, allowInitDecl: true) else { + // The missing item is not neccessary to be a declaration, + // which is just a placeholder here + return RawCodeBlockItemSyntax( + item: .decl(RawDeclSyntax(RawMissingDeclSyntax(attributes: nil, modifiers: nil, arena: self.arena))), + semicolon: nil, + arena: self.arena + ) + } + return node + } } diff --git a/Sources/SwiftSyntaxBuilder/generated/SyntaxExpressibleByStringInterpolationConformances.swift b/Sources/SwiftSyntaxBuilder/generated/SyntaxExpressibleByStringInterpolationConformances.swift index 74ec2450258..f5c2c9cb5d2 100644 --- a/Sources/SwiftSyntaxBuilder/generated/SyntaxExpressibleByStringInterpolationConformances.swift +++ b/Sources/SwiftSyntaxBuilder/generated/SyntaxExpressibleByStringInterpolationConformances.swift @@ -34,6 +34,8 @@ extension CatchClauseSyntax: SyntaxExpressibleByStringInterpolation {} extension ClosureParameterSyntax: SyntaxExpressibleByStringInterpolation {} +extension CodeBlockItemSyntax: SyntaxExpressibleByStringInterpolation {} + extension DeclSyntax: SyntaxExpressibleByStringInterpolation {} extension EnumCaseParameterSyntax: SyntaxExpressibleByStringInterpolation {} diff --git a/Tests/SwiftSyntaxBuilderTest/CollectionNodeFlatteningTests.swift b/Tests/SwiftSyntaxBuilderTest/CollectionNodeFlatteningTests.swift index 099990c3c9e..b35296c54cb 100644 --- a/Tests/SwiftSyntaxBuilderTest/CollectionNodeFlatteningTests.swift +++ b/Tests/SwiftSyntaxBuilderTest/CollectionNodeFlatteningTests.swift @@ -44,4 +44,19 @@ final class CollectionNodeFlatteningTests: XCTestCase { """ ) } + + func test_FlattenCodeBlockItemListWithCodeBlockItemStrings() { + let buildable = CodeBlockItemListSyntax { + "let one = object.methodOne()" + "let two = object.methodTwo()" + } + + assertBuildResult( + buildable, + """ + let one = object.methodOne() + let two = object.methodTwo() + """ + ) + } } diff --git a/Tests/SwiftSyntaxBuilderTest/StringInterpolationTests.swift b/Tests/SwiftSyntaxBuilderTest/StringInterpolationTests.swift index f195e2bbb1b..520fd6fcde4 100644 --- a/Tests/SwiftSyntaxBuilderTest/StringInterpolationTests.swift +++ b/Tests/SwiftSyntaxBuilderTest/StringInterpolationTests.swift @@ -408,6 +408,26 @@ final class StringInterpolationTests: XCTestCase { ) } + func testCodeBlockItemInterpolation() { + let codeBlockItem: CodeBlockItemSyntax = + """ + func foo() { + return bar + } + """ + + XCTAssertTrue(codeBlockItem.is(CodeBlockItemSyntax.self)) + assertStringsEqualWithDiff( + codeBlockItem.description, + """ + func foo() { + return bar + } + """ + ) + + } + func testInvalidTrivia() { let invalid = Trivia("/*comment*/ invalid /*comm*/") XCTAssertEqual(invalid, [.blockComment("/*comment*/"), .spaces(1), .unexpectedText("invalid"), .spaces(1), .blockComment("/*comm*/")]) @@ -460,4 +480,21 @@ final class StringInterpolationTests: XCTestCase { ) } } + + func testInvalidSyntax3() { + let invalid: CodeBlockItemSyntax = " " + + XCTAssert(invalid.hasError) + XCTAssertThrowsError(try CodeBlockItemSyntax(validating: " ")) { error in + assertStringsEqualWithDiff( + String(describing: error), + """ + + 1 │ + │ ╰─ error: expected declaration + + """ + ) + } + } }