SyntaxSparrow 是一个 Swift 库,旨在方便分析和与 Swift 源代码进行交互。 它利用 SwiftSyntax 解析 Swift 代码并生成语法树,该语法树收集并遍历 Swift 代码的组成声明类型。
分支 | 最新的 Swift/Xcode |
---|---|
main | |
develop |
从 5.0.0
版本开始,SyntaxSparrow 明确支持 Swift 6(以及 Swift 6 语言模式)。 它仍然兼容 Swift 5.8,但是,5.8、5.9 和 5.10 版本的软件包清单不包括显式的 swift 6 语言模式设置。
底层 swift-syntax 库的最新版本不再支持 Swift 5.7,因此,Syntax Sparrow 将从 5.0.0 版本开始停止对 Swift 5.7 的积极支持。
如果您需要对 5.7 的支持,并且特别需要 5.0.0 版本升级后添加的功能,则可能需要 fork 该存储库并自行添加支持。 如果有可以作为小版本更新捕获的向后兼容更改,我们将很乐意通过 PR 流程发布它。 但是,如果存在导致重大升级的 5.7 支持更改,我们希望考虑该版本的替代发布流程(或者让您在自己的 fork 上维护它)。
SyntaxSparrow 的构建受到了现在已存档的 SwiftSemantics 项目的极大启发。 SwiftSemantics
非常棒,但由于它已被存档,唯一的选择是 fork 并自行添加功能,或者希望有人已将您的功能添加到他们的 fork 中。 SyntaxSparrow
旨在接替它并添加对便利性、功能和强化解析的更多支持(如果需要)。
产生语义类型以抽象 SwiftSyntax
生成的底层 Syntax
表达式的主要目标保持不变,但是 SyntaxSparrow
尝试实现其他几个目标
按需评估:由于某些源代码可能非常冗长和复杂,因此 SyntaxSparrow 旨在仅在您请求时才处理和迭代节点。 目标是提高性能,并使收集器专注于高级遍历。 从代码复杂性的角度来看,这是否值得内部权衡将在更新中进行审查。 幸运的是,公开可见的语义类型不受任何内部更新的影响。
源代码位置:SyntaxSparrow
使您能够询问声明在提供的源代码中的位置。
基于层次结构:与将嵌套声明展平为单个数组不同,SyntaxSparrow
中的声明能够收集子声明,因为它们在 Swift 中受支持。 例如,在枚举或扩展等中嵌套结构体
性能:将来,我们的目标是通过更高效的解析算法和数据结构来提高性能。 这将与扩展的测试套件相结合,以确保更广泛的 Swift 代码模式和习惯用法的准确性。 我们还在研究允许用户根据其特定需求定制库行为的方法,例如可定制的遍历策略和对收集的信息量进行细粒度控制。
Swift 宏开发:将宏提供的原始 SwiftSyntax 声明解析为其语义代码,以便专注于您生成的代码。
Swift 代码分析:解析 Swift 代码并创建语法树以进行深入分析。
Swift 代码生成:使用解析的语义类型以更易于阅读的方式生成代码。
语义提取:从语法树中将各种语义结构(如类、函数、枚举、结构、协议等)提取到组成类型中。
源代码更新:能够在树实例上更新源代码,从而允许在代码更改后进行后续收集。
不同的查看模式:控制处理源代码时的解析和遍历策略。
按需评估:仅在请求时才加载语义类型的详细信息。
基于层次结构:语义类型支持子声明(如果相关),以允许更多基于层次结构的遍历。
SyntaxSparrow
旨在实现源代码探索,并补充工具以实现一些常见任务。 例如
代码生成:迭代可读的语义类型以生成代码,并通过 IDE 插件、CLI、Swift Package Plugin 等添加到源代码中
静态代码分析:更准确地探索已解析的源代码,以补充代码分析任务。 即,解析函数名称以查找索引符号,并检查它们是否经过测试或未使用。
使用 Swift 源代码文件的路径、直接使用 Swift 源代码字符串或通过请求解析符合 SwiftSyntax.DeclSyntaxProtocol
的类型来初始化 SyntaxTree
。 然后,使用 SyntaxTree
的各种属性来访问收集的语义结构。
let syntaxTree = try SyntaxTree(viewMode: .fixedUp, sourceAtPath: "/path/to/your/swift/file")
syntaxTree.collectChildren()
let syntaxTree = try SyntaxTree(viewMode: .fixedUp, sourceBuffer: "source code")
syntaxTree.collectChildren()
let syntaxTree = try SyntaxTree(viewMode: .fixedUp, declarationSyntax: declaration)
syntaxTree.collectChildren()
如果要更新源代码并刷新语义结构
syntaxTree.updateToSource(newSourceCode)
syntaxTree.collectChildren()
初始化和收集后,您可以访问收集的语义结构及其属性,例如属性、修饰符、名称等
let sourceCode = """
class MyViewController: UIViewController, UICollectionViewDelegate, ListItemDisplaying {
@available(*, unavailable, message: "my message")
enum Section {
case summary, people
}
var people: [People], places: [Place]
var person: (name: String, age: Int)?
weak var delegate: MyDelegate?
@IBOutlet private(set) var tableView: UITableView!
struct MyStruct {
enum MyEnum {
case sample(title: String)
case otherSample
}
}
func performOperation<T: Any>(input: T, _ completion: (Int) -> String) where T: NSFetchResult {
typealias SampleAlias = String
}
}
"""
let syntaxTree = SyntaxTree(viewMode: .fixedUp, sourceBuffer: sourceCode)
syntaxTree.collectChildren()
//
syntaxTree.protocols[0].name // "ListItemDisplaying"
syntaxTree.protocols[0].functions[0].identifier // setListItems
syntaxTree.protocols[0].functions[0].signature.input[0].secondName // items
syntaxTree.protocols[0].functions[0].signature.input[0].isLabelOmitted // true
syntaxTree.protocols[0].functions[0].signature.input[0].type // .simple("[ListItem]")
syntaxTree.classes[0].name // MyViewController
syntaxTree.classes[0].inheritance // [UIViewController, UICollectionViewDelegate, ListItemDisplaying]
syntaxTree.classes[0].enumerations[0].name // Section
syntaxTree.classes[0].enumerations[0].cases.map(\.name) // [summary, people]
syntaxTree.classes[0].enumerations[0].attributes[0].name // available
syntaxTree.classes[0].enumerations[0].attributes[0].arguments[0].name // nil
syntaxTree.classes[0].enumerations[0].attributes[0].arguments[0].value // *
syntaxTree.classes[0].enumerations[0].attributes[0].arguments[1].name // nil
syntaxTree.classes[0].enumerations[0].attributes[0].arguments[1].value // unavailable
syntaxTree.classes[0].enumerations[0].attributes[0].arguments[2].name // "message"
syntaxTree.classes[0].enumerations[0].attributes[0].arguments[2].value // "my message"
syntaxTree.classes[0].variables[0].name // people
syntaxTree.classes[0].variables[0].type // .simple("[People]")
syntaxTree.classes[0].variables[1].name // places
syntaxTree.classes[0].variables[1].type // .simple("[Place]")
syntaxTree.classes[0].variables[2].name // person
syntaxTree.classes[0].variables[2].type // .tuple(Tuple)
syntaxTree.classes[0].variables[2].isOptional // true
switch syntaxTree.classes[0].variables[1].type {
case .tuple(let tuple):
tuple.elements.map(\.name) // [name, age]
tuple.isOptional // true
}
syntaxTree.classes[0].variables[3].type // .simple("MyDelegate")
syntaxTree.classes[0].variables[3].isOptional // true
syntaxTree.classes[0].variables[3].modifiers.map(\.name) // [weak]
syntaxTree.structures[0].name // MyStruct
syntaxTree.structures[0].enumerations[0] // MyEnum
syntaxTree.structures[0].enumerations[0].cases[0].associatedValues.map(\.name) // [title]
syntaxTree.functions[0].identifier // performOperation
syntaxTree.functions[0].genericParameters.map(\.name) // [T]
syntaxTree.functions[0].genericParameters.map(\.type) // [Any]
syntaxTree.functions[0].genericRequirements.map(\.name) // [T]
syntaxTree.functions[0].genericRequirements[0].leftTypeIdentifier // T
syntaxTree.functions[0].genericRequirements[0].rightTypeIdentifier // NSFetchResult
syntaxTree.functions[0].genericRequirements[0].relation // .sameType
syntaxTree.functions[0].typealiases[0].name // SampleAlias
syntaxTree.functions[0].typealiases[0].initializedType.type // .simple("String")
在适用的情况下,类型将符合 ModifierAssessing
协议,该协议允许您基于 SwiftSyntax.Keyword
类型评估存在哪些修饰符。 这也适用于元素类型为 Modifier
的任何 Collection
// Assess if the instnace has `private(set)` and is `public`
conformingInstance.containsModifierWithKeyword(.private, withDetail: "set")
conformingInstance.containsModifierWithKeyword(.public)
// or on a collection of modifiers
variable.modifiers.containsKeyword(.private, withDetail: "set")
一些常见的方案可用作直接 getter
conformingInstance.isPrivate
conformingInstance.isPublic
conformingInstance.isOpen
// etc
variable.isPrivateSetter
variable.isFilePrivateSetter
initializer.isConvenience
initializer.isRequired
// etc
符合的类型是
Actor
Class
Enumeration
Extension
Function
ProtocolDecl
Structure
Subscript
Typealias
Variable
Initializer
Modifier
的任何集合Declaration
类型也可以发送到 SyntaxTree
以提取源代码位置和内容
let sourceCode = """
enum Section {
case summary
case people
}
@available(*, unavailable, message: "my message")
struct MyStruct {
var name: String = "name"
}
"""
let syntaxTree = SyntaxTree(viewMode: .fixedUp, sourceBuffer: sourceCode)
syntaxTree.collectChildren()
let sourceDetails = try syntaxTree.extractSource(forDeclaration: structure)
sourceDetails.location.start.line // 5
sourceDetails.location.start.column // 4
sourceDetails.location.end.line // 8
sourceDetails.location.end.line // 4
sourceDetails.source // "@available(*, unavailable, message: \"my message\")\nstruct MyStruct {\n var name: String = \"name\"\n}"
// The `SyntaxSourceDetails` struct also has some conveniences for calculating ranges
sourceDetails.substringRange(in: source) // Range<String.Index>
sourceDetails.stringRange(in: source) // NSRange(location: 51, length: 99)
EntityType
是 SyntaxSparrow 的重要组成部分。 它表示 Swift 源代码中实体的类型。 这些实体可以包括参数、变量、函数返回类型等。
它为从 Swift 代码中提取的类型信息提供了一种结构化表示。 EntityType 对许多 Swift 类型提供全面的支持,处理简单、可选、元组、函数和结果类型。
各种 EntityType 选项包括
Int
、String
、Bool
或任何其他用户定义的类型。(Int, String)
(Int, String) -> Bool
[Type]
或关键字 Array<Type>
表示 swift 数组Set<Type>
表示 swift 集合[Type: Type]
或关键字 Dictionary<Type, Type>
表示 swift 字典Void
或 ()
var myName:
EntityType 提供了一种易于访问的方式,用于从 Swift 源代码中提取与类型相关的信息。
例如,
let function = syntaxTree.functions.first
let returnType = function?.returnType // This is an EntityType
然后,您可以检查 returnType 以确定其具体细节
switch returnType {
case .simple(let typeName):
print("Simple type: \(typeName)")
case .tuple(let tuple):
tuple.isOptional // true/false
tuple.elements // Array of `Parameter` types
case .array(let array):
array.isOptional // true/false
array.elementType // Entity Type
array.declType // .squareBrackets/.generic
case .set(let set):
set.isOptional // true/false
set.elementType // Entity Type
case .dictionary(let dict):
dict.isOptional // true/false
dict.keyType // Entity Type
dict.valueType // Entity Type
dict.declType // .squareBrackets/.generics
case .closure(let closure):
closure.input // Entity Type
closure.output // Entity Type
closure.isEscaping // true/false
closure.isAutoEscaping // true/false
closure.isOptional // true/false
closure.isVoidInput // true/false
closure.isVoidOutput // true/false
// see `Closure`
case .result(let result):
print(result.successType) // EntityType
print(result.failureType) // EntityType
// see `Tuple`
case .void(let rawType: let isOptional):
print(rawType) // "Void" or "()?" etc
case .empty:
print("undefined or partial")
}
目前,SyntaxSparrow 支持 Swift Package Manager (SPM)。
要将 SyntaxSparrow 添加到您的项目中,请将以下行添加到您的 Package.swift 文件中的依赖项中
.package(url: "https://github.com/CheekyGhost-Labs/SyntaxSparrow", from: "5.0.0")
然后,将 SyntaxSparrow 添加为目标的依赖项
.target(name: "YourTarget", dependencies: ["SyntaxSparrow"]),
SyntaxSparrow 在 MIT 许可证下发布。 有关更多信息,请参见 LICENSE 文件。
欢迎为 SyntaxSparrow 做出贡献! 如果您要报告错误,请随时通过打开新问题或提交拉取请求来提供帮助。
SyntaxSparrow 非常遵循标准的 git flow 流程。 在大多数情况下,应该针对 develop
分支发出拉取请求,以协调任何发布。 这还提供了一种在实际环境中从 develop
分支进行测试的方法,以进一步测试待发布的版本。 一旦准备好发布,它将被合并到 main
中,进行标记并切出一个发布分支。
❗️❗️ 请确保您根据 develop 分支创建任何拉取请求 ❗️❗️
Fork 存储库:首先,在您自己的 GitHub 帐户中创建该项目的 fork。
克隆 fork 的存储库:fork 后,将 fork 的存储库克隆到您的本地计算机,以便您可以进行更改。
git clone https://github.com/CheekyGhost-Labs/SyntaxSparrow.git
git checkout -b your-feature-branch
遵循 Swift 语言指南:确保您的代码符合 Swift 语言指南 以进行样式和语法约定。
进行更改:实施您的功能或错误修复,遵循项目的代码样式和最佳实践。 不要忘记添加测试并根据需要更新文档。
提交您的更改:使用描述性和简洁的提交消息提交您的更改。 使用祈使语气,并解释您的提交做了什么,而不是您做了什么。
# Feature
git commit -m "Feature: Adding convenience method for resolving awesomeness"
# Bug
git commit -m "Bug: Fixing issue where awesome query was not including awesome"
git pull origin develop
git push origin your-feature-branch
develop
分支。 使用必要的详细信息填写拉取请求模板,并等待项目维护者审核您的贡献。请确保为任何更改添加单元测试。 目标不是 100%
覆盖率,而是有意义的测试覆盖率,以确保您的更改按预期运行,而不会对现有行为产生负面影响。
请注意,项目维护者可能会要求您对您的贡献进行更改或提供其他信息。 对反馈持开放态度,并愿意根据需要进行调整。 一旦您的拉取请求获得批准并合并,您的更改将成为项目的一部分!