Skip to content

Commit 96cc428

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 49b3bbd commit 96cc428

File tree

23 files changed

+195
-141
lines changed

23 files changed

+195
-141
lines changed

CodeGeneration/Sources/Utils/CodeGenerationFormat.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ public class CodeGenerationFormat: BasicFormat {
108108
private func formatChildrenSeparatedByNewline<SyntaxType: SyntaxProtocol>(children: SyntaxChildren, elementType: SyntaxType.Type) -> [SyntaxType] {
109109
increaseIndentationLevel()
110110
var formattedChildren = children.map {
111-
self.visit($0).as(SyntaxType.self)!
111+
self.rewrite($0.cast(SyntaxType.self)).cast(SyntaxType.self)
112112
}
113113
formattedChildren = formattedChildren.map {
114114
if $0.leadingTrivia.first?.isNewline == true {

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

Lines changed: 65 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -30,33 +30,28 @@ 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)
49+
return withExtendedLifetime(rewritten) {
50+
return Syntax(node.data.replacingSelf(rewritten.raw, rawNodeArena: rewritten.raw.arena, allocationArena: SyntaxArena()))
51+
}
52+
}
53+
"""
54+
)
6055

6156
DeclSyntax(
6257
"""
@@ -105,6 +100,7 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
105100
/// Visit any Syntax node.
106101
/// - Parameter node: the node that is being visited
107102
/// - Returns: the rewritten node
103+
@available(*, deprecated, renamed: "rewrite(_:)")
108104
public func visit(_ node: Syntax) -> Syntax {
109105
return visit(node.data)
110106
}
@@ -114,11 +110,37 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
114110
DeclSyntax(
115111
"""
116112
public func visit<T: SyntaxChildChoices>(_ node: T) -> T {
117-
return visit(Syntax(node)).cast(T.self)
113+
return visit(node.data).cast(T.self)
118114
}
119115
"""
120116
)
121117

118+
for node in SYNTAX_NODES where !node.kind.isBase {
119+
if (node.base == .syntax || node.base == .syntaxCollection) && node.kind != .missing {
120+
DeclSyntax(
121+
"""
122+
/// Visit a ``\(node.kind.syntaxType)``.
123+
/// - Parameter node: the node that is being visited
124+
/// - Returns: the rewritten node
125+
open func visit(_ node: \(node.kind.syntaxType)) -> \(node.kind.syntaxType) {
126+
return Syntax(visitChildren(node)).cast(\(node.kind.syntaxType).self)
127+
}
128+
"""
129+
)
130+
} else {
131+
DeclSyntax(
132+
"""
133+
/// Visit a ``\(node.kind.syntaxType)``.
134+
/// - Parameter node: the node that is being visited
135+
/// - Returns: the rewritten node
136+
open func visit(_ node: \(node.kind.syntaxType)) -> \(raw: node.baseType.syntaxBaseName) {
137+
return \(raw: node.baseType.syntaxBaseName)(visitChildren(node))
138+
}
139+
"""
140+
)
141+
}
142+
}
143+
122144
for baseKind in SyntaxNodeKind.allCases where baseKind.isBase && baseKind != .syntax && baseKind != .syntaxCollection {
123145
DeclSyntax(
124146
"""
@@ -258,42 +280,44 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
258280
// initialize the new layout. Once we know that we have to rewrite the
259281
// layout, we need to collect all futher children, regardless of whether
260282
// they are rewritten or not.
261-
283+
262284
// newLayout is nil until the first child node is rewritten and rewritten
263285
// nodes are being collected.
264286
var newLayout: ContiguousArray<RawSyntax?>?
265-
287+
266288
// Rewritten children just to keep their 'SyntaxArena' alive until they are
267289
// wrapped with 'Syntax'
268290
var rewrittens: ContiguousArray<Syntax> = []
269-
291+
270292
let syntaxNode = node._syntaxNode
271-
293+
272294
// Incrementing i manually is faster than using .enumerated()
273295
var childIndex = 0
274296
for (raw, info) in RawSyntaxChildren(syntaxNode) {
275297
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.
298+
299+
guard let child = raw, viewMode.shouldTraverse(node: child) else {
300+
// Node does not exist or should not be visited. If we are collecting
301+
// rewritten nodes, we need to collect this one as well, otherwise we
302+
// can ignore it.
279303
if newLayout != nil {
280-
newLayout!.append(nil)
304+
newLayout!.append(raw)
281305
}
282306
continue
283307
}
284-
308+
285309
// Build the Syntax node to rewrite
286310
let absoluteRaw = AbsoluteRawSyntax(raw: child, info: info)
287311
let data = SyntaxData(absoluteRaw, parent: syntaxNode)
288-
312+
289313
let rewritten = visit(data)
290314
if rewritten.data.nodeId != info.nodeId {
291315
// The node was rewritten, let's handle it
292316
if newLayout == nil {
293317
// We have not yet collected any previous rewritten nodes. Initialize
294318
// the new layout with the previous nodes of the parent. This is
295319
// possible, since we know they were not rewritten.
296-
320+
297321
// The below implementation is based on Collection.map but directly
298322
// reserves enough capacity for the entire layout.
299323
newLayout = ContiguousArray<RawSyntax?>()
@@ -302,7 +326,7 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
302326
newLayout!.append(node.raw.layoutView!.children[j])
303327
}
304328
}
305-
329+
306330
// Now that we know we have a new layout in which we collect rewritten
307331
// nodes, add it.
308332
rewrittens.append(rewritten)
@@ -315,13 +339,13 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
315339
}
316340
}
317341
}
318-
342+
319343
if let newLayout {
320344
// A child node was rewritten. Build the updated node.
321-
345+
322346
// Sanity check, ensure the new children are the same length.
323347
precondition(newLayout.count == node.raw.layoutView!.children.count)
324-
348+
325349
let arena = SyntaxArena()
326350
let newRaw = node.raw.layoutView!.replacingLayout(with: Array(newLayout), arena: arena)
327351
// 'withExtendedLifetime' to keep 'SyntaxArena's of them alive until here.
@@ -335,18 +359,5 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
335359
}
336360
"""
337361
)
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-
return withExtendedLifetime(rewritten) {
346-
return Syntax(node.data.replacingSelf(rewritten.raw, rawNodeArena: rewritten.raw.arena, allocationArena: SyntaxArena()))
347-
}
348-
}
349-
"""
350-
)
351362
}
352363
}

EditorExtension/SwiftRefactorExtension/SourceEditorCommand.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ final class SourceEditorCommand: NSObject, XCSourceEditorCommand {
3636

3737
init(provider: any RefactoringProvider.Type) {
3838
self.provider = provider
39+
super.init(viewMode: .sourceAccurate)
3940
}
4041

4142
override func visitAny(_ node: Syntax) -> Syntax? {

Sources/SwiftBasicFormat/BasicFormat.swift

Lines changed: 15 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,12 @@ open class BasicFormat: SyntaxRewriter {
7076
indentationStack.removeLast()
7177
}
7278

79+
open override func visit(_ node: UnexpectedNodesSyntax) -> UnexpectedNodesSyntax {
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+
return node
83+
}
84+
7385
open override func visitPre(_ node: Syntax) {
7486
if requiresIndent(node) {
7587
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: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,7 @@ extension OperatorTable {
475475
) {
476476
self.opPrecedence = opPrecedence
477477
self.errorHandler = errorHandler
478+
super.init(viewMode: .fixedUp)
478479
}
479480

480481
override func visitAny(_ node: Syntax) -> Syntax? {
@@ -538,7 +539,7 @@ extension OperatorTable {
538539
opPrecedence: self,
539540
errorHandler: errorHandler
540541
)
541-
let result = folder.visit(Syntax(node))
542+
let result = folder.rewrite(node)
542543

543544
// If the sequence folder encountered an error that caused the error
544545
// 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: 9 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,7 +66,12 @@ class PresentMaker: SyntaxRewriter {
6266
}
6367
}
6468

69+
/// Transforms a syntax tree by making all present tokens missing.
6570
class MissingMaker: SyntaxRewriter {
71+
init() {
72+
super.init(viewMode: .sourceAccurate)
73+
}
74+
6675
override func visit(_ node: TokenSyntax) -> TokenSyntax {
6776
guard node.isPresent else {
6877
return node

Sources/SwiftRefactor/OpaqueParameterToGeneric.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ public struct OpaqueParameterToGeneric: SyntaxRefactoringProvider {
125125
in params: ParameterClauseSyntax,
126126
augmenting genericParams: GenericParameterClauseSyntax?
127127
) -> (ParameterClauseSyntax, GenericParameterClauseSyntax)? {
128-
let rewriter = SomeParameterRewriter()
128+
let rewriter = SomeParameterRewriter(viewMode: .sourceAccurate)
129129
let rewrittenParams = rewriter.visit(params.parameterList)
130130

131131
if rewriter.rewrittenSomeParameters.isEmpty {

0 commit comments

Comments
 (0)