Skip to content

Commit 6864c93

Browse files
authored
Merge pull request #1894 from ahoppen/ahoppen/missing-nodes-fixit
When handling a missing layout node, only make the placeholder present
2 parents 0911975 + caddfd1 commit 6864c93

File tree

17 files changed

+154
-40
lines changed

17 files changed

+154
-40
lines changed

CodeGeneration/Sources/SyntaxSupport/CommonNodes.swift

Lines changed: 22 additions & 6 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
],
@@ -180,7 +181,7 @@ public let COMMON_NODES: [Node] = [
180181
name: "Placeholder",
181182
kind: .token(choices: [.token(tokenKind: "IdentifierToken")], requiresLeadingSpace: false, requiresTrailingSpace: false),
182183
documentation: """
183-
A placeholder, i.e. `<#decl#>` that can be inserted into the source code to represent the missing declaration.
184+
A placeholder, i.e. `<#decl#>`, that can be inserted into the source code to represent the missing declaration.
184185
This token should always have `presence = .missing`.
185186
"""
186187
),
@@ -192,12 +193,15 @@ 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",
198202
kind: .token(choices: [.token(tokenKind: "IdentifierToken")], requiresLeadingSpace: false, requiresTrailingSpace: false),
199203
documentation: """
200-
A placeholder, i.e. `<#expression#>` that can be inserted into the source code to represent the missing expression.
204+
A placeholder, i.e. `<#expression#>`, that can be inserted into the source code to represent the missing expression.
201205
This token should always have `presence = .missing`.
202206
"""
203207
)
@@ -209,12 +213,15 @@ 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",
215222
kind: .token(choices: [.token(tokenKind: "IdentifierToken")], requiresLeadingSpace: false, requiresTrailingSpace: false),
216223
documentation: """
217-
A placeholder, i.e. `<#pattern#>` that can be inserted into the source code to represent the missing pattern.
224+
A placeholder, i.e. `<#pattern#>`, that can be inserted into the source code to represent the missing pattern.
218225
This token should always have `presence = .missing`.
219226
"""
220227
)
@@ -226,12 +233,15 @@ 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",
232242
kind: .token(choices: [.token(tokenKind: "IdentifierToken")], requiresLeadingSpace: false, requiresTrailingSpace: false),
233243
documentation: """
234-
A placeholder, i.e. `<#statement#>` that can be inserted into the source code to represent the missing pattern.
244+
A placeholder, i.e. `<#statement#>`, that can be inserted into the source code to represent the missing pattern.
235245
This token should always have `presence = .missing`.
236246
"""
237247
)
@@ -243,12 +253,15 @@ 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",
249262
kind: .token(choices: [.token(tokenKind: "IdentifierToken")], requiresLeadingSpace: false, requiresTrailingSpace: false),
250263
documentation: """
251-
A placeholder, i.e. `<#syntax#>` that can be inserted into the source code to represent the missing pattern.
264+
A placeholder, i.e. `<#syntax#>`, that can be inserted into the source code to represent the missing pattern.
252265
This token should always have `presence = .missing`
253266
"""
254267
)
@@ -260,12 +273,15 @@ 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",
266282
kind: .token(choices: [.token(tokenKind: "IdentifierToken")], requiresLeadingSpace: false, requiresTrailingSpace: false),
267283
documentation: """
268-
A placeholder, i.e. `<#type#>` that can be inserted into the source code to represent the missing type. This token should always have `presence = .missing`.
284+
A placeholder, i.e. `<#type#>`, that can be inserted into the source code to represent the missing type. This token should always have `presence = .missing`.
269285
"""
270286
)
271287
]

