@@ -19,40 +19,112 @@ import SwiftSyntax
19
19
/// Syntactic code action provider to provide refactoring actions that
20
20
/// edit a package manifest.
21
21
struct PackageManifestEdits : SyntaxCodeActionProvider {
22
- static func codeActions( in scope: SyntaxCodeActionScope ) -> [ CodeAction ] {
23
- guard
24
- let token = scope. firstToken,
25
- let call = token. findEnclosingCall ( )
26
- else {
27
- return [ ]
22
+ static func codeActions( in scope: SyntaxCodeActionScope ) -> [ CodeAction ] {
23
+ guard let token = scope. firstToken,
24
+ let call = token. findEnclosingCall ( )
25
+ else {
26
+ return [ ]
27
+ }
28
+
29
+ var actions = [ CodeAction] ( )
30
+
31
+ // Add test target(s)
32
+ actions. append (
33
+ contentsOf: maybeAddTestTargetActions ( call: call, in: scope)
34
+ )
35
+
36
+ // Add product(s).
37
+ actions. append (
38
+ contentsOf: maybeAddProductActions ( call: call, in: scope)
39
+ )
40
+
41
+ return actions
28
42
}
29
43
30
- var actions = [ CodeAction] ( )
44
+ /// Produce code actions to add test target(s) if we are currently on
45
+ /// a target for which we know how to create a test.
46
+ static func maybeAddTestTargetActions(
47
+ call: FunctionCallExprSyntax ,
48
+ in scope: SyntaxCodeActionScope
49
+ ) -> [ CodeAction ] {
50
+ guard let calledMember = call. findMemberAccessCallee ( ) ,
51
+ targetsThatAllowTests. contains ( calledMember) ,
52
+ let targetName = call. findStringArgument ( label: " name " )
53
+ else {
54
+ return [ ]
55
+ }
31
56
32
- // If there's a target name, offer to create a test target derived from it.
33
- if let targetName = call. findStringArgument ( label: " name " ) {
34
57
do {
58
+ // Describe the target we are going to create.
35
59
let target = try TargetDescription (
36
60
name: " \( targetName) Tests " ,
37
61
dependencies: [ . byName( name: targetName, condition: nil ) ] ,
38
62
type: . test
39
63
)
40
64
41
65
let edits = try AddTarget . addTarget ( target, to: scope. file)
42
- actions. append (
43
- CodeAction (
44
- title: " Add test target for this target " ,
45
- kind: . refactor,
46
- edit: edits. asWorkspaceEdit ( snapshot: scope. snapshot)
47
- )
66
+ return [
67
+ CodeAction (
68
+ title: " Add test target " ,
69
+ kind: . refactor,
70
+ edit: edits. asWorkspaceEdit ( snapshot: scope. snapshot)
71
+ )
72
+ ]
73
+ } catch {
74
+ return [ ]
75
+ }
76
+ }
77
+
78
+ /// A list of target kinds that allow the creation of tests.
79
+ static let targetsThatAllowTests : Set < String > = [
80
+ " executableTarget " ,
81
+ " macro " ,
82
+ " target " ,
83
+ ]
84
+
85
+ /// Produce code actions to add a product if we are currently on
86
+ /// a target for which we can create a product.
87
+ static func maybeAddProductActions(
88
+ call: FunctionCallExprSyntax ,
89
+ in scope: SyntaxCodeActionScope
90
+ ) -> [ CodeAction ] {
91
+ guard let calledMember = call. findMemberAccessCallee ( ) ,
92
+ targetsThatAllowProducts. contains ( calledMember) ,
93
+ let targetName = call. findStringArgument ( label: " name " )
94
+ else {
95
+ return [ ]
96
+ }
97
+
98
+ do {
99
+ let type : ProductType = calledMember == " executableTarget "
100
+ ? . executable
101
+ : . library( . automatic)
102
+
103
+ // Describe the target we are going to create.
104
+ let product = try ProductDescription (
105
+ name: " \( targetName) " ,
106
+ type: type,
107
+ targets: [ targetName ]
48
108
)
109
+
110
+ let edits = try AddProduct . addProduct ( product, to: scope. file)
111
+ return [
112
+ CodeAction (
113
+ title: " Add product to export this target " ,
114
+ kind: . refactor,
115
+ edit: edits. asWorkspaceEdit ( snapshot: scope. snapshot)
116
+ )
117
+ ]
49
118
} catch {
50
- // nothing to do
119
+ return [ ]
51
120
}
52
121
}
53
122
54
- return actions
55
- }
123
+ /// A list of target kinds that allow the creation of tests.
124
+ static let targetsThatAllowProducts : Set < String > = [
125
+ " executableTarget " ,
126
+ " target " ,
127
+ ]
56
128
}
57
129
58
130
fileprivate extension PackageEditResult {
@@ -163,4 +235,16 @@ fileprivate extension FunctionCallExprSyntax {
163
235
164
236
return nil
165
237
}
238
+
239
+ /// Find the callee when it is a member access expression referencing
240
+ /// a declaration when a specific name.
241
+ func findMemberAccessCallee( ) -> String ? {
242
+ guard let memberAccess = self . calledExpression
243
+ . as ( MemberAccessExprSyntax . self)
244
+ else {
245
+ return nil
246
+ }
247
+
248
+ return memberAccess. declName. baseName. text
249
+ }
166
250
}
0 commit comments