SwiftSemantics 是一个软件包,可让你将 Swift 代码解析为其组成的声明。
使用 SwiftSyntax 从 Swift 源代码构建抽象语法树,然后使用提供的 DeclarationCollector
(或你自己的符合 SyntaxVisitor
类型的类型)遍历 AST,并为每个访问的 DeclSyntax
节点构建一个 Declaration
值。
import SwiftSyntax
import SwiftSemantics
let source = #"""
import UIKit
class ViewController: UIViewController, UITableViewDelegate {
enum Section: Int {
case summary, people, places
}
var people: [People], places: [Place]
@IBOutlet private(set) var tableView: UITableView!
}
"""#
var collector = DeclarationCollector()
let tree = try SyntaxParser.parse(source: source)
collector.walk(tree)
// Import declarations
collector.imports.first?.pathComponents // ["UIKit"]
// Class declarations
collector.classes.first?.name // "ViewController"
collector.classes.first?.inheritance // ["UIViewController", "UITableViewDelegate"]
// Enumeration declarations
collector.enumerations.first?.name // "Section"
// Enumeration case declarations
collector.enumerationCases.count // 3
collector.enumerationCases.map { $0.name } // ["summary", "people", "places"])
// Variable (property) declarations
collector.variables.count // 3
collector.variables[0].name // "people"
collector.variables[1].typeAnnotation // "[Place]"
collector.variables[2].name // "tableView"
collector.variables[2].typeAnnotation // "UITableView!"
collector.variables[2].attributes.first?.name // "IBOutlet"
collector.variables[2].modifiers.first?.name // "private"
collector.variables[2].modifiers.first?.detail // "set"
注意:有关 SwiftSyntax 的更多信息,请参阅 NSHipster 的这篇文章。
此软件包与 SwiftMarkup 协调使用,被 swift-doc 用于生成 Swift 项目的文档(包括这个项目)。
将 SwiftSemantics 包添加到你的 Package.swift
中的目标依赖项。
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "YourProject",
dependencies: [
.package(
name: "SwiftSemantics",
url: "https://github.com/SwiftDocOrg/SwiftSemantics",
.exact("0.3.2")
)
]
)
如果你的项目有直接依赖项 SwiftSyntax
,请使用下面与你的 Swift 语言版本对应的声明。
// Swift 5.2
.package(url: "https://github.com/apple/swift-syntax.git",
.exact("0.50200.0")),
// Swift 5.3
.package(name: "SwiftSyntax",
url: "https://github.com/apple/swift-syntax.git",
.exact("0.50300.0")),
// Swift 5.4
.package(name: "SwiftSyntax",
url: "https://github.com/apple/swift-syntax.git",
.revision("release/5.4")),
// Swift 5.5
.package(name: "SwiftSyntax",
url: "https://github.com/apple/swift-syntax.git",
.revision("release/5.5")),
Swift 定义了 17 种不同的声明,每种声明都由 SwiftSemantics 中符合 Declaration
协议的对应类型表示。
AssociatedType(关联类型)
Class(类)
ConditionalCompilationBlock(条件编译块)
Deinitializer(析构器)
Enumeration(枚举)
Enumeration.Case(枚举用例)
Extension(扩展)
Function(函数)
Import(导入)
Initializer(构造器)
Operator(运算符)
PrecedenceGroup(优先级组)
Protocol(协议)
Structure(结构体)
Subscript(下标)
Typealias(类型别名)
Variable(变量)
注意:每个声明的示例都在文档以及 单元测试中提供。
Declaration
协议本身没有要求。但是,采用类型共享许多相同的属性,例如 attributes
、modifiers
和 keyword
。
SwiftSemantics 声明类型旨在最大限度地利用 SwiftSyntax 提供的信息,紧密遵循语法节点的结构和命名约定。在某些情况下,该库会采取额外措施来将结果优化为更传统的接口。例如,PrecedenceGroup
类型定义了嵌套的 Associativity
和 Relation
枚举,以提高便利性和类型安全性。但是,在其他情况下,结果可能会以其原始的、原始 String
值提供;此决定通常是出于对语言可能发生的未来变化的考虑,或者仅仅是出于实用性。
在大多数情况下,这些设计决策允许即使对 Swift 具有基本理解的开发人员也可以高效地使用解析的声明。但是,有一些细节值得进一步讨论。
在 Swift 中,类、枚举或结构体可以包含一个或多个构造器、属性、下标和方法,称为成员。类型本身可以是另一个类型的成员,例如 CodingKeys
枚举嵌套在符合 Codable
类型的类型中。同样,类型也可能具有一个或多个关联类型或类型别名成员。
SwiftSemantics 不提供内置支持来直接从声明值访问类型成员。这可能是该库迄今为止做出的最令人惊讶(也可能是有争议的)设计决策,但我们认为这是可用的最合理的选择。
一个动机归结为责任委托:DeclarationCollector
和其他符合 SyntaxVisitor
的类型会遍历抽象语法树,响应访问的节点,并决定是否访问或跳过节点的子节点。如果 Declaration
要初始化自己的成员,则会覆盖树遍历器访问或跳过任何子节点的决定。我们认为,涉及直接成员初始化的方法不灵活,并且更有可能产生意外结果。例如,如果你想遍历 AST 以仅收集 Swift 类声明,则没有明确的方法可以避免不必要地初始化每个顶级类的成员,而又不会潜在地错过嵌套在其他类型中的类声明。
但实际上,控制动机与扩展有关 --- 尤其是当在模块中的多个文件中使用时。考虑同一模块中的以下两个 Swift 文件:
// First.swift
enum A { enum B { } }
// Second.swift
extension A.B { static func f(){} }
第一个文件声明了两个枚举:A
和 B
,它们嵌套在 A
中。第二个文件声明了类型 A.B
的扩展,该扩展提供了一个静态函数 f()
。根据处理这些文件的顺序,A.B
上的扩展可能先于对 A
或 B
的任何了解。调和这些声明的能力超出了任何单个声明(甚至语法遍历器)的能力,并且任何中间结果都必然是不完整的,因此具有误导性。
考虑一下当我们把泛型约束扩展和条件编译混合在一起时会发生什么...
// Third.swift
#if platform(linux)
enum C {}
#else
protocol P {}
extension A.B where T: P { static func g(){} }
#end
相反,我们的方法将调和声明上下文的责任委托给 API 使用者。
这是我们为 swift-doc 确定的方法,到目前为止效果还不错。也就是说,我们当然乐于听取任何替代方法,并邀请你通过打开一个新的 Issue来分享有关项目架构的任何反馈。
Swift 是一种复杂的语言,具有许多不同的规则和概念,并非所有规则和概念都直接在 SwiftSemantics 中表示。
上一节讨论的声明成员资格就是一个例子。另一个例子是像 public
和 private(set)
这样的声明访问修饰符没有得到任何特殊处理;它们像任何其他 Modifier
值一样。
此设计策略使该库保持狭隘的重点,并且更易于随时间的推移适应语言的演变。
你可以在自己的代码中扩展 SwiftSemantics,以编码与你的问题相关的任何缺失的语言概念。例如,SwiftSemantics 不会对 属性包装器的概念进行编码,但你可以将其用作你自己的表示的基础。
protocol PropertyWrapperType {
var attributes: [Attribute] { get }
}
extension Class: PropertyWrapperType {}
extension Enumeration: PropertyWrapperType {}
extension Structure: PropertyWrapperType {}
extension PropertyWrapperType {
var isPropertyWrapper: Bool {
return attributes.contains { $0.name == "propertyWrapper" }
}
}
文档注释(如常规注释和空格)被 SwiftSyntax 视为语法节点的“琐事”。为了使该库保持狭隘的重点,我们不提供用于符号文档的内置功能(出于类似原因,声明中省略了源位置)。
如果你想自己这样做,你可以对 DeclarationCollector
进行子类化并覆盖 visit
委托方法来检索、解析文档注释并将其与相应的声明相关联。或者,你可以使用 SwiftDoc,它与 SwiftMarkup 结合使用,确实提供此功能。
swift package generate-xcodeproj
生成 Xcode 项目文件。(报告的错误是:Library not loaded: @rpath/lib_InternalSwiftSyntaxParser.dylib
)。作为一种解决方法,你可以安装最新的工具链并在“Xcode > Preferences > Components > Toolchains”中启用它。或者,你可以使用 swift test
从命令行运行单元测试。MIT
Mattt (@mattt)