diff --git a/Sources/SwiftParser/StringLiterals.swift b/Sources/SwiftParser/StringLiterals.swift index 9c422389172..668fbb22cb2 100644 --- a/Sources/SwiftParser/StringLiterals.swift +++ b/Sources/SwiftParser/StringLiterals.swift @@ -487,7 +487,7 @@ extension Parser { // string literal. guard currentToken.leadingTriviaText.isEmpty else { break } - if let stringSegment = self.consume(if: .stringSegment) { + if let stringSegment = self.consume(if: .stringSegment, TokenSpec(.identifier, remapping: .stringSegment)) { segments.append(.stringSegment(RawStringSegmentSyntax(content: stringSegment, arena: self.arena))) } else if let backslash = self.consume(if: .backslash) { let (unexpectedBeforeDelimiter, delimiter) = self.parsePoundDelimiter(.rawStringDelimiter, matching: openDelimiter) diff --git a/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift b/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift index 913e5109dba..a9a462fcb81 100644 --- a/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift +++ b/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift @@ -993,6 +993,35 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor { return handleMissingSyntax(node, additionalHandledNodes: [node.placeholder.id]) } + override open func visit(_ node: OriginallyDefinedInArgumentsSyntax) -> SyntaxVisitorContinueKind { + if shouldSkip(node) { + return .skipChildren + } + if let token = node.unexpectedBetweenModuleLabelAndColon?.onlyToken(where: { $0.tokenKind.isIdentifier }), + node.moduleLabel.presence == .missing + { + addDiagnostic( + node, + MissingNodesError(missingNodes: [Syntax(node.moduleLabel)]), + fixIts: [ + FixIt( + message: ReplaceTokensFixIt( + replaceTokens: [token], + replacements: [node.moduleLabel] + ), + changes: [ + FixIt.MultiNodeChange.makeMissing(token), + FixIt.MultiNodeChange.makePresent(node.moduleLabel), + ] + ) + ], + handledNodes: [node.moduleLabel.id, token.id] + ) + } + + return .visitChildren + } + public override func visit(_ node: OperatorDeclSyntax) -> SyntaxVisitorContinueKind { if shouldSkip(node) { return .skipChildren @@ -1347,6 +1376,36 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor { return .visitChildren } + public override func visit(_ node: UnavailableFromAsyncArgumentsSyntax) -> SyntaxVisitorContinueKind { + if shouldSkip(node) { + return .skipChildren + } + + if let token = node.unexpectedBetweenMessageLabelAndColon?.onlyToken(where: { $0.tokenKind.isIdentifier }), + node.messageLabel.presence == .missing + { + addDiagnostic( + node, + MissingNodesError(missingNodes: [Syntax(node.messageLabel)]), + fixIts: [ + FixIt( + message: ReplaceTokensFixIt( + replaceTokens: [token], + replacements: [node.messageLabel] + ), + changes: [ + FixIt.MultiNodeChange.makeMissing(token), + FixIt.MultiNodeChange.makePresent(node.messageLabel), + ] + ) + ], + handledNodes: [node.messageLabel.id, token.id] + ) + } + + return .visitChildren + } + public override func visit(_ node: UnresolvedTernaryExprSyntax) -> SyntaxVisitorContinueKind { if shouldSkip(node) { return .skipChildren diff --git a/Tests/SwiftParserTest/AttributeTests.swift b/Tests/SwiftParserTest/AttributeTests.swift index bdd0c6abf13..632b7a1b28a 100644 --- a/Tests/SwiftParserTest/AttributeTests.swift +++ b/Tests/SwiftParserTest/AttributeTests.swift @@ -422,12 +422,23 @@ final class AttributeTests: XCTestCase { assertParse( """ - @_expose(Cxx, 1️⃣baz) func foo() {} + @_expose(Cxx, 1️⃣baz2️⃣) func foo() {} """, diagnostics: [ - DiagnosticSpec(message: "expected string literal to end @_expose arguments", fixIts: ["insert string literal"]), - DiagnosticSpec(message: "unexpected code 'baz' in attribute"), - ] + DiagnosticSpec( + locationMarker: "1️⃣", + message: #"expected '"' in string literal"#, + fixIts: [#"insert '"'"#] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: #"expected '"' to end string literal"#, + fixIts: [#"insert '"'"#] + ), + ], + fixedSource: """ + @_expose(Cxx, "baz") func foo() {} + """ ) } @@ -475,9 +486,12 @@ final class AttributeTests: XCTestCase { func foo() {} """, diagnostics: [ - DiagnosticSpec(message: "expected 'message' in @_unavailableFromAsync argument", fixIts: ["insert 'message'"]), - DiagnosticSpec(message: "unexpected code 'nope' before @_unavailableFromAsync argument"), - ] + DiagnosticSpec(message: "expected 'message' in @_unavailableFromAsync argument", fixIts: ["replace 'nope' with 'message'"]) + ], + fixedSource: """ + @_unavailableFromAsync(message: "abc") + func foo() {} + """ ) assertParse( @@ -493,13 +507,25 @@ final class AttributeTests: XCTestCase { assertParse( """ - @_unavailableFromAsync(message: 1️⃣abc) + @_unavailableFromAsync(message: 1️⃣abc2️⃣) func foo() {} """, diagnostics: [ - DiagnosticSpec(message: "expected string literal to end @_unavailableFromAsync argument", fixIts: ["insert string literal"]), - DiagnosticSpec(message: "unexpected code 'abc' in attribute"), - ] + DiagnosticSpec( + locationMarker: "1️⃣", + message: #"expected '"' in string literal"#, + fixIts: [#"insert '"'"#] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: #"expected '"' to end string literal"#, + fixIts: [#"insert '"'"#] + ), + ], + fixedSource: """ + @_unavailableFromAsync(message: "abc") + func foo() {} + """ ) } diff --git a/Tests/SwiftParserTest/translated/OriginalDefinedInAttrTests.swift b/Tests/SwiftParserTest/translated/OriginalDefinedInAttrTests.swift index 7fe2115dcc1..226a8527a5d 100644 --- a/Tests/SwiftParserTest/translated/OriginalDefinedInAttrTests.swift +++ b/Tests/SwiftParserTest/translated/OriginalDefinedInAttrTests.swift @@ -31,9 +31,15 @@ final class OriginalDefinedInAttrTests: XCTestCase { public func foo1() {} """#, diagnostics: [ - DiagnosticSpec(message: "expected 'module' in @_originallyDefinedIn arguments", fixIts: ["insert 'module'"]), - DiagnosticSpec(message: "unexpected code 'modulename' before @_originallyDefinedIn arguments"), - ] + DiagnosticSpec( + message: "expected 'module' in @_originallyDefinedIn arguments", + fixIts: ["replace 'modulename' with 'module'"] + ) + ], + fixedSource: #""" + @_originallyDefinedIn(module: "foo", OSX 13.13) + public func foo1() {} + """# ) } @@ -53,7 +59,10 @@ final class OriginalDefinedInAttrTests: XCTestCase { public class ToplevelClass1 {} """#, diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "expected ',' and version list in @_originallyDefinedIn arguments", fixIts: ["insert ',' and version list"]) + DiagnosticSpec( + message: "expected ',' and version list in @_originallyDefinedIn arguments", + fixIts: ["insert ',' and version list"] + ) ] ) } @@ -61,12 +70,40 @@ final class OriginalDefinedInAttrTests: XCTestCase { func testOriginalDefinedInAttr5() { assertParse( """ - @_originallyDefinedIn(1️⃣OSX 13.13.3) + @_originallyDefinedIn(1️⃣OSX 2️⃣13.13.3) public class ToplevelClass2 {} """, diagnostics: [ - DiagnosticSpec(message: "expected 'module:', string literal, and ',' in @_originallyDefinedIn arguments", fixIts: ["insert 'module:', string literal, and ','"]) - ] + DiagnosticSpec( + locationMarker: "1️⃣", + message: "expected 'module:' in @_originallyDefinedIn arguments", + fixIts: ["insert 'module:'"] + ), + DiagnosticSpec( + locationMarker: "1️⃣", + message: #"expected '"' in string literal"#, + fixIts: [#"insert '"'"#] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: #"expected '"' to end string literal"#, + fixIts: [#"insert '"'"#] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "expected ',' in @_originallyDefinedIn arguments", + fixIts: ["insert ','"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "expected platform in version restriction", + fixIts: ["insert platform"] + ), + ], + fixedSource: """ + @_originallyDefinedIn(module: "OSX", <#identifier#> 13.13.3) + public class ToplevelClass2 {} + """ ) } @@ -172,5 +209,4 @@ final class OriginalDefinedInAttrTests: XCTestCase { """ ) } - }