CodeGeneration/Sources/SyntaxSupport/Traits.swift

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,19 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
import SwiftSyntax
14+
1315
public class Trait {
1416
public let traitName: String
17+
public let protocolName: TokenSyntax
18+
public let documentation: SwiftSyntax.Trivia
1519
public let children: [Child]
16-
public let description: String?
1720

18-
init(traitName: String, children: [Child], description: String? = nil) {
21+
init(traitName: String, documentation: String? = nil, children: [Child]) {
1922
self.traitName = traitName
23+
self.protocolName = .identifier("\(traitName)Syntax")
24+
self.documentation = docCommentTrivia(from: documentation)
2025
self.children = children
21-
self.description = description
2226
}
2327
}
2428

@@ -106,4 +110,21 @@ public let TRAITS: [Trait] = [
106110
Child(name: "TrailingComma", kind: .token(choices: [.token(tokenKind: "CommaToken")]), isOptional: true)
107111
]
108112
),
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+
),
109130
]

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ let nodesSections: String = {
6161

6262
addSection(heading: "Miscellaneous Syntax", types: SYNTAX_NODES.map(\.kind.syntaxType.description).filter({ !handledSyntaxTypes.contains($0) }))
6363

64-
addSection(heading: "Traits", types: TRAITS.map { "\($0.traitName)Syntax" })
64+
addSection(heading: "Traits", types: TRAITS.map { "\($0.protocolName)" })
6565

6666
return result
6767
}()

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

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,30 @@ let syntaxTraitsFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
1919
for trait in TRAITS {
2020
try! ProtocolDeclSyntax(
2121
"""
22-
// MARK: - \(raw: trait.traitName)Syntax
22+
// MARK: - \(trait.protocolName)
2323
24-
public protocol \(raw: trait.traitName)Syntax: SyntaxProtocol
24+
\(raw: trait.documentation)
25+
public protocol \(trait.protocolName): SyntaxProtocol
2526
"""
2627
) {
2728
for child in trait.children {
28-
DeclSyntax("var \(raw: child.varName): \(child.syntaxNodeKind.syntaxType)\(raw: child.isOptional ? "?" : "") { get set }")
29+
DeclSyntax(
30+
"""
31+
\(raw: child.docComment)
32+
var \(raw: child.varName): \(child.syntaxNodeKind.syntaxType)\(raw: child.isOptional ? "?" : "") { get set }
33+
"""
34+
)
2935
}
3036
}
3137

32-
try! ExtensionDeclSyntax("public extension \(raw: trait.traitName)Syntax") {
38+
try! ExtensionDeclSyntax("public extension \(trait.protocolName)") {
3339
DeclSyntax(
3440
"""
3541
/// Without this function, the `with` function defined on `SyntaxProtocol`
3642
/// does not work on existentials of this protocol type.
3743
@_disfavoredOverload
38-
func with<T>(_ keyPath: WritableKeyPath<\(raw: trait.traitName)Syntax, T>, _ newChild: T) -> \(raw: trait.traitName)Syntax {
39-
var copy: \(raw: trait.traitName)Syntax = self
44+
func with<T>(_ keyPath: WritableKeyPath<\(trait.protocolName), T>, _ newChild: T) -> \(trait.protocolName) {
45+
var copy: \(trait.protocolName) = self
4046
copy[keyPath: keyPath] = newChild
4147
return copy
4248
}
@@ -48,21 +54,21 @@ let syntaxTraitsFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
4854
DeclSyntax(
4955
"""
5056
/// Check whether the non-type erased version of this syntax node conforms to
51-
/// `\(raw: trait.traitName)Syntax`.
57+
/// `\(trait.protocolName)`.
5258
/// Note that this will incur an existential conversion.
53-
func isProtocol(_: \(raw: trait.traitName)Syntax.Protocol) -> Bool {
54-
return self.asProtocol(\(raw: trait.traitName)Syntax.self) != nil
59+
func isProtocol(_: \(trait.protocolName).Protocol) -> Bool {
60+
return self.asProtocol(\(trait.protocolName).self) != nil
5561
}
5662
"""
5763
)
5864

5965
DeclSyntax(
6066
"""
6167
/// Return the non-type erased version of this syntax node if it conforms to
62-
/// `\(raw: trait.traitName)Syntax`. Otherwise return `nil`.
68+
/// `\(trait.protocolName)`. Otherwise return `nil`.
6369
/// Note that this will incur an existential conversion.
64-
func asProtocol(_: \(raw: trait.traitName)Syntax.Protocol) -> \(raw: trait.traitName)Syntax? {
65-
return Syntax(self).asProtocol(SyntaxProtocol.self) as? \(raw: trait.traitName)Syntax
70+
func asProtocol(_: \(trait.protocolName).Protocol) -> \(trait.protocolName)? {
71+
return Syntax(self).asProtocol(SyntaxProtocol.self) as? \(trait.protocolName)
6672
}
6773
"""
6874
)

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/SwiftDiagnostics/Diagnostic.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public struct Diagnostic: CustomDebugStringConvertible {
7070
public var debugDescription: String {
7171
let locationConverter = SourceLocationConverter(file: "", tree: node.root)
7272
let location = location(converter: locationConverter)
73-
return "\(location): \(message)"
73+
return "\(location.line):\(location.column): \(message)"
7474
}
7575
}
7676

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

0 commit comments

Comments
 (0)