Skip to content

Commit 2c847f8

Browse files
authored
Merge pull request #2186 from kimdv/kimdv/add-token-syntax-editor-placeholder
Add token syntax editor placeholder
2 parents ad30a7b + 19d6393 commit 2c847f8

File tree

8 files changed

+310
-2
lines changed

8 files changed

+310
-2
lines changed

Sources/SwiftParser/Declarations.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1040,16 +1040,25 @@ extension Parser {
10401040
) -> RawFunctionDeclSyntax {
10411041
let (unexpectedBeforeFuncKeyword, funcKeyword) = self.eat(handle)
10421042
let unexpectedBeforeIdentifier: RawUnexpectedNodesSyntax?
1043+
let unexpectedAfterIdentifier: RawUnexpectedNodesSyntax?
10431044
let identifier: RawTokenSyntax
10441045
if self.at(anyIn: Operator.self) != nil || self.at(.exclamationMark, .prefixAmpersand) {
10451046
var name = self.currentToken.tokenText
1046-
if name.count > 1 && name.hasSuffix("<") && self.peek(isAt: .identifier) {
1047+
if !currentToken.isEditorPlaceholder && name.count > 1 && name.hasSuffix("<") && self.peek(isAt: .identifier) {
10471048
name = SyntaxText(rebasing: name.dropLast())
10481049
}
10491050
unexpectedBeforeIdentifier = nil
10501051
identifier = self.consumePrefix(name, as: .binaryOperator)
1052+
unexpectedAfterIdentifier = nil
10511053
} else {
10521054
(unexpectedBeforeIdentifier, identifier) = self.expectIdentifier(keywordRecovery: true)
1055+
1056+
if currentToken.isEditorPlaceholder {
1057+
let editorPlaceholder = self.parseAnyIdentifier()
1058+
unexpectedAfterIdentifier = RawUnexpectedNodesSyntax([editorPlaceholder], arena: self.arena)
1059+
} else {
1060+
unexpectedAfterIdentifier = nil
1061+
}
10531062
}
10541063

10551064
let genericParams: RawGenericParameterClauseSyntax?
@@ -1076,6 +1085,7 @@ extension Parser {
10761085
funcKeyword: funcKeyword,
10771086
unexpectedBeforeIdentifier,
10781087
name: identifier,
1088+
unexpectedAfterIdentifier,
10791089
genericParameterClause: genericParams,
10801090
signature: signature,
10811091
genericWhereClause: generics,

Sources/SwiftParser/Types.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -936,7 +936,9 @@ extension Parser {
936936

937937
extension Parser {
938938
mutating func parseResultType() -> RawTypeSyntax {
939-
if self.at(prefix: "<") {
939+
if self.currentToken.isEditorPlaceholder {
940+
return self.parseTypeIdentifier()
941+
} else if self.at(prefix: "<") {
940942
let generics = self.parseGenericParameters()
941943
let baseType = self.parseType()
942944
return RawTypeSyntax(

Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
388388
]
389389
)
390390
} else if let firstToken = node.first?.as(TokenSyntax.self),
391+
!firstToken.isEditorPlaceholder,
391392
firstToken.tokenKind.isIdentifier == true,
392393
firstToken.presence == .present,
393394
let previousToken = node.previousToken(viewMode: .sourceAccurate),

Sources/SwiftSyntax/TokenSyntax.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,16 @@ public struct TokenSyntax: SyntaxProtocol, SyntaxHashable {
131131
return raw.totalLength
132132
}
133133

134+
/// Whether the token text is an editor placeholder or not.
135+
public var isEditorPlaceholder: Bool {
136+
switch self.tokenKind {
137+
case .identifier(let text):
138+
return text.hasPrefix("<#") && text.hasSuffix("#>")
139+
default:
140+
return false
141+
}
142+
}
143+
134144
/// A token by itself has no structure, so we represent its structure by an
135145
/// empty layout node.
136146
///

Tests/SwiftParserTest/DeclarationTests.swift

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2550,4 +2550,166 @@ final class DeclarationTests: ParserTestCase {
25502550
"""
25512551
)
25522552
}
2553+
2554+
func testDeclarationsWithPlaceholders() {
2555+
assertParse(
2556+
"""
2557+
func 1️⃣<#name#>(2️⃣<#parameters#>3️⃣) -> 4️⃣<#return type#> {
2558+
5️⃣<#function body#>
2559+
}
2560+
""",
2561+
substructure: FunctionDeclSyntax(
2562+
funcKeyword: .keyword(.func),
2563+
name: .identifier("<#name#>"),
2564+
signature: FunctionSignatureSyntax(
2565+
parameterClause: FunctionParameterClauseSyntax(
2566+
parameters: FunctionParameterListSyntax([
2567+
FunctionParameterSyntax(
2568+
firstName: .identifier("<#parameters#>"),
2569+
colon: .colonToken(presence: .missing),
2570+
type: MissingTypeSyntax(
2571+
placeholder: .identifier("<#type#>", presence: .missing)
2572+
)
2573+
)
2574+
])
2575+
),
2576+
returnClause: ReturnClauseSyntax(
2577+
type: IdentifierTypeSyntax(
2578+
name: .identifier("<#return type#>")
2579+
)
2580+
)
2581+
),
2582+
body: CodeBlockSyntax(
2583+
statements: CodeBlockItemListSyntax([
2584+
CodeBlockItemSyntax(
2585+
item: .expr(
2586+
ExprSyntax(
2587+
EditorPlaceholderExprSyntax(
2588+
placeholder: .identifier("<#function body#>")
2589+
)
2590+
)
2591+
)
2592+
)
2593+
])
2594+
)
2595+
),
2596+
diagnostics: [
2597+
DiagnosticSpec(locationMarker: "1️⃣", message: "editor placeholder in source file"),
2598+
DiagnosticSpec(locationMarker: "2️⃣", message: "editor placeholder in source file"),
2599+
DiagnosticSpec(locationMarker: "3️⃣", message: "expected ':' and type in parameter", fixIts: ["insert ':' and type"]),
2600+
DiagnosticSpec(locationMarker: "4️⃣", message: "editor placeholder in source file"),
2601+
DiagnosticSpec(locationMarker: "5️⃣", message: "editor placeholder in source file"),
2602+
],
2603+
fixedSource: """
2604+
func <#name#>(<#parameters#>: <#type#>) -> <#return type#> {
2605+
<#function body#>
2606+
}
2607+
"""
2608+
)
2609+
2610+
assertParse(
2611+
"""
2612+
func test1️⃣<#name#>() {
2613+
2️⃣<#function body#>
2614+
}
2615+
""",
2616+
substructure: FunctionDeclSyntax(
2617+
funcKeyword: .keyword(.func),
2618+
name: .identifier("test"),
2619+
UnexpectedNodesSyntax([
2620+
TokenSyntax.identifier("<#name#>")
2621+
]),
2622+
signature: FunctionSignatureSyntax(
2623+
parameterClause: FunctionParameterClauseSyntax(
2624+
parameters: FunctionParameterListSyntax([])
2625+
)
2626+
),
2627+
body: CodeBlockSyntax(
2628+
statements: CodeBlockItemListSyntax([
2629+
CodeBlockItemSyntax(
2630+
item: .expr(
2631+
ExprSyntax(
2632+
EditorPlaceholderExprSyntax(
2633+
placeholder: .identifier("<#function body#>")
2634+
)
2635+
)
2636+
)
2637+
)
2638+
])
2639+
)
2640+
),
2641+
diagnostics: [
2642+
DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected code '<#name#>' in function"),
2643+
DiagnosticSpec(locationMarker: "2️⃣", message: "editor placeholder in source file"),
2644+
]
2645+
)
2646+
2647+
assertParse(
2648+
"""
2649+
class 1️⃣<#name#>: 2️⃣<#super class#> {
2650+
3️⃣<#code#>
2651+
}
2652+
""",
2653+
diagnostics: [
2654+
DiagnosticSpec(locationMarker: "1️⃣", message: "editor placeholder in source file"),
2655+
DiagnosticSpec(locationMarker: "2️⃣", message: "editor placeholder in source file"),
2656+
DiagnosticSpec(locationMarker: "3️⃣", message: "editor placeholder in source file"),
2657+
]
2658+
)
2659+
2660+
assertParse(
2661+
"""
2662+
enum 1️⃣<#name#> {
2663+
case 2️⃣<#code#>
2664+
}
2665+
""",
2666+
diagnostics: [
2667+
DiagnosticSpec(locationMarker: "1️⃣", message: "editor placeholder in source file"),
2668+
DiagnosticSpec(locationMarker: "2️⃣", message: "editor placeholder in source file"),
2669+
]
2670+
)
2671+
2672+
assertParse(
2673+
"""
2674+
struct 1️⃣<#name#> {
2675+
2️⃣<#code#>
2676+
}
2677+
""",
2678+
diagnostics: [
2679+
DiagnosticSpec(locationMarker: "1️⃣", message: "editor placeholder in source file"),
2680+
DiagnosticSpec(locationMarker: "2️⃣", message: "editor placeholder in source file"),
2681+
]
2682+
)
2683+
2684+
assertParse(
2685+
"""
2686+
protocol 1️⃣<#name#> {
2687+
2️⃣<#code#>
2688+
}
2689+
""",
2690+
diagnostics: [
2691+
DiagnosticSpec(locationMarker: "1️⃣", message: "editor placeholder in source file"),
2692+
DiagnosticSpec(locationMarker: "2️⃣", message: "editor placeholder in source file"),
2693+
]
2694+
)
2695+
2696+
assertParse(
2697+
"""
2698+
import 1️⃣<#name#>
2699+
""",
2700+
diagnostics: [
2701+
DiagnosticSpec(locationMarker: "1️⃣", message: "editor placeholder in source file")
2702+
]
2703+
)
2704+
2705+
assertParse(
2706+
"""
2707+
typealias 1️⃣<#name#> = 2️⃣<#code#>
2708+
""",
2709+
diagnostics: [
2710+
DiagnosticSpec(locationMarker: "1️⃣", message: "editor placeholder in source file"),
2711+
DiagnosticSpec(locationMarker: "2️⃣", message: "editor placeholder in source file"),
2712+
]
2713+
)
2714+
}
25532715
}

Tests/SwiftParserTest/LexerTests.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,16 @@ public class LexerTests: ParserTestCase {
846846
LexemeSpec(.identifier, text: "<##>", trailing: "", diagnostic: "editor placeholder in source file")
847847
]
848848
)
849+
850+
assertLexemes(
851+
"let 1️⃣<#name#> = 2️⃣<#value#>",
852+
lexemes: [
853+
LexemeSpec(.keyword, text: "let", trailing: " "),
854+
LexemeSpec(.identifier, text: "<#name#>", trailing: " ", errorLocationMarker: "1️⃣", diagnostic: "editor placeholder in source file"),
855+
LexemeSpec(.equal, text: "=", trailing: " "),
856+
LexemeSpec(.identifier, text: "<#value#>", errorLocationMarker: "2️⃣", diagnostic: "editor placeholder in source file"),
857+
]
858+
)
849859
}
850860

851861
func testCommentAttribution() {

Tests/SwiftParserTest/PatternTests.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,4 +168,29 @@ final class PatternTests: ParserTestCase {
168168
substructure: subscriptWithBindingPattern
169169
)
170170
}
171+
172+
func testPatternAsPlaceholderExpr() {
173+
assertParse(
174+
"let 1️⃣<#name#> = 2️⃣<#value#>",
175+
substructure: VariableDeclSyntax(
176+
bindingSpecifier: .keyword(.let),
177+
bindings: [
178+
PatternBindingSyntax(
179+
pattern: IdentifierPatternSyntax(
180+
identifier: .identifier("<#name#>")
181+
),
182+
initializer: InitializerClauseSyntax(
183+
value: EditorPlaceholderExprSyntax(
184+
placeholder: .identifier("<#value#>")
185+
)
186+
)
187+
)
188+
]
189+
),
190+
diagnostics: [
191+
DiagnosticSpec(locationMarker: "1️⃣", message: "editor placeholder in source file"),
192+
DiagnosticSpec(locationMarker: "2️⃣", message: "editor placeholder in source file"),
193+
]
194+
)
195+
}
171196
}

Tests/SwiftParserTest/TypeTests.swift

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,4 +216,92 @@ final class TypeTests: ParserTestCase {
216216
substructureAfterMarker: "1️⃣"
217217
)
218218
}
219+
220+
func testTypeWithPlaceholder() {
221+
assertParse(
222+
"let a: 1️⃣<#T#> = x",
223+
substructure: VariableDeclSyntax(
224+
bindingSpecifier: .keyword(.let),
225+
bindings: [
226+
PatternBindingSyntax(
227+
pattern: IdentifierPatternSyntax(identifier: .identifier("a")),
228+
typeAnnotation: TypeAnnotationSyntax(
229+
type: IdentifierTypeSyntax(
230+
name: .identifier("<#T#>")
231+
)
232+
),
233+
initializer: InitializerClauseSyntax(
234+
value: DeclReferenceExprSyntax(
235+
baseName: .identifier("x")
236+
)
237+
)
238+
)
239+
]
240+
),
241+
diagnostics: [
242+
DiagnosticSpec(message: "editor placeholder in source file")
243+
]
244+
)
245+
246+
assertParse(
247+
"let a: 1️⃣<#T#><Foo> = x",
248+
substructure: VariableDeclSyntax(
249+
bindingSpecifier: .keyword(.let),
250+
bindings: [
251+
PatternBindingSyntax(
252+
pattern: IdentifierPatternSyntax(identifier: .identifier("a")),
253+
typeAnnotation: TypeAnnotationSyntax(
254+
type: IdentifierTypeSyntax(
255+
name: .identifier("<#T#>"),
256+
genericArgumentClause: GenericArgumentClauseSyntax(
257+
arguments: GenericArgumentListSyntax([
258+
GenericArgumentSyntax(
259+
argument: IdentifierTypeSyntax(
260+
name: .identifier("Foo")
261+
)
262+
)
263+
])
264+
)
265+
)
266+
),
267+
initializer: InitializerClauseSyntax(
268+
value: DeclReferenceExprSyntax(
269+
baseName: .identifier("x")
270+
)
271+
)
272+
)
273+
]
274+
),
275+
diagnostics: [
276+
DiagnosticSpec(message: "editor placeholder in source file")
277+
]
278+
)
279+
280+
assertParse(
281+
"let a: [1️⃣<#T#>] = x",
282+
substructure: VariableDeclSyntax(
283+
bindingSpecifier: .keyword(.let),
284+
bindings: [
285+
PatternBindingSyntax(
286+
pattern: IdentifierPatternSyntax(identifier: .identifier("a")),
287+
typeAnnotation: TypeAnnotationSyntax(
288+
type: ArrayTypeSyntax(
289+
element: IdentifierTypeSyntax(
290+
name: .identifier("<#T#>")
291+
)
292+
)
293+
),
294+
initializer: InitializerClauseSyntax(
295+
value: DeclReferenceExprSyntax(
296+
baseName: .identifier("x")
297+
)
298+
)
299+
)
300+
]
301+
),
302+
diagnostics: [
303+
DiagnosticSpec(message: "editor placeholder in source file")
304+
]
305+
)
306+
}
219307
}

0 commit comments

Comments
 (0)