Skip to content

Commit b808267

Browse files
committed
Add diagnostic for invalid whitespace in @ Attribute
1 parent d14176d commit b808267

File tree

4 files changed

+108
-2
lines changed

4 files changed

+108
-2
lines changed

Sources/SwiftParser/Attributes.swift

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,29 @@ extension Parser {
166166
}
167167

168168
mutating func parseAttribute(argumentMode: AttributeArgumentMode, parseArguments: (inout Parser) -> RawAttributeSyntax.Argument) -> RawAttributeListSyntax.Element {
169-
let (unexpectedBeforeAtSign, atSign) = self.expect(.atSign)
169+
var (unexpectedBeforeAtSign, atSign) = self.expect(.atSign)
170170
let attributeName = self.parseType()
171+
172+
if atSign.trailingTriviaByteLength != 0 {
173+
unexpectedBeforeAtSign = RawUnexpectedNodesSyntax(
174+
combining: unexpectedBeforeAtSign, atSign,
175+
arena: self.arena
176+
)
177+
atSign = RawTokenSyntax(
178+
kind: .atSign,
179+
text: atSign.tokenText,
180+
leadingTriviaPieces: atSign.leadingTriviaPieces,
181+
presence: .missing,
182+
arena: self.arena
183+
)
184+
} else if attributeName.raw.leadingTriviaByteLength != 0 {
185+
unexpectedBeforeAtSign = RawUnexpectedNodesSyntax(
186+
combining: unexpectedBeforeAtSign, attributeName.as(RawTokenSyntax.self),
187+
arena: self.arena
188+
)
189+
// TODO: Synthesize fixed attributeName (RawTypeSyntax)
190+
}
191+
171192
let shouldParseArgument: Bool
172193
switch argumentMode {
173194
case .required:

Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,50 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
456456
],
457457
handledNodes: [argument.id]
458458
)
459-
return .visitChildren
459+
}
460+
if node.hasError,
461+
let unexpectedBeforeAtSignTokens = node.unexpectedBeforeAtSignToken,
462+
let unexpectedBeforeAtSign = unexpectedBeforeAtSignTokens.lastToken(viewMode: .sourceAccurate) {
463+
let tokensToDiscard = unexpectedBeforeAtSignTokens.compactMap { $0.as(TokenSyntax.self) }
464+
if unexpectedBeforeAtSign.tokenKind == .atSign, !unexpectedBeforeAtSign.trailingTrivia.isEmpty {
465+
addDiagnostic(
466+
unexpectedBeforeAtSign,
467+
StaticParserError.invalidWhitespaceBetweenAttributeAtSignAndIdentifier,
468+
fixIts: [
469+
FixIt(
470+
message: StaticParserFixIt.removeExtraneousWhitespace,
471+
changes: [
472+
.makeMissing(tokensToDiscard, transferTrivia: false),
473+
.makePresent(node.atSignToken),
474+
]
475+
)
476+
],
477+
handledNodes: [
478+
node.id,
479+
unexpectedBeforeAtSignTokens.id,
480+
node.atSignToken.id
481+
]
482+
)
483+
} else if unexpectedBeforeAtSign.tokenKind == .identifier(unexpectedBeforeAtSign.text) {
484+
addDiagnostic(
485+
unexpectedBeforeAtSign,
486+
StaticParserError.invalidWhitespaceBetweenAttributeAtSignAndIdentifier,
487+
fixIts: [
488+
FixIt(
489+
message: StaticParserFixIt.removeExtraneousWhitespace,
490+
changes: [
491+
.makeMissing(tokensToDiscard, transferTrivia: false),
492+
.makePresent(node.attributeName), // TODO: Provide synthesized attributeName as a replacement.
493+
]
494+
)
495+
],
496+
handledNodes: [
497+
node.id,
498+
unexpectedBeforeAtSignTokens.id,
499+
node.attributeName.id
500+
]
501+
)
502+
}
460503
}
461504
return .visitChildren
462505
}

Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift

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

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+
@
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)