Skip to content

Commit e00526b

Browse files
committed
Add diagnostic for invalid whitespace in @ Attribute
1 parent 3ed9dcf commit e00526b

File tree

5 files changed

+119
-5
lines changed

5 files changed

+119
-5
lines changed

Sources/SwiftParser/Attributes.swift

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,34 @@ extension Parser {
166166
}
167167

168168
mutating func parseAttribute(argumentMode: AttributeArgumentMode, parseArguments: (inout Parser) -> RawAttributeSyntax.Argument) -> RawAttributeListSyntax.Element {
169-
let (unexpectedBeforeAtSign, atSign) = self.expect(.atSign)
170-
let attributeName = self.parseType()
169+
var (unexpectedBeforeAtSign, atSign) = self.expect(.atSign)
170+
var attributeName = self.parseType()
171+
var unexpectedBetweenAtSignAndAttributeName: RawUnexpectedNodesSyntax?
172+
173+
if atSign.trailingTriviaByteLength != 0 {
174+
unexpectedBeforeAtSign = RawUnexpectedNodesSyntax(
175+
combining: unexpectedBeforeAtSign,
176+
atSign,
177+
arena: self.arena
178+
)
179+
atSign = RawTokenSyntax(
180+
kind: .atSign,
181+
text: atSign.tokenText,
182+
leadingTriviaPieces: atSign.leadingTriviaPieces,
183+
presence: .missing,
184+
arena: self.arena
185+
)
186+
} else if attributeName.raw.leadingTriviaByteLength != 0 {
187+
unexpectedBetweenAtSignAndAttributeName = RawUnexpectedNodesSyntax(attributeName)
188+
// `withLeadingTrivia` only returns `nil` if there is no token in `attributeName`.
189+
// But since `attributeName` has leadingTriviaLength != 0 there must be trivia and thus a token.
190+
// So we can safely force-unwrap here.
191+
attributeName = attributeName
192+
.raw
193+
.withLeadingTrivia([], arena: self.arena)!
194+
.as(RawTypeSyntax.self)!
195+
}
196+
171197
let shouldParseArgument: Bool
172198
switch argumentMode {
173199
case .required:
@@ -185,6 +211,7 @@ extension Parser {
185211
RawAttributeSyntax(
186212
unexpectedBeforeAtSign,
187213
atSignToken: atSign,
214+
unexpectedBetweenAtSignAndAttributeName,
188215
attributeName: attributeName,
189216
unexpectedBeforeLeftParen,
190217
leftParen: leftParen,
@@ -199,6 +226,7 @@ extension Parser {
199226
RawAttributeSyntax(
200227
unexpectedBeforeAtSign,
201228
atSignToken: atSign,
229+
unexpectedBetweenAtSignAndAttributeName,
202230
attributeName: attributeName,
203231
leftParen: nil,
204232
argument: nil,

Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,49 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
456456
],
457457
handledNodes: [argument.id]
458458
)
459-
return .visitChildren
459+
}
460+
if let unexpectedAtSign = node.unexpectedBeforeAtSignToken?.onlyToken(where: { $0.tokenKind == .atSign && !$0.trailingTrivia.isEmpty }) {
461+
addDiagnostic(
462+
unexpectedAtSign,
463+
position: node.unexpectedBeforeAtSignToken?.endPositionBeforeTrailingTrivia,
464+
StaticParserError.invalidWhitespaceBetweenAttributeAtSignAndIdentifier,
465+
fixIts: [
466+
FixIt(
467+
message: StaticParserFixIt.removeExtraneousWhitespace,
468+
changes: [
469+
.makeMissing(unexpectedAtSign, transferTrivia: false),
470+
.makePresent(node.atSignToken),
471+
]
472+
)
473+
],
474+
handledNodes: [
475+
node.id,
476+
unexpectedAtSign.id,
477+
node.atSignToken.id,
478+
]
479+
)
480+
} else if node.attributeName.isMissingAllTokens,
481+
let unexpectedBetweenAtSignTokenAndAttributeName = node.unexpectedBetweenAtSignTokenAndAttributeName,
482+
unexpectedBetweenAtSignTokenAndAttributeName.trailingTriviaLength.utf8Length != 0
483+
{
484+
addDiagnostic(
485+
unexpectedBetweenAtSignTokenAndAttributeName,
486+
StaticParserError.invalidWhitespaceBetweenAttributeAtSignAndIdentifier,
487+
fixIts: [
488+
FixIt(
489+
message: StaticParserFixIt.removeExtraneousWhitespace,
490+
changes: [
491+
.makeMissing(unexpectedBetweenAtSignTokenAndAttributeName, transferTrivia: false),
492+
.makePresent(node.attributeName),
493+
]
494+
)
495+
],
496+
handledNodes: [
497+
node.id,
498+
unexpectedBetweenAtSignTokenAndAttributeName.id,
499+
node.attributeName.id,
500+
]
501+
)
460502
}
461503
return .visitChildren
462504
}

Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,9 @@ extension DiagnosticMessage where Self == StaticParserError {
171171
public static var invalidWhitespaceAfterPeriod: Self {
172172
.init("extraneous whitespace after '.' is not permitted")
173173
}
174+
public static var invalidWhitespaceBetweenAttributeAtSignAndIdentifier: Self {
175+
.init("extraneous whitespace after '@' is not permitted")
176+
}
174177
public static var joinConditionsUsingComma: Self {
175178
.init("expected ',' joining parts of a multi-clause condition")
176179
}

Sources/SwiftSyntax/Raw/RawSyntax.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,8 @@ extension RawSyntax {
293293
/// - Parameters:
294294
/// - leadingTrivia: The trivia to attach.
295295
/// - arena: SyntaxArena to the result node data resides.
296-
func withLeadingTrivia(_ leadingTrivia: Trivia, arena: SyntaxArena) -> RawSyntax? {
296+
@_spi(RawSyntax)
297+
public func withLeadingTrivia(_ leadingTrivia: Trivia, arena: SyntaxArena) -> RawSyntax? {
297298
switch view {
298299
case .token(let tokenView):
299300
return .makeMaterializedToken(
@@ -319,7 +320,8 @@ extension RawSyntax {
319320
/// - Parameters:
320321
/// - trailingTrivia: The trivia to attach.
321322
/// - arena: SyntaxArena to the result node data resides.
322-
func withTrailingTrivia(_ trailingTrivia: Trivia, arena: SyntaxArena) -> RawSyntax? {
323+
@_spi(RawSyntax)
324+
public func withTrailingTrivia(_ trailingTrivia: Trivia, arena: SyntaxArena) -> RawSyntax? {
323325
switch view {
324326
case .token(let tokenView):
325327
return .makeMaterializedToken(

Tests/SwiftParserTest/AttributeTests.swift

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,4 +631,43 @@ final class AttributeTests: XCTestCase {
631631
"""
632632
)
633633
}
634+
635+
func testInvalidWhitespaceBetweenAtSignAndIdenfifierIsDiagnosed() {
636+
assertParse(
637+
"""
638+
@1️⃣ MyAttribute
639+
func foo() {}
640+
""",
641+
diagnostics: [
642+
DiagnosticSpec(
643+
message: "extraneous whitespace after '@' is not permitted",
644+
fixIts: ["remove whitespace"]
645+
)
646+
],
647+
fixedSource: """
648+
@MyAttribute
649+
func foo() {}
650+
"""
651+
)
652+
}
653+
654+
func testInvalidNewlineBetweenAtSignAndIdenfifierIsDiagnosed() {
655+
assertParse(
656+
"""
657+
@1️⃣
658+
MyAttribute
659+
func foo() {}
660+
""",
661+
diagnostics: [
662+
DiagnosticSpec(
663+
message: "extraneous whitespace after '@' is not permitted",
664+
fixIts: ["remove whitespace"]
665+
)
666+
],
667+
fixedSource: """
668+
@MyAttribute
669+
func foo() {}
670+
"""
671+
)
672+
}
634673
}

0 commit comments

Comments
 (0)