Skip to content

Commit 1e75014

Browse files
committed
BasicFormat should not format unexpected nodes
`BasicFormat` depends on the parent layout nodes to determine formatting in some cases, so it doesn't make sense to visit unexpected nodes. Also fix up `SyntaxRewriter` to take a `viewMode`, since as it was `BasicFormat` was actually visiting `missing` nodes, which also doesn't make much sense. Resolves rdar://110463876.
1 parent 19b0310 commit 1e75014

File tree

13 files changed

+150
-133
lines changed

13 files changed

+150
-133
lines changed

CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/SyntaxRewriterFile.swift

Lines changed: 62 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -30,33 +30,27 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
3030
open class SyntaxRewriter
3131
"""
3232
) {
33-
DeclSyntax("public init() {}")
33+
DeclSyntax("public let viewMode: SyntaxTreeViewMode")
3434

35-
for node in SYNTAX_NODES where !node.kind.isBase {
36-
if (node.base == .syntax || node.base == .syntaxCollection) && node.kind != .missing {
37-
DeclSyntax(
38-
"""
39-
/// Visit a ``\(node.kind.syntaxType)``.
40-
/// - Parameter node: the node that is being visited
41-
/// - Returns: the rewritten node
42-
open func visit(_ node: \(node.kind.syntaxType)) -> \(node.kind.syntaxType) {
43-
return Syntax(visitChildren(node)).cast(\(node.kind.syntaxType).self)
44-
}
45-
"""
46-
)
47-
} else {
48-
DeclSyntax(
49-
"""
50-
/// Visit a ``\(node.kind.syntaxType)``.
51-
/// - Parameter node: the node that is being visited
52-
/// - Returns: the rewritten node
53-
open func visit(_ node: \(node.kind.syntaxType)) -> \(raw: node.baseType.syntaxBaseName) {
54-
return \(raw: node.baseType.syntaxBaseName)(visitChildren(node))
55-
}
56-
"""
57-
)
35+
DeclSyntax(
36+
"""
37+
public init(viewMode: SyntaxTreeViewMode = .sourceAccurate) {
38+
self.viewMode = viewMode
5839
}
59-
}
40+
"""
41+
)
42+
43+
DeclSyntax(
44+
"""
45+
/// Rewrite `node` and anchor, making sure that the rewritten node also has
46+
/// a parent if `node` had one.
47+
public func rewrite(_ node: some SyntaxProtocol) -> Syntax {
48+
let rewritten = self.visit(node.data)
49+
let arena = SyntaxArena()
50+
return Syntax(node.data.replacingSelf(rewritten.raw, arena: arena))
51+
}
52+
"""
53+
)
6054

6155
DeclSyntax(
6256
"""
@@ -102,22 +96,37 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
10296

10397
DeclSyntax(
10498
"""
105-
/// Visit any Syntax node.
106-
/// - Parameter node: the node that is being visited
107-
/// - Returns: the rewritten node
108-
public func visit(_ node: Syntax) -> Syntax {
109-
return visit(node.data)
99+
public func visit<T: SyntaxChildChoices>(_ node: T) -> T {
100+
return visit(node.data).cast(T.self)
110101
}
111102
"""
112103
)
113104

114-
DeclSyntax(
115-
"""
116-
public func visit<T: SyntaxChildChoices>(_ node: T) -> T {
117-
return visit(Syntax(node)).cast(T.self)
105+
for node in SYNTAX_NODES where !node.kind.isBase {
106+
if (node.base == .syntax || node.base == .syntaxCollection) && node.kind != .missing {
107+
DeclSyntax(
108+
"""
109+
/// Visit a ``\(node.kind.syntaxType)``.
110+
/// - Parameter node: the node that is being visited
111+
/// - Returns: the rewritten node
112+
open func visit(_ node: \(node.kind.syntaxType)) -> \(node.kind.syntaxType) {
113+
return Syntax(visitChildren(node)).cast(\(node.kind.syntaxType).self)
114+
}
115+
"""
116+
)
117+
} else {
118+
DeclSyntax(
119+
"""
120+
/// Visit a ``\(node.kind.syntaxType)``.
121+
/// - Parameter node: the node that is being visited
122+
/// - Returns: the rewritten node
123+
open func visit(_ node: \(node.kind.syntaxType)) -> \(raw: node.baseType.syntaxBaseName) {
124+
return \(raw: node.baseType.syntaxBaseName)(visitChildren(node))
125+
}
126+
"""
127+
)
118128
}
119-
"""
120-
)
129+
}
121130

