该软件包旨在从 Swift 源代码文件中收集信息,并将这些信息编译成具体的对象,这些对象具有强类型的属性,包含找到的符号的描述。
换句话说,如果您有一个像这样的源代码文件:
// MyClass.swift
/// My class does nothing.
open class MyClass {}
— Synopsis 将为您提供结构化的信息,即存在一个 class
,它是 open
且名为 MyClass
,没有方法或属性,并且该类被记录为 My class does nothing
。此外,它没有父类。
Package.Dependency.package(
url: "https://github.com/RedMadRobot/synopsis",
from: "1.0.0"
)
Synopsis
结构体是您的起点。此结构体为您提供了一个 init(files:)
初始化器,它接受您的 *.swift
源代码文件的文件 URL 列表。
let mySwiftFiles: [URL] = getFiles()
let synopsis = Synopsis(files: mySwiftFiles)
初始化的 Synopsis
结构体具有属性 classes
、structures
、protocols
、enums
和 functions
,分别包含找到的类、结构体、协议、枚举和高级自由函数的描述。您还可以检查 parsingErrors
属性,其中包含编译过程中出现的问题列表。
struct Synopsis {
let classes: [ClassDescription]
let structures: [StructDescription]
let protocols: [ProtocolDescription]
let enums: [EnumDescription]
let functions: [FunctionDescription]
let parsingErrors: [SynopsisError]
}
关于找到的类、结构体和协议的元信息分别组织为 ClassDescription
、StructDescription
或 ProtocolDescription
结构体。这些结构体都实现了 Extensible
协议。
struct ClassDescription: Extensible {}
struct StructDescription: Extensible {}
struct ProtocolDescription: Extensible {}
protocol Extensible: Equatable, CustomDebugStringConvertible {
var comment: String?
var annotations: [Annotation]
var declaration: Declaration
var accessibility: Accessibility
var name: String
var inheritedTypes: [String]
var properties: [PropertyDescription]
var methods: [MethodDescription]
var verse: String // this one is special
}
Extensibles(可以理解为“类”、“结构体”或“协议”)包括
comment
— extensible 上方的可选文档。annotations
— 从 comment
解析的 Annotation
实例列表;有关更多详细信息,请参见 注解。declaration
— 关于当前 extensible 可以在哪里找到的信息(文件、行号、列号等);有关更多详细信息,请参见 声明。accessibility
— private
、internal
、public
和 open
的 enum
。name
— extensible 的名称。inheritedTypes
— 所有父类的列表(如果有)。properties
— 所有属性的列表;有关更多详细信息,请参见 属性。methods
— 方法列表,包括初始化程序;有关更多详细信息,请参见 方法和函数。还有一个特殊的计算属性 verse: String
,允许将 Extensible
获取为源代码。这是一种组合新实用程序类的便捷方法,有关更多信息,请参见 代码生成、模板和版本化。
所有 extensibles 都支持 Equatable
和 CustomDebugStringConvertible
协议,并使用 subscript(name:)
和 contains(name:)
方法扩展 Sequence
。
extension Sequence where Iterator.Element: Extensible {
subscript(name: String) -> Iterator.Element?
func contains(name: String) -> Bool
}
struct EnumDescription: Equatable, CustomDebugStringConvertible {
let comment: String?
let annotations: [Annotation]
let declaration: Declaration
let accessibility: Accessibility
let name: String
let inheritedTypes: [String]
let cases: [EnumCase] // !!! enum cases !!!
let properties: [PropertyDescription]
let methods: [MethodDescription]
var verse: String
}
枚举描述包含几乎与 extensibles 相同的信息,但还包括一个 cases 列表。
struct EnumCase: Equatable, CustomDebugStringConvertible {
let comment: String?
let annotations: [Annotation]
let name: String
let defaultValue: String? // everything after "=", e.g. case firstName = "first_name"
let declaration: Declaration
var verse: String
}
所有枚举 cases 都有 String
名称和声明。它们还可能具有文档(带有 注解)和可选的 defaultValue: String?
。
您应该知道,defaultValue
是原始文本,可能包含引号之类的符号。
enum CodingKeys {
case firstName = "first_name" // defaultValue == "\"first_name\""
}
class FunctionDescription: Equatable, CustomDebugStringConvertible {
let comment: String?
let annotations: [Annotation]
let accessibility: Accessibility
let name: String
let arguments: [ArgumentDescription]
let returnType: TypeDescription?
let declaration: Declaration
let kind: Kind // see below
let body: String?
var verse: String
enum Kind {
case free
case class
case static
case instance
}
}
Synopsis 假设方法是一个函数子类,具有一些附加功能。
所有函数都具有
private
、internal
、public
或 open
);ArgumentDescription
,见下文);TypeDescription
,见下文);Declaration
,见下文);方法还具有一个计算属性 isInitializer: Bool
。
class MethodDescription: FunctionDescription {
var isInitializer: Bool {
return name.hasPrefix("init(")
}
}
// literally no more reasonable code
虽然大多数 FunctionDescription
属性是不言自明的,但其中一些属性背后有其自身的怪癖和棘手的细节。例如,方法名称必须包含圆括号 ()
,并且实际上是一种没有类型的签名,例如 myFunction(argument:count:)
。
func myFunction(arg argument: String) -> Int {}
// this function is named "myFunction(arg:)"
函数 kind
只能是 free
,而方法可以具有 class
、static
或 instance
kind。
协议内部的方法具有相同的属性集,但不包含 body。body 本身是花括号 {...}
内部的文本,但不包含括号。
func topLevelFunction() {
}
// this function body is equal to "\n"
struct ArgumentDescription: Equatable, CustomDebugStringConvertible {
let name: String
let bodyName: String
let type: TypeDescription
let defaultValue: String?
let annotations: [Annotation]
let comment: String?
var verse: String
}
函数和方法参数都具有外部名称和内部名称、类型、可选的 defaultValue
、自己的可选文档和 注解。
外部 name
是调用函数时的参数名称。 内部 bodyName
在函数 body 中使用。两者都是强制性的,尽管它们可能相等。
参数类型在下面描述,请参见 TypeDescription。
属性由 PropertyDescription
结构体表示。
struct PropertyDescription: Equatable, CustomDebugStringConvertible {
let comment: String?
let annotations: [Annotation]
let accessibility: Accessibility
let constant: Bool // is it "let"? If not, it's "var"
let name: String
let type: TypeDescription
let defaultValue: String? // literally everything after "=", if there is a "="
let declaration: Declaration
let kind: Kind // see below
let body: String? // literally everything between curly brackets, but without brackets
var verse: String
enum Kind {
case class
case static
case instance
}
}
属性可以具有文档和 注解。所有属性都有自己的 kind
,即 class
、static
或 instance
。所有属性都有名称、constant
布尔标志、可访问性、类型(参见 TypeDescription)、原始 defaultValue: String?
和 declaration: Declaration
。
计算属性也可以具有 body
,就像函数一样。body 本身是花括号 {...}
内部的文本,但不包含括号。
struct Annotation: Equatable, CustomDebugStringConvertible {
let name: String
let value: String?
}
扩展、枚举、函数、方法和属性都允许有文档。
Synopsis 会解析文档,以收集带有重要元信息的特殊注解元素。这些注解类似于 Java 注解,但缺少它们的编译时检查。
所有注解都需要有一个名称。注解也可以包含一个可选的 String
值。
注解通过 @
符号来识别,例如:
/// @model
class Model {}
注意:文档注释语法继承自 Swift 编译器,目前支持块注释和三斜线注释。方法或函数参数通常在附近的内联注释中包含文档,见下文。
使用换行符或分号 ;
分隔不同的注解
/**
@annotation1
@annotation2; @annotation3
@annotation4 value1
@annotation5 value2; @annotation5 value3
@anontation6; @annotation7 value4
*/
为了可读性,请将带有注解的函数或方法参数放在各自独立的行上
func doSomething(
with argument: String, // @annotation1
or argument2: Int, /* @annotation2 value1; @annotation3 value2 */
finally argument3: Double // @annotation4; annotation5 value3
) -> Int
虽然不禁止在参数上方添加注解
func doSomething(
// @annotation1
with argument: String,
/* @annotation2 value1; @annotation3 value2 */
or argument2: Int,
// @annotation4; annotation5 value3
finally argument3: Double
) -> Int
属性类型、参数类型、函数返回类型用一个 TypeDescription
枚举表示,包含以下几种情况:
boolean(布尔值)
integer(整数)
floatingPoint(浮点数)
doublePrecision(双精度浮点数)
string(字符串)
date(日期)
data(数据)
optional(wrapped: TypeDescription)(可选类型,包含一个包裹的 TypeDescription)
object(name: String)(对象,包含一个名称字符串)
array(element: TypeDescription)(数组,包含一个元素类型 TypeDescription)
map(key: TypeDescription, value: TypeDescription)(映射/字典,包含键和值的类型 TypeDescription)
generic(name: String, constraints: [TypeDescription])(泛型,包含名称和约束类型列表)
虽然其中一些情况是不言自明的,但其他的则需要额外的说明。
integer
类型目前有一个限制,因为它表示所有 Int
类型,如 Int16
、Int32
等。这意味着 Synopsis 无法让您确定 Int
的大小。
optional
类型包含一个包裹的 TypeDescription
,用于实际的值类型。数组、映射和泛型也是如此。
除了 Data
、Date
、NSData
和 NSDate
之外的所有对象类型都用 object(name: String)
表示。 因此,虽然 CGRect
是一个结构体,但 Synopsis
仍然认为它是一个 object("CGRect")
。
struct Declaration: Equatable {
public let filePath: URL
public let rawText: String
public let offset: Int
public let lineNumber: Int
public let columnNumber: Int
}
类、结构体、协议、属性、方法等 - 几乎所有检测到的源代码元素都有一个 declaration: Declaration
属性。
Declaration
结构封装了几个属性:
每个源代码元素都提供一个计算的 String
属性 verse
,允许获取该元素的源代码。
此源代码以编程方式组成,因此可能与手动实现有所不同。
这允许通过手动组合,例如,ClassDescrption
实例,来生成新的源代码。
虽然,每个 ClassDescription
实例都需要一个 Declaration
,其中包含一个 filePath
、rawText
、offset
和其他尚未定义的属性,因为此类源代码尚未生成。
这就是为什么 ClassDescription
和其他类为你提供了一个 template(...)
构造函数,它用一个特殊的模拟对象替换了声明。
请考虑查看 Tests/SynopsisTests/Versing
测试用例,以便熟悉这个概念。
func testVerse_fullyPacked_returnsAsExpected() {
let enumDescription = EnumDescription.template(
comment: "Docs",
accessibility: Accessibility.`private`,
name: "MyEnum",
inheritedTypes: ["String"],
cases: [
EnumCase.template(comment: "First", name: "firstName", defaultValue: "\"first_name\""),
EnumCase.template(comment: "Second", name: "lastName", defaultValue: "\"last_name\""),
],
properties: [],
methods: []
)
let expectedVerse = """
/// Docs
private enum MyEnum: String {
/// First
case firstName = "first_name"
/// Second
case lastName = "last_name"
}
"""
XCTAssertEqual(enumDescription.verse, expectedVerse)
}
使用 spm_resolve.command
加载所有依赖项,并使用 spm_generate_xcodeproj.command
组装一个 Xcode 项目文件。 此外,请确保在运行测试时 Xcode 目标是 macOS。