Skip to content

Commit 1f4ebd9

Browse files
authored
Merge pull request #1833 from kimdv/kimdv/fix-diagnostic-for-declarations-after-endif
Add diagnostic if `#endif` is followed by any declarations
2 parents e7068e8 + 74dc3f8 commit 1f4ebd9

File tree

6 files changed

+201
-1
lines changed

6 files changed

+201
-1
lines changed

Sources/SwiftBasicFormat/BasicFormat.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,12 +186,12 @@ open class BasicFormat: SyntaxRewriter {
186186
}
187187

188188
switch (first?.tokenKind, second?.tokenKind) {
189-
190189
case (.multilineStringQuote, .backslash), // string interpolation segment inside a multi-line string literal
191190
(.multilineStringQuote, .multilineStringQuote), // empty multi-line string literal
192191
(.multilineStringQuote, .stringSegment), // segment starting a multi-line string literal
193192
(.stringSegment, .multilineStringQuote), // ending a multi-line string literal that has a string interpolation segment at its end
194193
(.rightParen, .multilineStringQuote), // ending a multi-line string literal that has a string interpolation segment at its end
194+
(.poundEndifKeyword, _),
195195
(_, .poundElseKeyword),
196196
(_, .poundElseifKeyword),
197197
(_, .poundEndifKeyword),

Sources/SwiftParser/Directives.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,14 @@ extension Parser {
106106
// Parse #if
107107
let (unexpectedBeforePoundIfKeyword, poundIfKeyword) = self.expect(.poundIfKeyword)
108108
let condition = RawExprSyntax(self.parseSequenceExpression(.basic, forDirective: true))
109+
let unexpectedBetweenConditionAndElements = self.consumeRemainingTokenOnLine()
109110

110111
clauses.append(
111112
RawIfConfigClauseSyntax(
112113
unexpectedBeforePoundIfKeyword,
113114
poundKeyword: poundIfKeyword,
114115
condition: condition,
116+
unexpectedBetweenConditionAndElements,
115117
elements: syntax(&self, parseIfConfigClauseElements(parseElement, addSemicolonIfNeeded: addSemicolonIfNeeded)),
116118
arena: self.arena
117119
)
@@ -123,11 +125,13 @@ extension Parser {
123125
var unexpectedBeforePoundKeyword: RawUnexpectedNodesSyntax?
124126
var poundKeyword: RawTokenSyntax
125127
let condition: RawExprSyntax?
128+
let unexpectedBetweenConditionAndElements: RawUnexpectedNodesSyntax?
126129

127130
switch match {
128131
case .poundElseifKeyword:
129132
(unexpectedBeforePoundKeyword, poundKeyword) = self.eat(handle)
130133
condition = RawExprSyntax(self.parseSequenceExpression(.basic, forDirective: true))
134+
unexpectedBetweenConditionAndElements = self.consumeRemainingTokenOnLine()
131135
case .poundElseKeyword:
132136
(unexpectedBeforePoundKeyword, poundKeyword) = self.eat(handle)
133137
if let ifToken = self.consume(if: .init(.if, allowAtStartOfLine: false)) {
@@ -137,6 +141,7 @@ extension Parser {
137141
} else {
138142
condition = nil
139143
}
144+
unexpectedBetweenConditionAndElements = self.consumeRemainingTokenOnLine()
140145
case .pound:
141146
if self.atElifTypo() {
142147
(unexpectedBeforePoundKeyword, poundKeyword) = self.eat(handle)
@@ -146,6 +151,7 @@ extension Parser {
146151
unexpectedBeforePoundKeyword = RawUnexpectedNodesSyntax(combining: unexpectedBeforePoundKeyword, poundKeyword, elif, arena: self.arena)
147152
poundKeyword = self.missingToken(.poundElseifKeyword)
148153
condition = RawExprSyntax(self.parseSequenceExpression(.basic, forDirective: true))
154+
unexpectedBetweenConditionAndElements = self.consumeRemainingTokenOnLine()
149155
} else {
150156
break LOOP
151157
}
@@ -156,17 +162,21 @@ extension Parser {
156162
unexpectedBeforePoundKeyword,
157163
poundKeyword: poundKeyword,
158164
condition: condition,
165+
unexpectedBetweenConditionAndElements,
159166
elements: syntax(&self, parseIfConfigClauseElements(parseElement, addSemicolonIfNeeded: addSemicolonIfNeeded)),
160167
arena: self.arena
161168
)
162169
)
163170
}
164171

165172
let (unexpectedBeforePoundEndIf, poundEndIf) = self.expect(.poundEndifKeyword)
173+
let unexpectedAfterPoundEndif = self.consumeRemainingTokenOnLine()
174+
166175
return RawIfConfigDeclSyntax(
167176
clauses: RawIfConfigClauseListSyntax(elements: clauses, arena: self.arena),
168177
unexpectedBeforePoundEndIf,
169178
poundEndif: poundEndIf,
179+
unexpectedAfterPoundEndif,
170180
arena: self.arena
171181
)
172182
}
@@ -256,13 +266,16 @@ extension Parser {
256266
args = nil
257267
}
258268
let (unexpectedBeforeRParen, rparen) = self.expect(.rightParen)
269+
let unexpectedAfterRightParen = self.consumeRemainingTokenOnLine()
270+
259271
return RawPoundSourceLocationSyntax(
260272
poundSourceLocation: line,
261273
unexpectedBeforeLParen,
262274
leftParen: lparen,
263275
args: args,
264276
unexpectedBeforeRParen,
265277
rightParen: rparen,
278+
unexpectedAfterRightParen,
266279
arena: self.arena
267280
)
268281
}

Sources/SwiftParser/Parser.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,22 @@ extension Parser {
257257
return self.consumeAnyToken()
258258
}
259259

260+
/// Consumes remaining token on the line and returns a ``RawUnexpectedNodesSyntax``
261+
/// if there is any tokens consumed.
262+
mutating func consumeRemainingTokenOnLine() -> RawUnexpectedNodesSyntax? {
263+
guard !self.currentToken.isAtStartOfLine else {
264+
return nil
265+
}
266+
267+
var unexpectedTokens = [RawTokenSyntax]()
268+
var loopProgress = LoopProgressCondition()
269+
while !self.at(.eof), !currentToken.isAtStartOfLine, loopProgress.evaluate(self.currentToken) {
270+
unexpectedTokens += [self.consumeAnyToken()]
271+
}
272+
273+
return RawUnexpectedNodesSyntax(unexpectedTokens, arena: self.arena)
274+
}
275+
260276
}
261277

262278
// MARK: Check if we can recover to a token

Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,6 +1100,22 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
11001100
return .visitChildren
11011101
}
11021102

1103+
public override func visit(_ node: IfConfigClauseSyntax) -> SyntaxVisitorContinueKind {
1104+
if shouldSkip(node) {
1105+
return .skipChildren
1106+
}
1107+
1108+
if let unexpectedBetweenConditionAndElements = node.unexpectedBetweenConditionAndElements {
1109+
addDiagnostic(
1110+
unexpectedBetweenConditionAndElements,
1111+
.extraTokensFollowingConditionalCompilationDirective,
1112+
handledNodes: [unexpectedBetweenConditionAndElements.id]
1113+
)
1114+
}
1115+
1116+
return .visitChildren
1117+
}
1118+
11031119
public override func visit(_ node: IfConfigDeclSyntax) -> SyntaxVisitorContinueKind {
11041120
for clause in node.clauses where clause.hasError {
11051121
if let unexpectedBeforePoundKeyword = clause.unexpectedBeforePoundKeyword,
@@ -1136,6 +1152,15 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
11361152
}
11371153
}
11381154
}
1155+
1156+
if let unexpectedAfterPoundEndif = node.unexpectedAfterPoundEndif {
1157+
addDiagnostic(
1158+
unexpectedAfterPoundEndif,
1159+
.extraTokensFollowingConditionalCompilationDirective,
1160+
handledNodes: [unexpectedAfterPoundEndif.id]
1161+
)
1162+
}
1163+
11391164
return .visitChildren
11401165
}
11411166

