Skip to content

Commit c19de99

Browse files
committed
Rewrite Format to recursively format an entire SwiftSyntax tree
Previously, `Format` required very delicate interaction with SwiftSyntaxBuilder because it did not format a syntax tree recursively. This tight coupling was difficult to understand and even harder to describe. With the new split, `SwiftBasicFormat` can also be used outside of SwiftSyntaxBuilder.
1 parent b3f093a commit c19de99

File tree

65 files changed

+5856
-4317
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+5856
-4317
lines changed

CodeGeneration/Sources/SyntaxSupport/gyb_generated/TokenSpec.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ public let SYNTAX_TOKENS: [TokenSpec] = [
253253
StmtKeywordSpec(name: "Fallthrough", serializationCode: 35, text: "fallthrough", requiresTrailingSpace: true),
254254
StmtKeywordSpec(name: "Switch", serializationCode: 36, text: "switch", requiresTrailingSpace: true),
255255
StmtKeywordSpec(name: "Case", serializationCode: 37, text: "case", requiresTrailingSpace: true),
256-
StmtKeywordSpec(name: "Default", serializationCode: 38, text: "default", requiresTrailingSpace: true),
256+
StmtKeywordSpec(name: "Default", serializationCode: 38, text: "default"),
257257
StmtKeywordSpec(name: "Where", serializationCode: 39, text: "where", requiresLeadingSpace: true, requiresTrailingSpace: true),
258258
StmtKeywordSpec(name: "Catch", serializationCode: 40, text: "catch", requiresLeadingSpace: true),
259259
StmtKeywordSpec(name: "Throw", serializationCode: 50, text: "throw", requiresTrailingSpace: true),

CodeGeneration/Sources/Utils/SyntaxBuildableChild.swift

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,20 +35,12 @@ public extension Child {
3535
/// Generate a Swift expression that creates a proper SwiftSyntax node of type
3636
/// `type.syntax` from a variable named `varName` of type `type.buildable` that
3737
/// represents this child node.
38-
func generateExprBuildSyntaxNode(varName: ExpressibleAsExprBuildable, formatName: String) -> ExpressibleAsExprBuildable {
38+
func generateExprBuildSyntaxNode(varName: ExpressibleAsExprBuildable) -> ExpressibleAsExprBuildable {
3939
if type.isToken {
40-
return FunctionCallExpr(calledExpression: MemberAccessExpr(base: type.optionalChained(expr: varName), name: "buildToken")) {
41-
TupleExprElement(label: "format", expression: "format")
42-
}
40+
return FunctionCallExpr(calledExpression: MemberAccessExpr(base: type.optionalChained(expr: varName), name: "buildToken"))
4341
} else {
44-
var format: ExpressibleAsExprBuildable = formatName
45-
if isIndented {
46-
format = MemberAccessExpr(base: format, name: "_indented")
47-
}
4842
let expr = type.optionalChained(expr: varName)
49-
return FunctionCallExpr(calledExpression: MemberAccessExpr(base: expr, name: "build\(type.baseName)")) {
50-
TupleExprElement(label: "format", expression: format)
51-
}
43+
return FunctionCallExpr(calledExpression: MemberAccessExpr(base: expr, name: "build\(type.baseName)"))
5244
}
5345
}
5446

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Foundation
14+
import SwiftSyntax
15+
import SwiftSyntaxBuilder
16+
import SyntaxSupport
17+
import Utils
18+
19+
let basicFormatFile = SourceFile {
20+
ImportDecl(
21+
leadingTrivia: .docLineComment(copyrightHeader),
22+
path: "SwiftSyntax"
23+
)
24+
25+
ClassDecl(modifiers: [Token.open], identifier: "BasicFormat", inheritanceClause: TypeInheritanceClause { InheritedType(typeName: "SyntaxRewriter") }) {
26+
VariableDecl("public var indentationLevel: Int = 0")
27+
VariableDecl("open var indentation: TriviaPiece { .spaces(indentationLevel * 4) }")
28+
VariableDecl("private var indentedNewline: Trivia { Trivia(pieces: [.newlines(1), indentation]) }")
29+
VariableDecl("private var lastRewrittenToken: TokenSyntax?")
30+
31+
for node in SYNTAX_NODES where !node.isBase {
32+
if node.isSyntaxCollection {
33+
makeSyntaxCollectionRewriteFunc(node: node)
34+
} else {
35+
makeLayoutNodeRewriteFunc(node: node)
36+
}
37+
}
38+
39+
createTokenFormatFunction()
40+
}
41+
}
42+
43+
private func makeLayoutNodeRewriteFunc(node: Node) -> FunctionDecl {
44+
let rewriteResultType: String
45+
if node.isSyntaxCollection {
46+
rewriteResultType = "Syntax"
47+
} else {
48+
rewriteResultType = node.type.baseType?.syntaxBaseName ?? "Syntax"
49+
}
50+
return FunctionDecl(
51+
leadingTrivia: .newline,
52+
modifiers: [Token.open, Token(tokenSyntax: TokenSyntax.contextualKeyword("override", trailingTrivia: .space))],
53+
identifier: .identifier("visit"),
54+
signature: FunctionSignature(
55+
input: ParameterClause(parameterList: [
56+
FunctionParameter(
57+
firstName: Token.wildcard,
58+
secondName: .identifier("node"),
59+
colon: .colon,
60+
type: node.type.syntaxBaseName
61+
62+
)
63+
]),
64+
output: rewriteResultType
65+
)
66+
) {
67+
for child in node.children {
68+
if child.isIndented {
69+
SequenceExpr("indentationLevel += 1")
70+
}
71+
let variableLetVar = child.requiresLeadingNewline ? "var" : "let"
72+
if child.isOptional {
73+
VariableDecl("\(variableLetVar) \(child.swiftName) = node.\(child.swiftName).map(self.visit)?.cast(\(child.type.syntaxBaseName).self)")
74+
} else {
75+
VariableDecl("\(variableLetVar) \(child.swiftName) = self.visit(node.\(child.swiftName)).cast(\(child.type.syntaxBaseName).self)")
76+
}
77+
if child.requiresLeadingNewline {
78+
IfStmt(
79+
"""
80+
if \(child.swiftName).leadingTrivia.first?.isNewline != true {
81+
\(child.swiftName).leadingTrivia = indentedNewline + \(child.swiftName).leadingTrivia
82+
}
83+
"""
84+
)
85+
}
86+
if child.isIndented {
87+
SequenceExpr("indentationLevel -= 1")
88+
}
89+
}
90+
let reconstructed = FunctionCallExpr(calledExpression: "\(node.type.syntaxBaseName)") {
91+
for child in node.children {
92+
TupleExprElement(
93+
label: child.isUnexpectedNodes ? nil : child.swiftName,
94+
expression: child.swiftName
95+
)
96+
}
97+
}
98+
ReturnStmt("return \(rewriteResultType)(\(reconstructed))")
99+
}
100+
}
101+
102+
private func makeSyntaxCollectionRewriteFunc(node: Node) -> FunctionDecl {
103+
let rewriteResultType: String
104+
if node.isSyntaxCollection {
105+
rewriteResultType = "Syntax"
106+
} else {
107+
rewriteResultType = node.type.baseType?.syntaxBaseName ?? "Syntax"
108+
}
109+
return FunctionDecl(
110+
leadingTrivia: .newline,
111+
modifiers: [Token.open, Token(tokenSyntax: TokenSyntax.contextualKeyword("override", trailingTrivia: .space))],
112+
identifier: .identifier("visit"),
113+
signature: FunctionSignature(
114+
input: ParameterClause(parameterList: [
115+
FunctionParameter(
116+
firstName: Token.wildcard,
117+
secondName: .identifier("node"),
118+
colon: .colon,
119+
type: node.type.syntaxBaseName
120+
121+
)
122+
]),
123+
output: rewriteResultType
124+
)
125+
) {
126+
let formattedChildrenVarLet = node.elementsSeparatedByNewline ? "var" : "let"
127+
VariableDecl(
128+
"""
129+
\(formattedChildrenVarLet) formattedChildren = node.children(viewMode: .all).map {
130+
self.visit($0).cast(\(node.collectionElementType.syntaxBaseName).self)
131+
}
132+
"""
133+
)
134+
if node.elementsSeparatedByNewline {
135+
SequenceExpr(
136+
"""
137+
formattedChildren = formattedChildren.map {
138+
if $0.leadingTrivia?.first?.isNewline == true {
139+
return $0
140+
} else {
141+
return $0.withLeadingTrivia(indentedNewline + ($0.leadingTrivia ?? []))
142+
}
143+
}
144+
"""
145+
)
146+
}
147+
ReturnStmt("return Syntax(\(node.type.syntaxBaseName)(formattedChildren))")
148+
}
149+
}
150+
151+
private func createTokenFormatFunction() -> FunctionDecl {
152+
return FunctionDecl(
153+
leadingTrivia: .newline,
154+
modifiers: [Token.open, Token(tokenSyntax: TokenSyntax.contextualKeyword("override", trailingTrivia: .space))],
155+
identifier: .identifier("visit"),
156+
signature: FunctionSignature(
157+
input: ParameterClause(parameterList: [
158+
FunctionParameter(
159+
firstName: Token.wildcard,
160+
secondName: .identifier("node"),
161+
colon: .colon,
162+
type: "TokenSyntax"
163+
164+
)
165+
]),
166+
output: "Syntax"
167+
)
168+
) {
169+
VariableDecl("var node = node")
170+
SwitchStmt(expression: MemberAccessExpr(base: "node", name: "tokenKind")) {
171+
for token in SYNTAX_TOKENS {
172+
SwitchCase(label: SwitchCaseLabel(caseItems: CaseItem(pattern: ExpressionPattern(expression: MemberAccessExpr(name: token.swiftKind))))) {
173+
if token.requiresLeadingSpace {
174+
IfStmt(
175+
"""
176+
if node.leadingTrivia.isEmpty && lastRewrittenToken?.trailingTrivia.isEmpty != false {
177+
node.leadingTrivia += .space
178+
}
179+
"""
180+
)
181+
}
182+
if token.requiresTrailingSpace {
183+
IfStmt(
184+
"""
185+
if node.trailingTrivia.isEmpty {
186+
node.trailingTrivia += .space
187+
}
188+
"""
189+
)
190+
}
191+
if !token.requiresLeadingSpace && !token.requiresTrailingSpace {
192+
BreakStmt("break")
193+
}
194+
}
195+
}
196+
SwitchCase(label: SwitchCaseLabel(caseItems: CaseItem(pattern: ExpressionPattern(expression: MemberAccessExpr(name: "eof"))))) {
197+
BreakStmt("break")
198+
}
199+
}
200+
SequenceExpr("node.leadingTrivia = node.leadingTrivia.indented(indentation: indentation)")
201+
SequenceExpr("node.trailingTrivia = node.trailingTrivia.indented(indentation: indentation)")
202+
SequenceExpr("lastRewrittenToken = node")
203+
ReturnStmt("return Syntax(node)")
204+
}
205+
}

0 commit comments

Comments
 (0)