Skip to content

Commit f902113

Browse files
Merge pull request #48 from CheekyGhost-Labs/develop
Merging develop for 4.1.0 minor release
2 parents d12a352 + c0cee1c commit f902113

File tree

1,310 files changed

+2593
-1099
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

1,310 files changed

+2593
-1099
lines changed

README.md

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,49 @@ syntaxTree.functions[0].typealiases[0].name // SampleAlias
189189
syntaxTree.functions[0].typealiases[0].initializedType.type // .simple("String")
190190
```
191191

192+
### Modifier Conveniences
193+
194+
Where applicable, types will conform to the `ModifierAssessing` protocol, which lets you assess what modifiers are present based on a `SwiftSyntax.Keyword` type. This is also available on any `Collection` where the element type is `Modifier`
195+
196+
```swift
197+
// Assess if the instnace has `private(set)` and is `public`
198+
conformingInstance.containsModifierWithKeyword(.private, withDetail: "set")
199+
conformingInstance.containsModifierWithKeyword(.public)
200+
// or on a collection of modifiers
201+
variable.modifiers.containsKeyword(.private, withDetail: "set")
202+
```
203+
204+
some common scenarios are available as direct getters:
205+
206+
```swift
207+
conformingInstance.isPrivate
208+
conformingInstance.isPublic
209+
conformingInstance.isOpen
210+
// etc
211+
variable.isPrivateSetter
212+
variable.isFilePrivateSetter
213+
initializer.isConvenience
214+
initializer.isRequired
215+
// etc
216+
```
217+
218+
Conforming types are:
219+
220+
- `Actor`
221+
- `Class`
222+
- `Enumeration`
223+
- `Extension`
224+
- `Function`
225+
- `ProtocolDecl`
226+
- `Structure`
227+
- `Subscript`
228+
- `Typealias`
229+
- `Variable`
230+
- `Initializer`
231+
- Any collection where the type is `Modifier`
232+
192233
### Source Locations and Bounds:
234+
193235
`Declaration` types can can also be sent to the `SyntaxTree` to extract source location and content:
194236

195237
```swift
@@ -294,7 +336,7 @@ Currently, SyntaxSparrow supports Swift Package Manager (SPM).
294336
To add SyntaxSparrow to your project, add the following line to your dependencies in your Package.swift file:
295337

296338
```swift
297-
.package(url: "https://github.com/CheekyGhost-Labs/SyntaxSparrow", from: "4.0.0")
339+
.package(url: "https://github.com/CheekyGhost-Labs/SyntaxSparrow", from: "4.1.0")
298340
```
299341

300342
Then, add SyntaxSparrow as a dependency for your target:

Sources/SyntaxSparrow/Internal/Resolvers/Declaration/VariableSemanticsResolver.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,21 @@ struct VariableSemanticsResolver: SemanticsResolving {
9797
return typeNode.type.resolveIsSyntaxOptional()
9898
}
9999

