Skip to content

Commit caddfd1

Browse files
committed
When handling a missing layout node, only make the placeholder present
This fixes a couple of formatting issues and resolves the question raised in #1727 (comment).
1 parent 22b6620 commit caddfd1

File tree

8 files changed

+98
-5
lines changed

8 files changed

+98
-5
lines changed

CodeGeneration/Sources/SyntaxSupport/CommonNodes.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ public let COMMON_NODES: [Node] = [
160160
nameForDiagnostics: "declaration",
161161
documentation: "In case the source code is missing a declaration, this node stands in place of the missing declaration.",
162162
traits: [
163+
"MissingNode",
163164
"WithAttributes",
164165
"WithModifiers",
165166
],
@@ -192,6 +193,9 @@ public let COMMON_NODES: [Node] = [
192193
base: .expr,
193194
nameForDiagnostics: "expression",
194195
documentation: "In case the source code is missing an expression, this node stands in place of the missing expression.",
196+
traits: [
197+
"MissingNode"
198+
],
195199
children: [
196200
Child(
197201
name: "Placeholder",
@@ -209,6 +213,9 @@ public let COMMON_NODES: [Node] = [
209213
base: .pattern,
210214
nameForDiagnostics: "pattern",
211215
documentation: "In case the source code is missing a pattern, this node stands in place of the missing pattern.",
216+
traits: [
217+
"MissingNode"
218+
],
212219
children: [
213220
Child(
214221
name: "Placeholder",
@@ -226,6 +233,9 @@ public let COMMON_NODES: [Node] = [
226233
base: .stmt,
227234
nameForDiagnostics: "statement",
228235
documentation: "In case the source code is missing a statement, this node stands in place of the missing statement.",
236+
traits: [
237+
"MissingNode"
238+
],
229239
children: [
230240
Child(
231241
name: "Placeholder",
@@ -243,6 +253,9 @@ public let COMMON_NODES: [Node] = [
243253
base: .syntax,
244254
nameForDiagnostics: nil,
245255
documentation: "In case the source code is missing a syntax node, this node stands in place of the missing node.",
256+
traits: [
257+
"MissingNode"
258+
],
246259
children: [
247260
Child(
248261
name: "Placeholder",
@@ -260,6 +273,9 @@ public let COMMON_NODES: [Node] = [
260273
base: .type,
261274
nameForDiagnostics: "type",
262275
documentation: "In case the source code is missing a type, this node stands in place of the missing type.",
276+
traits: [
277+
"MissingNode"
278+
],
263279
children: [
264280
Child(
265281
name: "Placeholder",

CodeGeneration/Sources/SyntaxSupport/Traits.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,21 @@ public let TRAITS: [Trait] = [
110110
Child(name: "TrailingComma", kind: .token(choices: [.token(tokenKind: "CommaToken")]), isOptional: true)
111111
]
112112
),
113+
Trait(
114+
traitName: "MissingNode",
115+
documentation: """
116+
Represents a layout node that is missing in the source file.
117+
118+
See the types conforming to this protocol for examples of where missing nodes can occur.
119+
""",
120+
children: [
121+
Child(
122+
name: "Placeholder",
123+
kind: .token(choices: [.token(tokenKind: "IdentifierToken")]),
124+
documentation: """
125+
A placeholder, i.e. `<#placeholder#>`, that can be inserted into the source code to represent the missing node.
126+
"""
127+
)
128+
]
129+
),
113130
]

CodeGeneration/Tests/ValidateSyntaxNodes/ValidateSyntaxNodes.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,7 @@ class ValidateSyntaxNodes: XCTestCase {
540540
ValidationFailure(node: .availabilityCondition, message: "could conform to trait 'Parenthesized' but does not"),
541541
ValidationFailure(node: .canImportExpr, message: "could conform to trait 'Parenthesized' but does not"),
542542
ValidationFailure(node: .differentiabilityParams, message: "could conform to trait 'Parenthesized' but does not"),
543+
ValidationFailure(node: .editorPlaceholderDecl, message: "could conform to trait 'MissingNode' but does not"),
543544
ValidationFailure(node: .editorPlaceholderExpr, message: "could conform to trait 'IdentifiedDecl' but does not"),
544545
ValidationFailure(node: .enumCaseElement, message: "could conform to trait 'IdentifiedDecl' but does not"),
545546
ValidationFailure(node: .initializesEffect, message: "could conform to trait 'Parenthesized' but does not"),

Sources/SwiftParserDiagnostics/MissingNodesError.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,15 @@ extension ParseDiagnosticsGenerator {
388388
}
389389
}
390390

391-
let changes = missingNodes.map { FixIt.MultiNodeChange.makePresent($0) }
391+
let changes = missingNodes.map { node in
392+
if let missing = node.asProtocol(MissingNodeSyntax.self) {
393+
// For missing nodes, only make the placeholder present. Don’t make any
394+
// missing nodes, e.g. in a malformed attribute, present.
395+
return FixIt.MultiNodeChange.makePresent(missing.placeholder)
396+
} else {
397+
return FixIt.MultiNodeChange.makePresent(node)
398+
}
399+
}
392400
let fixIt = FixIt(
393401
message: InsertTokenFixIt(missingNodes: missingNodes),
394402
changes: additionalChanges + changes

Sources/SwiftSyntax/Documentation.docc/generated/SwiftSyntax.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,7 @@ These articles are intended for developers wishing to contribute to SwiftSyntax
383383
- <doc:SwiftSyntax/WithModifiersSyntax>
384384
- <doc:SwiftSyntax/WithStatementsSyntax>
385385
- <doc:SwiftSyntax/WithTrailingCommaSyntax>
386+
- <doc:SwiftSyntax/MissingNodeSyntax>
386387

387388

388389

Sources/SwiftSyntax/generated/SyntaxTraits.swift

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,46 @@ public extension SyntaxProtocol {
504504
}
505505
}
506506

507+
// MARK: - MissingNodeSyntax
508+
509+
/// Represents a layout node that is missing in the source file.
510+
///
511+
/// See the types conforming to this protocol for examples of where missing nodes can occur.
512+
public protocol MissingNodeSyntax: SyntaxProtocol {
513+
/// A placeholder, i.e. `<#placeholder#>`, that can be inserted into the source code to represent the missing node.
514+
var placeholder: TokenSyntax {
515+
get
516+
set
517+
}
518+
}
519+
520+
public extension MissingNodeSyntax {
521+
/// Without this function, the `with` function defined on `SyntaxProtocol`
522+
/// does not work on existentials of this protocol type.
523+
@_disfavoredOverload
524+
func with<T>(_ keyPath: WritableKeyPath<MissingNodeSyntax, T>, _ newChild: T) -> MissingNodeSyntax {
525+
var copy: MissingNodeSyntax = self
526+
copy[keyPath: keyPath] = newChild
527+
return copy
528+
}
529+
}
530+
531+
public extension SyntaxProtocol {
532+
/// Check whether the non-type erased version of this syntax node conforms to
533+
/// `MissingNodeSyntax`.
534+
/// Note that this will incur an existential conversion.
535+
func isProtocol(_: MissingNodeSyntax.Protocol) -> Bool {
536+
return self.asProtocol(MissingNodeSyntax.self) != nil
537+
}
538+
539+
/// Return the non-type erased version of this syntax node if it conforms to
540+
/// `MissingNodeSyntax`. Otherwise return `nil`.
541+
/// Note that this will incur an existential conversion.
542+
func asProtocol(_: MissingNodeSyntax.Protocol) -> MissingNodeSyntax? {
543+
return Syntax(self).asProtocol(SyntaxProtocol.self) as? MissingNodeSyntax
544+
}
545+
}
546+
507547
extension AccessorBlockSyntax: BracedSyntax {}
508548

509549
extension AccessorDeclSyntax: WithAttributesSyntax {}
@@ -616,7 +656,17 @@ extension MacroExpansionExprSyntax: FreestandingMacroExpansionSyntax {}
616656

617657
extension MemberDeclBlockSyntax: BracedSyntax {}
618658

619-
extension MissingDeclSyntax: WithAttributesSyntax, WithModifiersSyntax {}
659+
extension MissingDeclSyntax: MissingNodeSyntax, WithAttributesSyntax, WithModifiersSyntax {}
660+
661+
extension MissingExprSyntax: MissingNodeSyntax {}
662+
663+
extension MissingPatternSyntax: MissingNodeSyntax {}
664+
665+
extension MissingStmtSyntax: MissingNodeSyntax {}
666+
667+
extension MissingSyntax: MissingNodeSyntax {}
668+
669+
extension MissingTypeSyntax: MissingNodeSyntax {}
620670

621671
extension OperatorDeclSyntax: IdentifiedDeclSyntax {}
622672

Tests/SwiftParserTest/AttributeTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ final class AttributeTests: XCTestCase {
107107
DiagnosticSpec(locationMarker: "3️⃣", message: "expected declaration after attribute", fixIts: ["insert declaration"]),
108108
],
109109
fixedSource: """
110-
@_specialize(e:, exported: false) <#declaration#>
110+
@_specialize(e:, exported: false) <#declaration#>
111111
"""
112112
)
113113
}
@@ -343,7 +343,7 @@ final class AttributeTests: XCTestCase {
343343
"@resultBuilder1️⃣",
344344
diagnostics: [DiagnosticSpec(message: "expected declaration after attribute", fixIts: ["insert declaration"])],
345345
fixedSource: """
346-
@resultBuilder <#declaration#>
346+
@resultBuilder <#declaration#>
347347
"""
348348
)
349349
}

Tests/SwiftParserTest/DeclarationTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,7 @@ final class DeclarationTests: XCTestCase {
730730
],
731731
fixedSource: """
732732
struct a {
733-
public <#declaration#>
733+
public <#declaration#>
734734
}
735735
"""
736736
)

0 commit comments

Comments
 (0)