Skip to content

Add diagnostic if #endif is followed by any declarations #1833

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Sources/SwiftBasicFormat/BasicFormat.swift
Original file line number Diff line number Diff line change
Expand Up @@ -186,12 +186,12 @@ open class BasicFormat: SyntaxRewriter {
}

switch (first?.tokenKind, second?.tokenKind) {

case (.multilineStringQuote, .backslash), // string interpolation segment inside a multi-line string literal
(.multilineStringQuote, .multilineStringQuote), // empty multi-line string literal
(.multilineStringQuote, .stringSegment), // segment starting a multi-line string literal
(.stringSegment, .multilineStringQuote), // ending a multi-line string literal that has a string interpolation segment at its end
(.rightParen, .multilineStringQuote), // ending a multi-line string literal that has a string interpolation segment at its end
(.poundEndifKeyword, _),
(_, .poundElseKeyword),
(_, .poundElseifKeyword),
(_, .poundEndifKeyword),
Expand Down
13 changes: 13 additions & 0 deletions Sources/SwiftParser/Directives.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,14 @@ extension Parser {
// Parse #if
let (unexpectedBeforePoundIfKeyword, poundIfKeyword) = self.expect(.poundIfKeyword)
let condition = RawExprSyntax(self.parseSequenceExpression(.basic, forDirective: true))
let unexpectedBetweenConditionAndElements = self.consumeRemainingTokenOnLine()

clauses.append(
RawIfConfigClauseSyntax(
unexpectedBeforePoundIfKeyword,
poundKeyword: poundIfKeyword,
condition: condition,
unexpectedBetweenConditionAndElements,
elements: syntax(&self, parseIfConfigClauseElements(parseElement, addSemicolonIfNeeded: addSemicolonIfNeeded)),
arena: self.arena
)
Expand All @@ -123,11 +125,13 @@ extension Parser {
var unexpectedBeforePoundKeyword: RawUnexpectedNodesSyntax?
var poundKeyword: RawTokenSyntax
let condition: RawExprSyntax?
let unexpectedBetweenConditionAndElements: RawUnexpectedNodesSyntax?

switch match {
case .poundElseifKeyword:
(unexpectedBeforePoundKeyword, poundKeyword) = self.eat(handle)
condition = RawExprSyntax(self.parseSequenceExpression(.basic, forDirective: true))
unexpectedBetweenConditionAndElements = self.consumeRemainingTokenOnLine()
case .poundElseKeyword:
(unexpectedBeforePoundKeyword, poundKeyword) = self.eat(handle)
if let ifToken = self.consume(if: .init(.if, allowAtStartOfLine: false)) {
Expand All @@ -137,6 +141,7 @@ extension Parser {
} else {
condition = nil
}
unexpectedBetweenConditionAndElements = self.consumeRemainingTokenOnLine()
case .pound:
if self.atElifTypo() {
(unexpectedBeforePoundKeyword, poundKeyword) = self.eat(handle)
Expand All @@ -146,6 +151,7 @@ extension Parser {
unexpectedBeforePoundKeyword = RawUnexpectedNodesSyntax(combining: unexpectedBeforePoundKeyword, poundKeyword, elif, arena: self.arena)
poundKeyword = self.missingToken(.poundElseifKeyword)
condition = RawExprSyntax(self.parseSequenceExpression(.basic, forDirective: true))
unexpectedBetweenConditionAndElements = self.consumeRemainingTokenOnLine()
} else {
break LOOP
}
Expand All @@ -156,17 +162,21 @@ extension Parser {
unexpectedBeforePoundKeyword,
poundKeyword: poundKeyword,
condition: condition,
unexpectedBetweenConditionAndElements,
elements: syntax(&self, parseIfConfigClauseElements(parseElement, addSemicolonIfNeeded: addSemicolonIfNeeded)),
arena: self.arena
)
)
}

let (unexpectedBeforePoundEndIf, poundEndIf) = self.expect(.poundEndifKeyword)
let unexpectedAfterPoundEndif = self.consumeRemainingTokenOnLine()

return RawIfConfigDeclSyntax(
clauses: RawIfConfigClauseListSyntax(elements: clauses, arena: self.arena),
unexpectedBeforePoundEndIf,
poundEndif: poundEndIf,
unexpectedAfterPoundEndif,
arena: self.arena
)
}
Expand Down Expand Up @@ -256,13 +266,16 @@ extension Parser {
args = nil
}
let (unexpectedBeforeRParen, rparen) = self.expect(.rightParen)
let unexpectedAfterRightParen = self.consumeRemainingTokenOnLine()

return RawPoundSourceLocationSyntax(
poundSourceLocation: line,
unexpectedBeforeLParen,
leftParen: lparen,
args: args,
unexpectedBeforeRParen,
rightParen: rparen,
unexpectedAfterRightParen,
arena: self.arena
)
}
Expand Down
16 changes: 16 additions & 0 deletions Sources/SwiftParser/Parser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,22 @@ extension Parser {
return self.consumeAnyToken()
}

/// Consumes remaining token on the line and returns a ``RawUnexpectedNodesSyntax``
/// if there is any tokens consumed.
mutating func consumeRemainingTokenOnLine() -> RawUnexpectedNodesSyntax? {
guard !self.currentToken.isAtStartOfLine else {
return nil
}

var unexpectedTokens = [RawTokenSyntax]()
var loopProgress = LoopProgressCondition()
while !self.at(.eof), !currentToken.isAtStartOfLine, loopProgress.evaluate(self.currentToken) {
unexpectedTokens += [self.consumeAnyToken()]
}

return RawUnexpectedNodesSyntax(unexpectedTokens, arena: self.arena)
}

}

// MARK: Check if we can recover to a token
Expand Down
41 changes: 41 additions & 0 deletions Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1100,6 +1100,22 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
return .visitChildren
}

public override func visit(_ node: IfConfigClauseSyntax) -> SyntaxVisitorContinueKind {
if shouldSkip(node) {
return .skipChildren
}

if let unexpectedBetweenConditionAndElements = node.unexpectedBetweenConditionAndElements {
addDiagnostic(
unexpectedBetweenConditionAndElements,
.extraTokensFollowingConditionalCompilationDirective,
handledNodes: [unexpectedBetweenConditionAndElements.id]
)
}

return .visitChildren
}

public override func visit(_ node: IfConfigDeclSyntax) -> SyntaxVisitorContinueKind {
for clause in node.clauses where clause.hasError {
if let unexpectedBeforePoundKeyword = clause.unexpectedBeforePoundKeyword,
Expand Down Expand Up @@ -1136,6 +1152,15 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
}
}
}

