https://github.com/luizzak/swiftsyntaxsearch
A small experimental library containing generic types for performing search and replacement on Swift Syntax trees.
https://github.com/luizzak/swiftsyntaxsearch
swift swift-syntax swiftsyntax
Last synced: over 1 year ago
JSON representation
A small experimental library containing generic types for performing search and replacement on Swift Syntax trees.
- Host: GitHub
- URL: https://github.com/luizzak/swiftsyntaxsearch
- Owner: LuizZak
- License: mit
- Created: 2022-09-30T21:09:19.000Z (almost 4 years ago)
- Default Branch: main
- Last Pushed: 2023-03-21T21:08:54.000Z (over 3 years ago)
- Last Synced: 2025-03-21T19:04:15.605Z (over 1 year ago)
- Topics: swift, swift-syntax, swiftsyntax
- Language: Swift
- Homepage:
- Size: 36.1 KB
- Stars: 5
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE.md
Awesome Lists containing this project
README
# SwiftSyntaxSearch
A small experimental library containing generic types for performing search and replacement on Swift Syntax trees.
## Requirements
Swift 5.7
## Searching
From a given syntax tree:
```swift
class AClass {
init() {
var decl: Int = 0
}
func member1() {
var decl2: Int = 0
var decl: Int = 1
}
func member2() {
var decl: Int = 0, decl2: Int = 0
}
}
```
We can query for all variable declarations whose first pattern binding index binds an identifier `decl` to an initial value of `0` with the following search term:
```swift
let declOf0Search = SyntaxSearchTerm
.child(
\VariableDeclSyntax.bindings[index: 0],
matches:
(\PatternBindingSyntax.pattern).matches(
as: IdentifierPatternSyntax.self,
SyntaxReplacer
.token(\.identifier, matches: "decl")
) &&
(\PatternBindingSyntax.initializer?.value).matches(
as: IntegerLiteralExprSyntax.self,
SyntaxReplacer
.token(\.digits, matches: "0")
)
)
```
And search for the syntax tree like so:
```swift
syntax.findAllDepthFirst(declOf0Search)
// Returns syntax nodes:
// var decl: Int = 0
// var decl: Int = 0, decl2: Int = 0
```
## Find and Replace
From a given syntax tree:
```swift
class AClass {
init() {
var decl: Int = 0
}
func member(_ param: Int = 1) {
var decl: Int = 2
}
}
let global = 3
```
We can find and replace all variable declarations that bind an integer of value `0` or `1`, and invoke a closure to construct a replacement to the syntax node:
```swift
let declOf0Or2Replacer =
SyntaxReplacer(searchTerm:
.or([
SyntaxReplacer
.token(\.digits, matches: "0"),
SyntaxReplacer
.token(\.digits, matches: "2"),
])
) { node in
node.withDigits(
SyntaxFactory.makeIntegerLiteral("50_" + node.digits.text)
)
}
```
And create a new the syntax tree with the replacements applied like so:
```swift
syntax.replacingAll(declOf0Or2Replacer)
// Prints the new syntax tree:
// class AClass {
// init() {
// var decl: Int = 50_0
// }
// func member(_ param: Int = 1) {
// var decl: Int = 50_2
// }
// }
//
// let global = 3
```
### Creating search terms
The following syntaxes are available and produce the same result:
```swift
// Keypath-based binding
(\PatternBindingSyntax.pattern).matches(
as: IdentifierPatternSyntax.self,
SyntaxSearchTerm
.token(\.identifier, matches: "decl")
)
// Struct creation
SyntaxSearchTerm
.child(
// KeyPath
\.pattern,
// Cast `T` to IdentifierPatternSyntax, and if successful, invokes the matcher, otherwise matching fails.
castTo: IdentifierPatternSyntax.self,
// Match IdentifierPatternSyntax.identifier (a TokenSyntax) with a given StringMatcher (string literals match with `==`)
matches:
SyntaxSearchTerm
.token(\.identifier, matches: "decl")
)
// Appending to existing search term
let emptySearch = SyntaxSearchTerm()
let declIdentSearch = emptySearch
.child(
// KeyPath
\.pattern,
// Cast `T` to IdentifierPatternSyntax, and if successful, invokes the matcher, otherwise matching fails.
castTo: IdentifierPatternSyntax.self,
// Match IdentifierPatternSyntax.identifier (a TokenSyntax) with a given StringMatcher (string literals match with `==`)
matches:
SyntaxSearchTerm
.token(\.identifier, matches: "decl")
)
```
Search terms that inspect tokens can use the shortcut `KeyPath<_, TokenSyntax>.==` to generate token string matches like with `SyntaxSearchTerm.token(\.identifier, matches: "decl")`:
```swift
let declIdentSearch: SyntaxSearchTerm
declIdentSearch = \.identifier == "decl" // equivalent to declIdentSearch = .token(\.identifier, matches: "decl")
```
#### StringMatcher
A simple enum-based string matcher that performs matches based on equality, prefix, suffix or string containment. Used by `SyntaxSearchTerm` to perform token-based string equality:
```swift
// StringMatcher.exact
let exact = StringMatcher.exact("a text")
print(exact.matches("a text")) // true
print(exact.matches("")) // false
print(exact.matches("A Text")) // false
print(exact.matches("a string containing a text with prefix and suffix")) // false
print(exact.matches("a text with suffix")) // false
print(exact.matches("prefix and then a text")) // false
// StringMatcher.contains
let contains = StringMatcher.contains("a text")
print(contains.matches("a text")) // true
print(contains.matches("")) // false
print(contains.matches("A Text")) // false
print(contains.matches("a string containing a text with prefix and suffix")) // true
print(contains.matches("a text with suffix")) // true
print(contains.matches("prefix and then a text")) // true
// StringMatcher.prefix
let prefix = StringMatcher.prefix("a text")
print(prefix.matches("a text")) // true
print(prefix.matches("")) // false
print(prefix.matches("A Text")) // false
print(prefix.matches("a string containing a text with prefix and suffix")) // false
print(prefix.matches("a text with suffix")) // true
print(prefix.matches("prefix and then a text")) // false
// StringMatcher.suffix
let suffix = StringMatcher.suffix("a text")
print(suffix.matches("a text")) // true
print(suffix.matches("")) // false
print(suffix.matches("A Text")) // false
print(suffix.matches("a string containing a text with prefix and suffix")) // false
print(suffix.matches("a text with suffix")) // false
print(suffix.matches("prefix and then a text")) // true
```