From a3fde1e537c6552ece0c70104e6ea35a950c32bc Mon Sep 17 00:00:00 2001 From: Kim de Vos Date: Thu, 22 Jun 2023 21:11:53 +0200 Subject: [PATCH 1/4] Add diagnostic if `#endif` is followed by any declarations --- Sources/SwiftBasicFormat/BasicFormat.swift | 2 +- Sources/SwiftParser/Directives.swift | 11 +++++++++++ .../ParseDiagnosticsGenerator.swift | 9 +++++++++ .../ParserDiagnosticMessages.swift | 3 +++ Tests/SwiftParserTest/DirectiveTests.swift | 16 ++++++++++++++++ 5 files changed, 40 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftBasicFormat/BasicFormat.swift b/Sources/SwiftBasicFormat/BasicFormat.swift index 090218d75ff..6dec7d7962f 100644 --- a/Sources/SwiftBasicFormat/BasicFormat.swift +++ b/Sources/SwiftBasicFormat/BasicFormat.swift @@ -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), diff --git a/Sources/SwiftParser/Directives.swift b/Sources/SwiftParser/Directives.swift index ac5d3aeda22..f7836d479b9 100644 --- a/Sources/SwiftParser/Directives.swift +++ b/Sources/SwiftParser/Directives.swift @@ -163,10 +163,21 @@ extension Parser { } let (unexpectedBeforePoundEndIf, poundEndIf) = self.expect(.poundEndifKeyword) + var unexpectedAfterPoundEndif: RawUnexpectedNodesSyntax? + if !self.currentToken.isAtStartOfLine { + var unexpectedTokens = [RawTokenSyntax]() + var loopProgress = LoopProgressCondition() + while !self.at(.eof), !currentToken.isAtStartOfLine, loopProgress.evaluate(self.currentToken) { + unexpectedTokens += [self.consumeAnyToken()] + } + + unexpectedAfterPoundEndif = RawUnexpectedNodesSyntax(unexpectedTokens, arena: self.arena) + } return RawIfConfigDeclSyntax( clauses: RawIfConfigClauseListSyntax(elements: clauses, arena: self.arena), unexpectedBeforePoundEndIf, poundEndif: poundEndIf, + unexpectedAfterPoundEndif, arena: self.arena ) } diff --git a/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift b/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift index e41df780f42..15613f1bdf2 100644 --- a/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift +++ b/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift @@ -1136,6 +1136,15 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor { } } } + + if let unexpectedAfterPoundEndif = node.unexpectedAfterPoundEndif { + addDiagnostic( + unexpectedAfterPoundEndif, + .extraTokensFollowingConditionalCompilationDirective, + handledNodes: [unexpectedAfterPoundEndif.id] + ) + } + return .visitChildren } diff --git a/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift b/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift index 8b000d2757e..ebcc63779b0 100644 --- a/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift +++ b/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift @@ -156,6 +156,9 @@ extension DiagnosticMessage where Self == StaticParserError { public static var expectedSequenceExpressionInForEachLoop: Self { .init("expected Sequence expression for for-each loop") } + 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?") } diff --git a/Tests/SwiftParserTest/DirectiveTests.swift b/Tests/SwiftParserTest/DirectiveTests.swift index 6f76a1d02de..7239d99bd64 100644 --- a/Tests/SwiftParserTest/DirectiveTests.swift +++ b/Tests/SwiftParserTest/DirectiveTests.swift @@ -197,4 +197,20 @@ final class DirectiveTests: XCTestCase { ) } + func testFollowedByDeclarations() { + assertParse( + """ + struct Foo { + #if false + var x: Int + #endif1️⃣; var x = 1 + } + """, + diagnostics: [ + DiagnosticSpec( + message: "extra tokens following conditional compilation directive" + ) + ] + ) + } } From 25a1d5d2b9d6f12b4aa14c9451baa7c5af871398 Mon Sep 17 00:00:00 2001 From: Kim de Vos Date: Tue, 27 Jun 2023 21:09:26 +0200 Subject: [PATCH 2/4] Add diagnostic if `#sourceLocation` is followed by any declarations --- Sources/SwiftParser/Directives.swift | 29 +++++++++++++------ .../ParseDiagnosticsGenerator.swift | 16 ++++++++++ .../ParserDiagnosticMessages.swift | 3 ++ Tests/SwiftParserTest/DirectiveTests.swift | 21 ++++++++++++++ 4 files changed, 60 insertions(+), 9 deletions(-) diff --git a/Sources/SwiftParser/Directives.swift b/Sources/SwiftParser/Directives.swift index f7836d479b9..c299c704999 100644 --- a/Sources/SwiftParser/Directives.swift +++ b/Sources/SwiftParser/Directives.swift @@ -163,16 +163,8 @@ extension Parser { } let (unexpectedBeforePoundEndIf, poundEndIf) = self.expect(.poundEndifKeyword) - var unexpectedAfterPoundEndif: RawUnexpectedNodesSyntax? - if !self.currentToken.isAtStartOfLine { - var unexpectedTokens = [RawTokenSyntax]() - var loopProgress = LoopProgressCondition() - while !self.at(.eof), !currentToken.isAtStartOfLine, loopProgress.evaluate(self.currentToken) { - unexpectedTokens += [self.consumeAnyToken()] - } + let unexpectedAfterPoundEndif = self.consumeRemainingTokenOnLine() - unexpectedAfterPoundEndif = RawUnexpectedNodesSyntax(unexpectedTokens, arena: self.arena) - } return RawIfConfigDeclSyntax( clauses: RawIfConfigClauseListSyntax(elements: clauses, arena: self.arena), unexpectedBeforePoundEndIf, @@ -267,6 +259,8 @@ extension Parser { args = nil } let (unexpectedBeforeRParen, rparen) = self.expect(.rightParen) + let unexpectedAfterRightParen = self.consumeRemainingTokenOnLine() + return RawPoundSourceLocationSyntax( poundSourceLocation: line, unexpectedBeforeLParen, @@ -274,7 +268,24 @@ extension Parser { args: args, unexpectedBeforeRParen, rightParen: rparen, + unexpectedAfterRightParen, arena: self.arena ) } + + /// Consumes remaining token on the line and returns a ``RawUnexpectedNodesSyntax`` + /// if there is any tokens consumed. + private 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) + } } diff --git a/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift b/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift index 15613f1bdf2..74f158e0a0d 100644 --- a/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift +++ b/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift @@ -1424,6 +1424,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 diff --git a/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift b/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift index ebcc63779b0..349aa5641fb 100644 --- a/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift +++ b/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift @@ -156,6 +156,9 @@ 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") } diff --git a/Tests/SwiftParserTest/DirectiveTests.swift b/Tests/SwiftParserTest/DirectiveTests.swift index 7239d99bd64..d9879fc2fec 100644 --- a/Tests/SwiftParserTest/DirectiveTests.swift +++ b/Tests/SwiftParserTest/DirectiveTests.swift @@ -213,4 +213,25 @@ final class DirectiveTests: XCTestCase { ] ) } + + 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" + ) + ] + ) + } } From 6e02c9416888c2aef6a6169c2eb9c1b7f4add070 Mon Sep 17 00:00:00 2001 From: Kim de Vos Date: Sat, 8 Jul 2023 16:08:33 +0200 Subject: [PATCH 3/4] Add diagnostic if `#if` is followed by any declarations --- Sources/SwiftParser/Directives.swift | 18 ++-------- Sources/SwiftParser/Parser.swift | 16 +++++++++ .../ParseDiagnosticsGenerator.swift | 16 +++++++++ Tests/SwiftParserTest/DirectiveTests.swift | 36 +++++++++++++++++-- 4 files changed, 68 insertions(+), 18 deletions(-) diff --git a/Sources/SwiftParser/Directives.swift b/Sources/SwiftParser/Directives.swift index c299c704999..40c98b4c430 100644 --- a/Sources/SwiftParser/Directives.swift +++ b/Sources/SwiftParser/Directives.swift @@ -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 ) @@ -272,20 +274,4 @@ extension Parser { arena: self.arena ) } - - /// Consumes remaining token on the line and returns a ``RawUnexpectedNodesSyntax`` - /// if there is any tokens consumed. - private 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) - } } diff --git a/Sources/SwiftParser/Parser.swift b/Sources/SwiftParser/Parser.swift index c8e0e8896dd..3e9a5404845 100644 --- a/Sources/SwiftParser/Parser.swift +++ b/Sources/SwiftParser/Parser.swift @@ -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 diff --git a/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift b/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift index 74f158e0a0d..efbab454832 100644 --- a/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift +++ b/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift @@ -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, diff --git a/Tests/SwiftParserTest/DirectiveTests.swift b/Tests/SwiftParserTest/DirectiveTests.swift index d9879fc2fec..d9fe13c49ee 100644 --- a/Tests/SwiftParserTest/DirectiveTests.swift +++ b/Tests/SwiftParserTest/DirectiveTests.swift @@ -197,13 +197,45 @@ final class DirectiveTests: XCTestCase { ) } - func testFollowedByDeclarations() { + func testEndIfFollowedByDeclarations() { assertParse( """ struct Foo { #if false var x: Int - #endif1️⃣; var x = 1 + #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: [ From 74dc3f8b88e9b35cf418b6aa00b9ac81eefcc737 Mon Sep 17 00:00:00 2001 From: Kim de Vos Date: Sat, 8 Jul 2023 16:23:01 +0200 Subject: [PATCH 4/4] Add diagnostic if `#elseif` and `#else` is followed by any declarations --- Sources/SwiftParser/Directives.swift | 5 ++ Tests/SwiftParserTest/DirectiveTests.swift | 55 ++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/Sources/SwiftParser/Directives.swift b/Sources/SwiftParser/Directives.swift index 40c98b4c430..6ac4d73f516 100644 --- a/Sources/SwiftParser/Directives.swift +++ b/Sources/SwiftParser/Directives.swift @@ -125,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)) { @@ -139,6 +141,7 @@ extension Parser { } else { condition = nil } + unexpectedBetweenConditionAndElements = self.consumeRemainingTokenOnLine() case .pound: if self.atElifTypo() { (unexpectedBeforePoundKeyword, poundKeyword) = self.eat(handle) @@ -148,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 } @@ -158,6 +162,7 @@ extension Parser { unexpectedBeforePoundKeyword, poundKeyword: poundKeyword, condition: condition, + unexpectedBetweenConditionAndElements, elements: syntax(&self, parseIfConfigClauseElements(parseElement, addSemicolonIfNeeded: addSemicolonIfNeeded)), arena: self.arena ) diff --git a/Tests/SwiftParserTest/DirectiveTests.swift b/Tests/SwiftParserTest/DirectiveTests.swift index d9fe13c49ee..780832aec02 100644 --- a/Tests/SwiftParserTest/DirectiveTests.swift +++ b/Tests/SwiftParserTest/DirectiveTests.swift @@ -246,6 +246,61 @@ final class DirectiveTests: XCTestCase { ) } + 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( """