@@ -1415,6 +1440,22 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
14151440
return .visitChildren
14161441
}
14171442

1443+
public override func visit(_ node: PoundSourceLocationSyntax) -> SyntaxVisitorContinueKind {
1444+
if shouldSkip(node) {
1445+
return .skipChildren
1446+
}
1447+
1448+
if let unexpectedAfterRightParen = node.unexpectedAfterRightParen {
1449+
addDiagnostic(
1450+
unexpectedAfterRightParen,
1451+
.extraTokensAtTheEndOfSourceLocationDirective,
1452+
handledNodes: [unexpectedAfterRightParen.id]
1453+
)
1454+
}
1455+
1456+
return .visitChildren
1457+
}
1458+
14181459
public override func visit(_ node: PrecedenceGroupAssignmentSyntax) -> SyntaxVisitorContinueKind {
14191460
if shouldSkip(node) {
14201461
return .skipChildren

Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,12 @@ extension DiagnosticMessage where Self == StaticParserError {
156156
public static var expectedSequenceExpressionInForEachLoop: Self {
157157
.init("expected Sequence expression for for-each loop")
158158
}
159+
public static var extraTokensAtTheEndOfSourceLocationDirective: Self {
160+
.init("extra tokens following the #sourceLocation directive")
161+
}
162+
public static var extraTokensFollowingConditionalCompilationDirective: Self {
163+
.init("extra tokens following conditional compilation directive")
164+
}
159165
public static var extraRightBracket: Self {
160166
.init("unexpected ']' in type; did you mean to write an array type?")
161167
}

Tests/SwiftParserTest/DirectiveTests.swift

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,128 @@ final class DirectiveTests: XCTestCase {
197197
)
198198
}
199199

200+
func testEndIfFollowedByDeclarations() {
201+
assertParse(
202+
"""
203+
struct Foo {
204+
#if false
205+
var x: Int
206+
#endif1️⃣; var x = 1
207+
}
208+
""",
209+
diagnostics: [
210+
DiagnosticSpec(
211+
message: "extra tokens following conditional compilation directive"
212+
)
213+
]
214+
)
215+
}
216+
217+
func testIfFollowByDeclarations() {
218+
assertParse(
219+
"""
220+
struct Foo {
221+
#if DEBUG1️⃣; var x = 1
222+
var x: Int
223+
#endif
224+
}
225+
""",
226+
diagnostics: [
227+
DiagnosticSpec(
228+
message: "extra tokens following conditional compilation directive"
229+
)
230+
]
231+
)
232+
233+
assertParse(
234+
"""
235+
struct Foo {
236+
#if DEBUG || UAT1️⃣; var x = 1
237+
var x: Int
238+
#endif
239+
}
240+
""",
241+
diagnostics: [
242+
DiagnosticSpec(
243+
message: "extra tokens following conditional compilation directive"
244+
)
245+
]
246+
)
247+
}
248+
249+
func testElseIfFollowByDeclarations() {
250+
assertParse(
251+
"""
252+
struct Foo {
253+
#if DEBUG
254+
var x: Int = 1
255+
#elseif UAT1️⃣; var x = 1
256+
var x: Int = 2
257+
#endif
258+
}
259+
""",
260+
diagnostics: [
261+
DiagnosticSpec(
262+
message: "extra tokens following conditional compilation directive"
263+
)
264+
]
265+
)
266+
267+
assertParse(
268+
"""
269+
struct Foo {
270+
#if DEBUG
271+
var x: Int = 1
272+
#elseif UAT || UAT1️⃣; var x = 1
273+
var x: Int = 2
274+
#endif
275+
}
276+
""",
277+
diagnostics: [
278+
DiagnosticSpec(
279+
message: "extra tokens following conditional compilation directive"
280+
)
281+
]
282+
)
283+
}
284+
285+
func testElseFollowByDeclarations() {
286+
assertParse(
287+
"""
288+
struct Foo {
289+
#if DEBUG
290+
var x: Int = 1
291+
#else1️⃣; var x = 1
292+
var x: Int = 2
293+
#endif
294+
}
295+
""",
296+
diagnostics: [
297+
DiagnosticSpec(
298+
message: "extra tokens following conditional compilation directive"
299+
)
300+
]
301+
)
302+
}
303+
304+
func testSourcelocationDirectiveFollowedByDeclarations() {
305+
assertParse(
306+
"""
307+
var sometName: Int
308+
#sourceLocation(file: "other.swift", line: 1)
309+
var someName: Int
310+
"""
311+
)
312+
313+
assertParse(
314+
"""
315+
#sourceLocation(file: "other.swift", line: 1)1️⃣; let x = 1
316+
""",
317+
diagnostics: [
318+
DiagnosticSpec(
319+
message: "extra tokens following the #sourceLocation directive"
320+
)
321+
]
322+
)
323+
}
200324
}

0 commit comments

Comments
 (0)