Skip to content

Commit dc5564e

Browse files
authored
Merge pull request #395 from CodaFi/parsely-sage-rosemary-and-thyme
Adopt the Swift Parser
2 parents 7216a18 + ac8064f commit dc5564e

File tree

20 files changed

+171
-127
lines changed

20 files changed

+171
-127
lines changed

Package.swift

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ let package = Package(
4646
"SwiftFormatRules",
4747
"SwiftFormatWhitespaceLinter",
4848
.product(name: "SwiftSyntax", package: "swift-syntax"),
49-
.product(name: "SwiftSyntaxParser", package: "swift-syntax"),
49+
.product(name: "SwiftParser", package: "swift-syntax"),
5050
]
5151
),
5252
.target(
@@ -89,7 +89,7 @@ let package = Package(
8989
"SwiftFormatCore",
9090
"SwiftFormatRules",
9191
.product(name: "SwiftSyntax", package: "swift-syntax"),
92-
.product(name: "SwiftSyntaxParser", package: "swift-syntax"),
92+
.product(name: "SwiftParser", package: "swift-syntax"),
9393
]
9494
),
9595
.executableTarget(
@@ -100,6 +100,7 @@ let package = Package(
100100
"SwiftFormatCore",
101101
.product(name: "ArgumentParser", package: "swift-argument-parser"),
102102
.product(name: "SwiftSyntax", package: "swift-syntax"),
103+
.product(name: "SwiftParser", package: "swift-syntax"),
103104
.product(name: "TSCBasic", package: "swift-tools-support-core"),
104105
]
105106
),
@@ -109,7 +110,7 @@ let package = Package(
109110
dependencies: [
110111
"SwiftFormat",
111112
.product(name: "SwiftSyntax", package: "swift-syntax"),
112-
.product(name: "SwiftSyntaxParser", package: "swift-syntax"),
113+
.product(name: "SwiftParser", package: "swift-syntax"),
113114
]
114115
),
115116
.testTarget(
@@ -122,7 +123,7 @@ let package = Package(
122123
"SwiftFormatConfiguration",
123124
"SwiftFormatCore",
124125
.product(name: "SwiftSyntax", package: "swift-syntax"),
125-
.product(name: "SwiftSyntaxParser", package: "swift-syntax"),
126+
.product(name: "SwiftParser", package: "swift-syntax"),
126127
]
127128
),
128129
.testTarget(
@@ -131,7 +132,7 @@ let package = Package(
131132
"SwiftFormatTestSupport",
132133
"SwiftFormatWhitespaceLinter",
133134
.product(name: "SwiftSyntax", package: "swift-syntax"),
134-
.product(name: "SwiftSyntaxParser", package: "swift-syntax"),
135+
.product(name: "SwiftParser", package: "swift-syntax"),
135136
]
136137
),
137138
.testTarget(
@@ -143,7 +144,7 @@ let package = Package(
143144
"SwiftFormatRules",
144145
"SwiftFormatTestSupport",
145146
.product(name: "SwiftSyntax", package: "swift-syntax"),
146-
.product(name: "SwiftSyntaxParser", package: "swift-syntax"),
147+
.product(name: "SwiftParser", package: "swift-syntax"),
147148
]
148149
),
149150
.testTarget(
@@ -155,7 +156,7 @@ let package = Package(
155156
"SwiftFormatRules",
156157
"SwiftFormatTestSupport",
157158
.product(name: "SwiftSyntax", package: "swift-syntax"),
158-
.product(name: "SwiftSyntaxParser", package: "swift-syntax"),
159+
.product(name: "SwiftParser", package: "swift-syntax"),
159160
]
160161
),
161162
.testTarget(
@@ -166,7 +167,7 @@ let package = Package(
166167
"SwiftFormatTestSupport",
167168
"SwiftFormatWhitespaceLinter",
168169
.product(name: "SwiftSyntax", package: "swift-syntax"),
169-
.product(name: "SwiftSyntaxParser", package: "swift-syntax"),
170+
.product(name: "SwiftParser", package: "swift-syntax"),
170171
]
171172
),
172173
]
@@ -182,7 +183,7 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil {
182183
branch: "main"
183184
),
184185
.package(
185-
url: "https://github.com/apple/swift-syntax",
186+
url: "https://github.com/apple/swift-syntax.git",
186187
branch: "main"
187188
),
188189
.package(

Sources/SwiftFormat/SwiftFormatter.swift

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import SwiftFormatCore
1616
import SwiftFormatPrettyPrint
1717
import SwiftFormatRules
1818
import SwiftSyntax
19-
import SwiftSyntaxParser
19+
import SwiftParser
20+
import SwiftDiagnostics
2021

2122
/// Formats Swift source code or syntax trees according to the Swift style guidelines.
2223
public final class SwiftFormatter {
@@ -55,7 +56,7 @@ public final class SwiftFormatter {
5556
public func format<Output: TextOutputStream>(
5657
contentsOf url: URL,
5758
to outputStream: inout Output,
58-
parsingDiagnosticHandler: ((Diagnostic) -> Void)? = nil
59+
parsingDiagnosticHandler: ((Diagnostic, SourceLocation) -> Void)? = nil
5960
) throws {
6061
guard FileManager.default.isReadableFile(atPath: url.path) else {
6162
throw SwiftFormatError.fileNotReadable
@@ -64,8 +65,16 @@ public final class SwiftFormatter {
6465
if FileManager.default.fileExists(atPath: url.path, isDirectory: &isDir), isDir.boolValue {
6566
throw SwiftFormatError.isDirectory
6667
}
67-
let sourceFile = try SyntaxParser.parse(url, diagnosticHandler: parsingDiagnosticHandler)
6868
let source = try String(contentsOf: url, encoding: .utf8)
69+
let sourceFile = try Parser.parse(source: source)
70+
if let parsingDiagnosticHandler = parsingDiagnosticHandler {
71+
let expectedConverter = SourceLocationConverter(file: url.path, tree: sourceFile)
72+
let diagnostics = ParseDiagnosticsGenerator.diagnostics(for: sourceFile)
73+
for diagnostic in diagnostics {
74+
let location = diagnostic.location(converter: expectedConverter)
75+
parsingDiagnosticHandler(diagnostic, location)
76+
}
77+
}
6978
try format(syntax: sourceFile, assumingFileURL: url, source: source, to: &outputStream)
7079
}
7180

@@ -85,10 +94,17 @@ public final class SwiftFormatter {
8594
source: String,
8695
assumingFileURL url: URL?,
8796
to outputStream: inout Output,
88-
parsingDiagnosticHandler: ((Diagnostic) -> Void)? = nil
97+
parsingDiagnosticHandler: ((Diagnostic, SourceLocation) -> Void)? = nil
8998
) throws {
90-
let sourceFile =
91-
try SyntaxParser.parse(source: source, diagnosticHandler: parsingDiagnosticHandler)
99+
let sourceFile = try Parser.parse(source: source)
100+
if let parsingDiagnosticHandler = parsingDiagnosticHandler {
101+
let expectedConverter = SourceLocationConverter(file: url?.path ?? "<unknown>", tree: sourceFile)
102+
let diagnostics = ParseDiagnosticsGenerator.diagnostics(for: sourceFile)
103+
for diagnostic in diagnostics {
104+
let location = diagnostic.location(converter: expectedConverter)
105+
parsingDiagnosticHandler(diagnostic, location)
106+
}
107+
}
92108
try format(syntax: sourceFile, assumingFileURL: url, source: source, to: &outputStream)
93109
}
94110

Sources/SwiftFormat/SwiftLinter.swift

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import SwiftFormatPrettyPrint
1717
import SwiftFormatRules
1818
import SwiftFormatWhitespaceLinter
1919
import SwiftSyntax
20-
import SwiftSyntaxParser
20+
import SwiftParser
21+
import SwiftDiagnostics
2122

2223
/// Diagnoses and reports problems in Swift source code or syntax trees according to the Swift style
2324
/// guidelines.
@@ -53,7 +54,7 @@ public final class SwiftLinter {
5354
/// - Throws: If an unrecoverable error occurs when formatting the code.
5455
public func lint(
5556
contentsOf url: URL,
56-
parsingDiagnosticHandler: ((Diagnostic) -> Void)? = nil
57+
parsingDiagnosticHandler: ((Diagnostic, SourceLocation) -> Void)? = nil
5758
) throws {
5859
guard FileManager.default.isReadableFile(atPath: url.path) else {
5960
throw SwiftFormatError.fileNotReadable
@@ -62,9 +63,10 @@ public final class SwiftLinter {
6263
if FileManager.default.fileExists(atPath: url.path, isDirectory: &isDir), isDir.boolValue {
6364
throw SwiftFormatError.isDirectory
6465
}
65-
let sourceFile = try SyntaxParser.parse(url, diagnosticHandler: parsingDiagnosticHandler)
6666
let source = try String(contentsOf: url, encoding: .utf8)
67-
try lint(syntax: sourceFile, assumingFileURL: url, source: source)
67+
let sourceFile = try Parser.parse(source: source)
68+
try lint(syntax: sourceFile, assumingFileURL: url,
69+
source: source, parsingDiagnosticHandler: parsingDiagnosticHandler)
6870
}
6971

7072
/// Lints the given Swift source code.
@@ -78,11 +80,11 @@ public final class SwiftLinter {
7880
public func lint(
7981
source: String,
8082
assumingFileURL url: URL,
81-
parsingDiagnosticHandler: ((Diagnostic) -> Void)? = nil
83+
parsingDiagnosticHandler: ((Diagnostic, SourceLocation) -> Void)? = nil
8284
) throws {
83-
let sourceFile =
84-
try SyntaxParser.parse(source: source, diagnosticHandler: parsingDiagnosticHandler)
85-
try lint(syntax: sourceFile, assumingFileURL: url, source: source)
85+
let sourceFile = try Parser.parse(source: source)
86+
try lint(syntax: sourceFile, assumingFileURL: url,
87+
source: source, parsingDiagnosticHandler: parsingDiagnosticHandler)
8688
}
8789

8890
/// Lints the given Swift syntax tree.
@@ -94,14 +96,29 @@ public final class SwiftLinter {
9496
/// - url: A file URL denoting the filename/path that should be assumed for this syntax tree.
9597
/// - Throws: If an unrecoverable error occurs when formatting the code.
9698
public func lint(syntax: SourceFileSyntax, assumingFileURL url: URL) throws {
97-
try lint(syntax: syntax, assumingFileURL: url, source: nil)
99+
try lint(syntax: syntax, assumingFileURL: url,
100+
source: nil, parsingDiagnosticHandler: nil)
98101
}
99102

100-
private func lint(syntax: SourceFileSyntax, assumingFileURL url: URL, source: String?) throws {
103+
private func lint(
104+
syntax: SourceFileSyntax,
105+
assumingFileURL url: URL,
106+
source: String?,
107+
parsingDiagnosticHandler: ((Diagnostic, SourceLocation) -> Void)?
108+
) throws {
101109
if let position = _firstInvalidSyntaxPosition(in: Syntax(syntax)) {
102110
throw SwiftFormatError.fileContainsInvalidSyntax(position: position)
103111
}
104112

113+
if let parsingDiagnosticHandler = parsingDiagnosticHandler {
114+
let expectedConverter = SourceLocationConverter(file: url.path, tree: syntax)
115+
let diagnostics = ParseDiagnosticsGenerator.diagnostics(for: syntax)
116+
for diagnostic in diagnostics {
117+
let location = diagnostic.location(converter: expectedConverter)
118+
parsingDiagnosticHandler(diagnostic, location)
119+
}
120+
}
121+
105122
let context = Context(
106123
configuration: configuration, findingConsumer: findingConsumer, fileURL: url,
107124
sourceFileSyntax: syntax, source: source, ruleNameCache: ruleNameCache)

Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
6262
self.config = configuration
6363
self.operatorContext = operatorContext
6464
self.maxlinelength = config.lineLength
65-
super.init(viewMode: .sourceAccurate)
65+
super.init(viewMode: .all)
6666
}
6767

6868
func makeStream(from node: Syntax) -> [Token] {
@@ -328,7 +328,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
328328
// Due to visitation order, the matching .open break is added in ParameterClauseSyntax.
329329
after(node.signature.lastToken, tokens: .close)
330330
}
331-
331+
332332
arrangeParameterClause(node.signature.input, forcesBreakBeforeRightParen: node.body != nil)
333333

334334
// Prioritize keeping "<modifiers> init<punctuation>" together.
@@ -525,7 +525,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
525525

526526
// Breaks are only allowed after `else` when there's a comment; otherwise there shouldn't be
527527
// any newlines between `else` and the open brace or a following `if`.
528-
if let tokenAfterElse = elseKeyword.nextToken, tokenAfterElse.leadingTrivia.hasLineComment {
528+
if let tokenAfterElse = elseKeyword.nextToken(viewMode: .all), tokenAfterElse.leadingTrivia.hasLineComment {
529529
after(node.elseKeyword, tokens: .break(.same, size: 1))
530530
} else if let elseBody = node.elseBody, elseBody.is(IfStmtSyntax.self) {
531531
after(node.elseKeyword, tokens: .space)
@@ -793,7 +793,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
793793
// to exist at the EOL with the left paren or on its own line. The contents are always
794794
// indented on the following lines, since parens always create a scope. An open/close break
795795
// pair isn't used here to avoid forcing the closing paren down onto a new line.
796-
if node.leftParen.nextToken?.leadingTrivia.hasLineComment ?? false {
796+
if node.leftParen.nextToken(viewMode: .all)?.leadingTrivia.hasLineComment ?? false {
797797
after(node.leftParen, tokens: .break(.continue, size: 0))
798798
}
799799
} else if elementCount > 1 {
@@ -951,8 +951,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
951951
// When this function call is wrapped by a try-expr or await-expr, the group applied when
952952
// visiting that wrapping expression is sufficient. Adding another group here in that case
953953
// can result in unnecessarily breaking after the try/await keyword.
954-
if !(base.firstToken?.previousToken?.parent?.is(TryExprSyntax.self) ?? false
955-
|| base.firstToken?.previousToken?.parent?.is(AwaitExprSyntax.self) ?? false) {
954+
if !(base.firstToken?.previousToken(viewMode: .all)?.parent?.is(TryExprSyntax.self) ?? false
955+
|| base.firstToken?.previousToken(viewMode: .all)?.parent?.is(AwaitExprSyntax.self) ?? false) {
956956
before(base.firstToken, tokens: .open)
957957
after(calledMemberAccessExpr.name.lastToken, tokens: .close)
958958
}
@@ -1304,7 +1304,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
13041304
if let lastElemTok = node.elements.lastToken {
13051305
after(lastElemTok, tokens: .break(breakKindClose, newlines: .soft), .close)
13061306
} else {
1307-
before(tokenToOpenWith.nextToken, tokens: .break(breakKindClose, newlines: .soft), .close)
1307+
before(tokenToOpenWith.nextToken(viewMode: .all), tokens: .break(breakKindClose, newlines: .soft), .close)
13081308
}
13091309

13101310
if isNestedInPostfixIfConfig(node: Syntax(node)) {
@@ -1915,7 +1915,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
19151915
var closesNeeded: Int = 0
19161916
var closeAfterToken: TokenSyntax? = nil
19171917

1918-
if let typeAnnotation = node.typeAnnotation {
1918+
if let typeAnnotation = node.typeAnnotation, !typeAnnotation.type.is(MissingTypeSyntax.self) {
19191919
after(
19201920
typeAnnotation.colon,
19211921
tokens: .break(.open(kind: .continuation), newlines: .elective(ignoresDiscretionary: true)))
@@ -2190,6 +2190,11 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
21902190
}
21912191

21922192
override func visit(_ node: GenericWhereClauseSyntax) -> SyntaxVisitorContinueKind {
2193+
guard node.whereKeyword != node.lastToken else {
2194+
verbatimToken(Syntax(node))
2195+
return .skipChildren
2196+
}
2197+
21932198
after(node.whereKeyword, tokens: .break(.open))
21942199
after(node.lastToken, tokens: .break(.close, size: 0))
21952200

@@ -2378,6 +2383,11 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
23782383

23792384
// MARK: - Nodes representing unknown or malformed syntax
23802385

2386+
override func visit(_ node: UnexpectedNodesSyntax) -> SyntaxVisitorContinueKind {
2387+
verbatimToken(Syntax(node))
2388+
return .skipChildren
2389+
}
2390+
23812391
override func visit(_ node: UnknownDeclSyntax) -> SyntaxVisitorContinueKind {
23822392
verbatimToken(Syntax(node))
23832393
return .skipChildren
@@ -2429,7 +2439,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
24292439
appendMultilineStringSegments(at: pendingSegmentIndex)
24302440
} else if !ignoredTokens.contains(token) {
24312441
// Otherwise, it's just a regular token, so add the text as-is.
2432-
appendToken(.syntax(token.text))
2442+
appendToken(.syntax(token.presence == .present ? token.text : ""))
24332443
}
24342444

24352445
appendTrailingTrivia(token)
@@ -2883,7 +2893,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
28832893
}
28842894

28852895
private func extractLeadingTrivia(_ token: TokenSyntax) {
2886-
var isStartOfFile = token.previousToken == nil
2896+
var isStartOfFile = token.previousToken(viewMode: .all) == nil
28872897
let trivia = token.leadingTrivia
28882898

28892899
// If we're at the end of the file, determine at which index to stop checking trivia pieces to
@@ -3260,12 +3270,12 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
32603270
/// alongside the last token of the given node. Any tokens between `node.lastToken` and the
32613271
/// returned node's `lastToken` are delimiter tokens that shouldn't be preceded by a break.
32623272
private func outermostEnclosingNode(from node: Syntax) -> Syntax? {
3263-
guard let afterToken = node.lastToken?.nextToken, closingDelimiterTokens.contains(afterToken)
3273+
guard let afterToken = node.lastToken?.nextToken(viewMode: .all), closingDelimiterTokens.contains(afterToken)
32643274
else {
32653275
return nil
32663276
}
32673277
var parenthesizedExpr = afterToken.parent
3268-
while let nextToken = parenthesizedExpr?.lastToken?.nextToken,
3278+
while let nextToken = parenthesizedExpr?.lastToken?.nextToken(viewMode: .all),
32693279
closingDelimiterTokens.contains(nextToken),
32703280
let nextExpr = nextToken.parent
32713281
{
@@ -3355,9 +3365,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
33553365
// followed by a dot (for example, in an implicit member reference)---removing the spaces in
33563366
// those situations would cause the parser to greedily treat the combined sequence of
33573367
// operator characters as a single operator.
3358-
if case .postfixOperator? = token.previousToken?.tokenKind { return true }
3368+
if case .postfixOperator? = token.previousToken(viewMode: .all)?.tokenKind { return true }
33593369

3360-
switch token.nextToken?.tokenKind {
3370+
switch token.nextToken(viewMode: .all)?.tokenKind {
33613371
case .prefixOperator?, .prefixPeriod?: return true
33623372
default: return false
33633373
}
@@ -3389,7 +3399,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
33893399
// The leading trivia of the next token, after the ignored node, may contain content that
33903400
// belongs with the ignored node. The trivia extraction that is performed for `lastToken` later
33913401
// excludes that content so it needs to be extracted and added to the token stream here.
3392-
if let next = node.lastToken?.nextToken, let trivia = next.leadingTrivia.first {
3402+
if let next = node.lastToken?.nextToken(viewMode: .all), let trivia = next.leadingTrivia.first {
33933403
switch trivia {
33943404
case .lineComment, .blockComment:
33953405
trivia.write(to: &nodeText)
@@ -3581,7 +3591,7 @@ class CommentMovingRewriter: SyntaxRewriter {
35813591
override func visit(_ node: SequenceExprSyntax) -> ExprSyntax {
35823592
for element in node.elements {
35833593
if let binaryOperatorExpr = element.as(BinaryOperatorExprSyntax.self),
3584-
let followingToken = binaryOperatorExpr.operatorToken.nextToken,
3594+
let followingToken = binaryOperatorExpr.operatorToken.nextToken(viewMode: .all),
35853595
followingToken.leadingTrivia.hasLineComment
35863596
{
35873597
// Rewrite the trivia so that the comment is in the operator token's leading trivia.

0 commit comments

Comments
 (0)