if let unexpectedAfterPoundEndif = node.unexpectedAfterPoundEndif {
addDiagnostic(
unexpectedAfterPoundEndif,
.extraTokensFollowingConditionalCompilationDirective,
handledNodes: [unexpectedAfterPoundEndif.id]
)
}

return .visitChildren
}

Expand Down Expand Up @@ -1415,6 +1440,22 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
return .visitChildren
}

public override func visit(_ node: PoundSourceLocationSyntax) -> SyntaxVisitorContinueKind {
if shouldSkip(node) {
return .skipChildren
}

if let unexpectedAfterRightParen = node.unexpectedAfterRightParen {
addDiagnostic(
unexpectedAfterRightParen,
.extraTokensAtTheEndOfSourceLocationDirective,
handledNodes: [unexpectedAfterRightParen.id]
)
}

return .visitChildren
}

public override func visit(_ node: PrecedenceGroupAssignmentSyntax) -> SyntaxVisitorContinueKind {
if shouldSkip(node) {
return .skipChildren
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,12 @@ extension DiagnosticMessage where Self == StaticParserError {
public static var expectedSequenceExpressionInForEachLoop: Self {
.init("expected Sequence expression for for-each loop")
}
public static var extraTokensAtTheEndOfSourceLocationDirective: Self {
.init("extra tokens following the #sourceLocation directive")
}
public static var extraTokensFollowingConditionalCompilationDirective: Self {
.init("extra tokens following conditional compilation directive")
}
public static var extraRightBracket: Self {
.init("unexpected ']' in type; did you mean to write an array type?")
}
Expand Down
124 changes: 124 additions & 0 deletions Tests/SwiftParserTest/DirectiveTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -197,4 +197,128 @@ final class DirectiveTests: XCTestCase {
)
}

func testEndIfFollowedByDeclarations() {
assertParse(
"""
struct Foo {
#if false
var x: Int
#endif1️⃣; var x = 1
}
""",
diagnostics: [
DiagnosticSpec(
message: "extra tokens following conditional compilation directive"
)
]
)
}

func testIfFollowByDeclarations() {
assertParse(
"""
struct Foo {
#if DEBUG1️⃣; var x = 1
var x: Int
#endif
}
""",
diagnostics: [
DiagnosticSpec(
message: "extra tokens following conditional compilation directive"
)
]
)

assertParse(
"""
struct Foo {
#if DEBUG || UAT1️⃣; var x = 1
var x: Int
#endif
}
""",
diagnostics: [
DiagnosticSpec(
message: "extra tokens following conditional compilation directive"
)
]
)
}

func testElseIfFollowByDeclarations() {
assertParse(
"""
struct Foo {
#if DEBUG
var x: Int = 1
#elseif UAT1️⃣; var x = 1
var x: Int = 2
#endif
}
""",
diagnostics: [
DiagnosticSpec(
message: "extra tokens following conditional compilation directive"
)
]
)

assertParse(
"""
struct Foo {
#if DEBUG
var x: Int = 1
#elseif UAT || UAT1️⃣; var x = 1
var x: Int = 2
#endif
}
""",
diagnostics: [
DiagnosticSpec(
message: "extra tokens following conditional compilation directive"
)
]
)
}

func testElseFollowByDeclarations() {
assertParse(
"""
struct Foo {
#if DEBUG
var x: Int = 1
#else1️⃣; var x = 1
var x: Int = 2
#endif
}
""",
diagnostics: [
DiagnosticSpec(
message: "extra tokens following conditional compilation directive"
)
]
)
}

func testSourcelocationDirectiveFollowedByDeclarations() {
assertParse(
"""
var sometName: Int
#sourceLocation(file: "other.swift", line: 1)
var someName: Int
"""
)

assertParse(
"""
#sourceLocation(file: "other.swift", line: 1)1️⃣; let x = 1
""",
diagnostics: [
DiagnosticSpec(
message: "extra tokens following the #sourceLocation directive"
)
]
)
}
}