Skip to content

Commit bae6ff8

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 ab682d8 commit bae6ff8

File tree

23 files changed

+192
-139
lines changed

23 files changed

+192
-139
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: 64 additions & 53 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
"""
@@ -105,6 +99,7 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
10599
/// Visit any Syntax node.
106100
/// - Parameter node: the node that is being visited
107101
/// - Returns: the rewritten node
102+
@available(*, deprecated, renamed: "rewrite(_:)")
108103
public func visit(_ node: Syntax) -> Syntax {
109104
return visit(node.data)
110105
}
@@ -114,11 +109,37 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
114109
DeclSyntax(
115110
"""
116111
public func visit<T: SyntaxChildChoices>(_ node: T) -> T {
117-
return visit(Syntax(node)).cast(T.self)
112+
return visit(node.data).cast(T.self)
118113
}
119114
"""
120115
)
121116

117+
for node in SYNTAX_NODES where !node.kind.isBase {
118+
if (node.base == .syntax || node.base == .syntaxCollection) && node.kind != .missing {
119+
DeclSyntax(
120+
"""
121+
/// Visit a ``\(node.kind.syntaxType)``.
122+
/// - Parameter node: the node that is being visited
123+
/// - Returns: the rewritten node
124+
open func visit(_ node: \(node.kind.syntaxType)) -> \(node.kind.syntaxType) {
125+
return Syntax(visitChildren(node)).cast(\(node.kind.syntaxType).self)
126+
}
127+
"""
128+
)
129+
} else {
130+
DeclSyntax(
131+
"""
132+
/// Visit a ``\(node.kind.syntaxType)``.
133+
/// - Parameter node: the node that is being visited
134+
/// - Returns: the rewritten node
135+
open func visit(_ node: \(node.kind.syntaxType)) -> \(raw: node.baseType.syntaxBaseName) {
136+
return \(raw: node.baseType.syntaxBaseName)(visitChildren(node))
137+
}
138+
"""
139+
)
140+
}
141+
}
142+
122143
for baseKind in SyntaxNodeKind.allCases where baseKind.isBase && baseKind != .syntax && baseKind != .syntaxCollection {
123144
DeclSyntax(
124145
"""
@@ -258,42 +279,44 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
258279
// initialize the new layout. Once we know that we have to rewrite the
259280
// layout, we need to collect all futher children, regardless of whether
260281
// they are rewritten or not.
261-
282+
262283
// newLayout is nil until the first child node is rewritten and rewritten
263284
// nodes are being collected.
264285
var newLayout: ContiguousArray<RawSyntax?>?
265-
286+
266287
// Rewritten children just to keep their 'SyntaxArena' alive until they are
267288
// wrapped with 'Syntax'
268289
var rewrittens: ContiguousArray<Syntax> = []
269-
290+
270291
let syntaxNode = node._syntaxNode
271-
292+
272293
// Incrementing i manually is faster than using .enumerated()
273294
var childIndex = 0
274295
for (raw, info) in RawSyntaxChildren(syntaxNode) {
275296
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.
297+
298+
guard let child = raw, viewMode.shouldTraverse(node: child) else {
299+
// Node does not exist or should not be visited. If we are collecting
300+
// rewritten nodes, we need to collect this one as well, otherwise we
301+
// can ignore it.
279302
if newLayout != nil {
280-
newLayout!.append(nil)
303+
newLayout!.append(raw)
281304
}
282305
continue
283306
}
284-
307+
285308
// Build the Syntax node to rewrite
286309
let absoluteRaw = AbsoluteRawSyntax(raw: child, info: info)
287310
let data = SyntaxData(absoluteRaw, parent: syntaxNode)
288-
311+
289312
let rewritten = visit(data)
290313
if rewritten.data.nodeId != info.nodeId {
291314
// The node was rewritten, let's handle it
292315
if newLayout == nil {
293316
// We have not yet collected any previous rewritten nodes. Initialize
294317
// the new layout with the previous nodes of the parent. This is
295318
// possible, since we know they were not rewritten.
296-
319+
297320
// The below implementation is based on Collection.map but directly
298321
// reserves enough capacity for the entire layout.
299322
newLayout = ContiguousArray<RawSyntax?>()
@@ -302,7 +325,7 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
302325
newLayout!.append(node.raw.layoutView!.children[j])
303326
}
304327
}
305-
328+
306329
// Now that we know we have a new layout in which we collect rewritten
307330
// nodes, add it.
308331
rewrittens.append(rewritten)
@@ -315,13 +338,13 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
315338
}
316339
}
317340
}
318-
341+
319342
if let newLayout {
320343
// A child node was rewritten. Build the updated node.
321-
344+
322345
// Sanity check, ensure the new children are the same length.
323346
precondition(newLayout.count == node.raw.layoutView!.children.count)
324-
347+
325348
let arena = SyntaxArena()
326349
let newRaw = node.raw.layoutView!.replacingLayout(with: Array(newLayout), arena: arena)
327350
// 'withExtendedLifetime' to keep 'SyntaxArena's of them alive until here.
@@ -335,17 +358,5 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
335358
}
336359
"""
337360
)
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-
)
350361
}
351362
}

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: .sourceAccurate)
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)