100+
func resolveIsComputed() -> Bool {
101+
// If there is a value assigned - return false
102+
guard node.initializer == nil else { return false}
103+
// If there is a setter accessor - return false
104+
let hasSetter = resolveHasSetter()
105+
guard !hasSetter else { return false }
106+
// If getter accessor exists - return true
107+
let accessors = resolveAccessors()
108+
if accessors.contains(where: { $0.kind == .get }) {
109+
return true
110+
}
111+
// Otherwise check if there is a code block
112+
return node.accessorBlock != nil
113+
}
114+
100115
func resolveHasSetter() -> Bool {
101116
// Resolver accessors for assessment
102117
let accessors = resolveAccessors()
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//
2+
// File.swift
3+
//
4+
//
5+
// Created by Michael O'Brien on 9/5/2024.
6+
//
7+
8+
import Foundation
9+
import SwiftSyntax
10+
11+
public extension Collection where Element == Modifier {
12+
13+
/// Will return true when the collection contains a ``Modifier`` whose ``Modifier/name`` matches the given keyword.
14+
///
15+
/// You can also provide a detail to assess. For example
16+
/// ```swift
17+
/// private(set) public var name: String = "foo"
18+
/// ```
19+
/// the following would return `true`:
20+
/// ```swift
21+
/// collection.containsKeyword(.private, withDetail: "set")
22+
/// ```
23+
/// - Parameters:
24+
/// - keyword: The keyword to search for
25+
/// - detail: Optional detail assigned to the modifier.
26+
/// - Returns: Bool
27+
func containsKeyword(_ keyword: Keyword, withDetail detail: String? = nil) -> Bool {
28+
contains(where: { $0.node.name.tokenKind == .keyword(keyword) && $0.detail == detail })
29+
}
30+
}

Sources/SyntaxSparrow/Public/Extensions/Collection+Parameters.swift

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,7 @@ public extension Collection where Element == Parameter {
1919
/// - Parameter includeParenthesis: Bool whether to wrap the result in parenthesis. Defaults to `true`.
2020
/// - Returns: `String`
2121
func signatureInputString(includeParenthesis: Bool = true) -> String {
22-
let components: [String] = map { param in
23-
var base = param.description
24-
base = base.replacingOccurrences(of: ",", with: "")
25-
base = base.replacingOccurrences(of: " :", with: ":")
26-
base = base.trimmingCharacters(in: .whitespacesAndNewlines)
27-
return base
28-
}
29-
let joined = components.joined(separator: ", ")
22+
let joined = map(\.description).joined(separator: ", ")
3023
if includeParenthesis {
3124
return "(\(joined))"
3225
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
//
2+
// ModifierAssessing.swift
3+
//
4+
//
5+
// Created by Michael O'Brien on 9/5/2024.
6+
//
7+
8+
import Foundation
9+
import SwiftSyntax
10+
11+
/// ``ModifierAssessing`` conforming instances provide convenience assessments of an expected array of ``Modifier`` instances.
12+
public protocol ModifierAssessing {
13+
14+
/// Array of modifiers found in the declaration.
15+
/// - See: ``SyntaxSparrow/Modifier``
16+
var modifiers: [Modifier] { get }
17+
18+
/// Returns `true` when the `public` keyword is within the collection.
19+
///
20+
/// For example:
21+
/// ```swift
22+
/// public var name: String = ""
23+
/// public func executeOrder66() {}
24+
/// public enum SomeType {}
25+
/// ```
26+
var isPublic: Bool { get }
27+
28+
/// Returns `true` when the `public`, `open`, `private`, and `fileprivate` keywords are not contained in the collection.
29+
///
30+
/// **Note: ** Will also assess whether the internal keyword is explicitly present.
31+
///
32+
/// For example:
33+
/// ```swift
34+
/// var name: String = ""
35+
/// func executeOrder66() {}
36+
/// enum SomeType {}
37+
/// ```
38+
var isInternal: Bool { get }
39+
40+
/// Returns `true` when the `open` keyword is within the collection.
41+
///
42+
/// For example:
43+
/// ```swift
44+
/// open var name: String = ""
45+
/// open func executeOrder66() {}
46+
/// open class SomeType {}
47+
/// ```
48+
var isOpen: Bool { get }
49+
50+
/// Returns `true` when the `private` keyword is within the collection with no detail assigned.
51+
///
52+
/// For example:
53+
/// ```swift
54+
/// private var name: String = ""
55+
/// private func executeOrder66() {}
56+
/// private class SomeType {}
57+
/// ```
58+
var isPrivate: Bool { get }
59+
60+
/// Returns `true` when the `filePrivate` keyword is within the collection with no detail assigned.
61+
///
62+
/// For example:
63+
/// ```swift
64+
/// fileprivate var name: String = ""
65+
/// fileprivate func executeOrder66() {}
66+
/// fileprivate class SomeType {}
67+
/// ```
68+
var isFilePrivate: Bool { get }
69+
70+
/// Returns `true` when the `private` keyword is within the collection with no detail assigned.
71+
///
72+
/// For example:
73+
/// ```swift
74+
/// final var name: String = ""
75+
/// final func executeOrder66() {}
76+
/// final class SomeType {}
77+
/// ```
78+
var isFinal: Bool { get }
79+
80+
/// Will return true when the ``ModifierAssessing/modifiers`` collection contains a modifier whose ``Modifier/name`` matches the given keyword.
81+
///
82+
/// You can also provide a detail to assess. For example
83+
/// ```swift
84+
/// private(set) public var name: String = "foo"
85+
/// ```
86+
/// the following would return `true`:
87+
/// ```swift
88+
/// collection.containsKeyword(.private, withDetail: "set")
89+
/// ```
90+
/// - Parameters:
91+
/// - keyword: The keyword to search for
92+
/// - detail: Optional detail assigned to the modifier.
93+
/// - Returns: Bool
94+
func containsModifierWithKeyword(_ keyword: Keyword, withDetail detail: String?) -> Bool
95+
}
96+
97+
extension ModifierAssessing {
98+
99+
public var isPublic: Bool { containsModifierWithKeyword(.public) }
100+
101+
public var isOpen: Bool { containsModifierWithKeyword(.open) }
102+
103+
public var isPrivate: Bool { containsModifierWithKeyword(.private) }
104+
105+
public var isFilePrivate: Bool { containsModifierWithKeyword(.fileprivate) }
106+
107+
public var isFinal: Bool { containsModifierWithKeyword(.final) }
108+
109+
public var isInternal: Bool {
110+
(!isPublic && !isOpen && !isPrivate && !isFilePrivate) || containsModifierWithKeyword(.internal)
111+
}
112+
113+
public func containsModifierWithKeyword(_ keyword: Keyword, withDetail detail: String? = nil) -> Bool {
114+
modifiers.lazy.containsKeyword(keyword, withDetail: detail)
115+
}
116+
}
117+
118+
// MARK: - Conformance
119+
120+
extension Actor: ModifierAssessing {}
121+
extension Class: ModifierAssessing {}
122+
extension Enumeration: ModifierAssessing {}
123+
extension Extension: ModifierAssessing {}
124+
extension Function: ModifierAssessing {}
125+
extension ProtocolDecl: ModifierAssessing {}
126+
extension Structure: ModifierAssessing {}
127+
extension Subscript: ModifierAssessing {}
128+
extension Typealias: ModifierAssessing {}
129+
130+
extension Variable: ModifierAssessing {
131+
132+
/// Returns `true` when the `private` keyword is within the modifier collection with the `set` detail assigned.
133+
///
134+
/// For example:
135+
/// ```swift
136+
/// private(set) var name: String = ""
137+
/// ```
138+
public var isPrivateSetter: Bool { containsModifierWithKeyword(.private, withDetail: "set") }
139+
140+
/// Returns `true` when the `fileprivate` keyword is within the modifier collection with the `set` detail assigned.
141+
///
142+
/// For example:
143+
/// ```swift
144+
/// private(set) var name: String = ""
145+
/// ```
146+
public var isFilePrivateSetter: Bool { containsModifierWithKeyword(.fileprivate, withDetail: "set") }
147+
}
148+
149+
extension Initializer: ModifierAssessing {
150+
151+
/// Returns `true` when the `required` keyword is within the modifier collection.
152+
///
153+
/// For example:
154+
/// ```swift
155+
/// required init(name: String) {}
156+
/// ```
157+
public var isRequired: Bool { containsModifierWithKeyword(.required) }
158+
159+
/// Returns `true` when the `convenience` keyword is within the modifier collection.
160+
///
161+
/// For example:
162+
/// ```swift
163+
/// convenience init(name: String) {}
164+
/// ```
165+
public var isConvenience: Bool { containsModifierWithKeyword(.convenience) }
166+
}

Sources/SyntaxSparrow/Public/Semantics/Components/Closure.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,19 @@ public struct Closure: DeclarationComponent {
2929
// MARK: - Properties
3030

3131
/// Will return the closure input element from the input `typeAnnotation` for the closure.
32-
/// **Note:** This will **always** resolve to ``EntityType/tuple`` with one or more parameters
33-
/// or ``EntityType/tuple`` if there are no inputs. i.e
32+
/// **Note:** This will **always** resolve to ``EntityType/tuple(_:)`` with one or more parameters
33+
/// or ``EntityType/tuple(_:)`` if there are no inputs. i.e
3434
/// ```swift
3535
/// - ((name: inout String, age: Int) -> Void
3636
/// - (inout String, Int) -> Void
3737
/// - (String) -> Void
3838
/// - () -> Void
3939
/// ```
4040
/// will result in:
41-
/// - ``EntityType/tuple`` with a single tuple element. The single tuple element will have the `name: inout String` and `age: Int` elements
42-
/// - ``EntityType/tuple`` with two elements. The inout `String` and the `Int`
43-
/// - ``EntityType/tuple`` with a single element. The `String`
44-
/// - ``EntityType/void``.
41+
/// - ``EntityType/tuple(_:)`` with a single tuple element. The single tuple element will have the `name: inout String` and `age: Int` elements
42+
/// - ``EntityType/tuple(_:)`` with two elements. The inout `String` and the `Int`
43+
/// - ``EntityType/tuple(_:)`` with a single element. The `String`
44+
/// - ``EntityType/void(_:_:)``.
4545
public var input: EntityType { resolver.resolveInput() }
4646

4747
/// Will return the input string for the closure. Returns an empty string if no result is found.

Sources/SyntaxSparrow/Public/Semantics/Declarations/Function.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,21 @@ public struct Function: Declaration, SyntaxChildCollecting {
5151
///
5252
/// **Note:** Value will be `false` when the `output` is `nil`
5353
public let outputIsOptional: Bool
54+
55+
/// Will return the raw function return type.
56+
///
57+
/// This can be used when a more accurate string description is needed for the ``Function/Signature/output`` property loses some info.
58+
/// For example:
59+
/// ```swift
60+
/// func example() -> (any SomeProtocol)?
61+
/// ```
62+
/// in the above:
63+
/// - `output` will be `.simple("any SomeProtocol")`
64+
/// - `outputIsOptional` will be `true`
65+
/// - The `rawOutputType` will be `"(any SomeProtocol)?"`
66+
public var rawOutputType: String? {
67+
node.returnClause?.type.description
68+
}
5469

5570
/// The `throws` or `rethrows` keyword, if any.
5671
/// Indicates whether the function can throw an error.
@@ -170,6 +185,25 @@ public struct Function: Declaration, SyntaxChildCollecting {
170185
/// `Operator.Kind` assigned when the `isOperator` is `true`.
171186
public var operatorKind: Operator.Kind? { resolver.resolveOperatorKind() }
172187

188+
/// Returns `true` when the ``Function/Signature/effectSpecifiers`` has the `throw` keyword.
189+
///
190+
/// For example, the following would return `true`:
191+
/// ```swift
192+
/// func example() throws
193+
/// ```
194+
public var isThrowing: Bool {
195+
return signature.effectSpecifiers?.throwsSpecifier != nil
196+
}
197+
198+
/// Returns `true` when the ``Function/Signature/effectSpecifiers`` has the `throw` keyword.
199+
///
200+
/// For example, the following would return `true`:
201+
/// ```swift
202+
/// func example() async
203+
/// ```
204+
public var isAsync: Bool {
205+
return signature.effectSpecifiers?.asyncSpecifier != nil
206+
}
173207
// MARK: - Properties: Resolving
174208

175209
private(set) var resolver: FunctionSemanticsResolver

0 commit comments

Comments
 (0)