122131
for baseKind in SyntaxNodeKind.allCases where baseKind.isBase && baseKind != .syntax && baseKind != .syntaxCollection {
123132
DeclSyntax(
@@ -258,42 +267,44 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
258267
// initialize the new layout. Once we know that we have to rewrite the
259268
// layout, we need to collect all futher children, regardless of whether
260269
// they are rewritten or not.
261-
270+
262271
// newLayout is nil until the first child node is rewritten and rewritten
263272
// nodes are being collected.
264273
var newLayout: ContiguousArray<RawSyntax?>?
265-
274+
266275
// Rewritten children just to keep their 'SyntaxArena' alive until they are
267276
// wrapped with 'Syntax'
268277
var rewrittens: ContiguousArray<Syntax> = []
269-
278+
270279
let syntaxNode = node._syntaxNode
271-
280+
272281
// Incrementing i manually is faster than using .enumerated()
273282
var childIndex = 0
274283
for (raw, info) in RawSyntaxChildren(syntaxNode) {
275284
defer { childIndex += 1 }
276-
guard let child = raw else {
277-
// Node does not exist. If we are collecting rewritten nodes, we need to
278-
// collect this one as well, otherwise we can ignore it.
285+
286+
guard let child = raw, viewMode.shouldTraverse(node: child) else {
287+
// Node does not exist or should not be visited. If we are collecting
288+
// rewritten nodes, we need to collect this one as well, otherwise we
289+
// can ignore it.
279290
if newLayout != nil {
280-
newLayout!.append(nil)
291+
newLayout!.append(raw)
281292
}
282293
continue
283294
}
284-
295+
285296
// Build the Syntax node to rewrite
286297
let absoluteRaw = AbsoluteRawSyntax(raw: child, info: info)
287298
let data = SyntaxData(absoluteRaw, parent: syntaxNode)
288-
299+
289300
let rewritten = visit(data)
290301
if rewritten.data.nodeId != info.nodeId {
291302
// The node was rewritten, let's handle it
292303
if newLayout == nil {
293304
// We have not yet collected any previous rewritten nodes. Initialize
294305
// the new layout with the previous nodes of the parent. This is
295306
// possible, since we know they were not rewritten.
296-
307+
297308
// The below implementation is based on Collection.map but directly
298309
// reserves enough capacity for the entire layout.
299310
newLayout = ContiguousArray<RawSyntax?>()
@@ -302,7 +313,7 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
302313
newLayout!.append(node.raw.layoutView!.children[j])
303314
}
304315
}
305-
316+
306317
// Now that we know we have a new layout in which we collect rewritten
307318
// nodes, add it.
308319
rewrittens.append(rewritten)
@@ -315,13 +326,13 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
315326
}
316327
}
317328
}
318-
329+
319330
if let newLayout {
320331
// A child node was rewritten. Build the updated node.
321-
332+
322333
// Sanity check, ensure the new children are the same length.
323334
precondition(newLayout.count == node.raw.layoutView!.children.count)
324-
335+
325336
let arena = SyntaxArena()
326337
let newRaw = node.raw.layoutView!.replacingLayout(with: Array(newLayout), arena: arena)
327338
// 'withExtendedLifetime' to keep 'SyntaxArena's of them alive until here.
@@ -335,17 +346,5 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
335346
}
336347
"""
337348
)
338-
339-
DeclSyntax(
340-
"""
341-
/// Rewrite `node` and anchor, making sure that the rewritten node also has
342-
/// a parent if `node` had one.
343-
public func rewrite(_ node: Syntax) -> Syntax {
344-
let rewritten = self.visit(node)
345-
let arena = SyntaxArena()
346-
return Syntax(node.data.replacingSelf(rewritten.raw, arena: arena))
347-
}
348-
"""
349-
)
350349
}
351350
}

Sources/SwiftBasicFormat/BasicFormat.swift

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@
1212

1313
import SwiftSyntax
1414

15+
/// A rewriter that performs a "basic" format of the passed tree.
16+
///
17+
/// The base implementation is primarily aimed at adding whitespace where
18+
/// required such that re-parsing the tree's description results in the same
19+
/// tree. But it also makes an attempt at adding in formatting, eg. splitting
20+
/// lines where obvious and some basic indentation at nesting levels.
21+
///
22+
/// Any subclasses *must* return the same node type as given.
1523
open class BasicFormat: SyntaxRewriter {
1624
/// How much indentation should be added at a new indentation level.
1725
public let indentationWidth: Trivia
@@ -35,8 +43,6 @@ open class BasicFormat: SyntaxRewriter {
3543
/// This is used as a reference-point to indent user-indented code.
3644
private var anchorPoints: [TokenSyntax: Trivia] = [:]
3745

38-
public let viewMode: SyntaxTreeViewMode
39-
4046
/// The previously visited token. This is faster than accessing
4147
/// `token.previousToken` inside `visit(_:TokenSyntax)`. `nil` if no token has
4248
/// been visited yet.
@@ -49,7 +55,7 @@ open class BasicFormat: SyntaxRewriter {
4955
) {
5056
self.indentationWidth = indentationWidth
5157
self.indentationStack = [(indentation: initialIndentation, isUserDefined: false)]
52-
self.viewMode = viewMode
58+
super.init(viewMode: viewMode)
5359
}
5460

5561
// MARK: - Updating indentation level
@@ -70,6 +76,15 @@ open class BasicFormat: SyntaxRewriter {
7076
indentationStack.removeLast()
7177
}
7278

79+
open override func visitAny(_ node: Syntax) -> Syntax? {
80+
// Do not perform any formatting on unexpected nodes, the result won't make any
81+
// sense as we rely on layout nodes to know what formatting to perform.
82+
if node.kind == .unexpectedNodes {
83+
return node
84+
}
85+
return nil
86+
}
87+
7388
open override func visitPre(_ node: Syntax) {
7489
if requiresIndent(node) {
7590
if let firstToken = node.firstToken(viewMode: viewMode),

Sources/SwiftBasicFormat/SyntaxProtocol+Formatted.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ import SwiftSyntax
33
public extension SyntaxProtocol {
44
/// Build a syntax node from this `Buildable` and format it with the given format.
55
func formatted(using format: BasicFormat = BasicFormat()) -> Syntax {
6-
return format.visit(Syntax(self))
6+
return format.rewrite(self)
77
}
88
}

Sources/SwiftOperators/OperatorTable+Folding.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,7 @@ extension OperatorTable {
538538
opPrecedence: self,
539539
errorHandler: errorHandler
540540
)
541-
let result = folder.visit(Syntax(node))
541+
let result = folder.rewrite(node)
542542

543543
// If the sequence folder encountered an error that caused the error
544544
// handler to throw, invoke the error handler again with the original

Sources/SwiftParserDiagnostics/DiagnosticExtensions.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ extension FixIt.MultiNodeChange {
7878
guard let node = node else {
7979
return FixIt.MultiNodeChange(primitiveChanges: [])
8080
}
81-
var changes = [FixIt.Change.replace(oldNode: Syntax(node), newNode: MissingMaker().visit(Syntax(node)))]
81+
var changes = [FixIt.Change.replace(oldNode: Syntax(node), newNode: MissingMaker().rewrite(node))]
8282
if transferTrivia {
8383
changes += FixIt.MultiNodeChange.transferTriviaAtSides(from: [node]).primitiveChanges
8484
}
@@ -123,7 +123,7 @@ extension FixIt.MultiNodeChange {
123123
leadingTrivia: Trivia? = nil,
124124
trailingTrivia: Trivia? = nil
125125
) -> Self {
126-
var presentNode = MissingNodesBasicFormatter(viewMode: .fixedUp).visit(Syntax(node))
126+
var presentNode = MissingNodesBasicFormatter(viewMode: .fixedUp).rewrite(node)
127127
presentNode = PresentMaker().rewrite(presentNode)
128128

129129
if let leadingTrivia {

Sources/SwiftParserDiagnostics/PresenceUtils.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ extension SyntaxProtocol {
4545

4646
/// Transforms a syntax tree by making all missing tokens present.
4747
class PresentMaker: SyntaxRewriter {
48+
init() {
49+
super.init(viewMode: .fixedUp)
50+
}
51+
4852
override func visit(_ token: TokenSyntax) -> TokenSyntax {
4953
if token.isMissing {
5054
let presentToken: TokenSyntax
@@ -62,6 +66,7 @@ class PresentMaker: SyntaxRewriter {
6266
}
6367
}
6468

69+
/// Transforms a syntax tree by making all present tokens missing.
6570
class MissingMaker: SyntaxRewriter {
6671
override func visit(_ node: TokenSyntax) -> TokenSyntax {
6772
guard node.isPresent else {

0 commit comments

Comments
 (0)