SwiftFormat自定义规则开发:从入门到精通
引言:为什么需要自定义规则?
你是否曾因团队代码风格不统一而头疼?是否在使用SwiftFormat时发现现有规则无法满足特定需求?作为iOS/macOS开发者,代码格式化工具SwiftFormat无疑是提升团队协作效率的利器。但官方规则库不可能覆盖所有场景——企业内部编码规范、遗留项目迁移需求、特殊业务逻辑格式化等问题,都需要通过自定义规则来解决。本文将带你从零开始掌握SwiftFormat规则开发全流程,从基础架构到高级技巧,最终实现专业级自定义规则。
读完本文你将获得:
- 理解SwiftFormat规则引擎工作原理
- 掌握自定义规则开发的完整技术栈
- 学会编写可配置、高性能的格式化规则
- 掌握规则测试与调试的专业方法
- 获得5个实战案例的完整实现代码
一、SwiftFormat架构解析
1.1 核心组件关系图
1.2 规则执行流程
1.3 核心文件功能解析
| 文件名 | 作用 | 关键类型/方法 |
|---|---|---|
| FormatRule.swift | 规则基类定义 | FormatRule, apply() |
| Formatter.swift | 格式化引擎 | forEachToken(), replaceToken() |
| Tokenizer.swift | 代码分词器 | tokenize() |
| Declaration.swift | 语法结构解析 | Declaration协议 |
| RuleRegistry.generated.swift | 规则注册中心 | ruleRegistry字典 |
二、开发环境搭建
2.1 源码编译与调试
# 克隆仓库
git clone https://gitcode.com/GitHub_Trending/sw/SwiftFormat.git
cd SwiftFormat
# 编译项目
swift build -c release
# 生成Xcode项目(可选)
swift package generate-xcodeproj
# 运行测试
swift test
2.2 自定义规则开发目录结构
SwiftFormat/
├── Sources/
│ ├── SwiftFormat/
│ │ ├── Rules/
│ │ │ ├── CustomRules/ # 自定义规则存放目录
│ │ │ │ ├── MyFirstRule.swift
│ │ │ │ └── AdvancedRule.swift
│ │ │ └── ... # 官方规则
│ │ └── ...
│ └── ...
└── Tests/
├── SwiftFormatTests/
│ ├── Rules/
│ │ ├── CustomRulesTests/ # 自定义规则测试
│ │ │ ├── MyFirstRuleTests.swift
│ │ │ └── AdvancedRuleTests.swift
│ │ └── ...
│ └── ...
└── ...
2.3 调试配置(VSCode示例)
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Custom Rule",
"type": "cpp",
"request": "launch",
"program": "${workspaceFolder}/.build/debug/swiftformat",
"args": ["--debug", "--rules", "myFirstRule", "test.swift"],
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": true
}
]
}
三、规则开发基础
3.1 第一个自定义规则:空行规范强化
3.1.1 规则实现(BlankLineEnforcer.swift)
import Foundation
public extension FormatRule {
static let blankLineEnforcer = FormatRule(
help: "确保函数间至少有一个空行,类定义前后有两个空行",
options: ["min-function-blank-lines", "min-type-blank-lines"]
) { formatter in
var previousWasFunction = false
var previousWasType = false
var blankLineCount = 0
formatter.forEachToken(where: { $0.isLinebreak || $0.isDeclarationKeyword }) { index, token in
guard formatter.isEnabled else { return }
let minFunctionLines = formatter.options.minFunctionBlankLines ?? 1
let minTypeLines = formatter.options.minTypeBlankLines ?? 2
if token.isLinebreak {
blankLineCount += 1
return
}
// 处理函数声明
if token.isFunctionKeyword, previousWasFunction {
let required = minFunctionLines
if blankLineCount < required {
formatter.insertBlankLines(at: index, count: required - blankLineCount)
} else if blankLineCount > required {
formatter.removeExtraBlankLines(at: index, keep: required)
}
}
// 处理类型声明
if token.isTypeKeyword, previousWasType {
let required = minTypeLines
if blankLineCount < required {
formatter.insertBlankLines(at: index, count: required - blankLineCount)
} else if blankLineCount > required {
formatter.removeExtraBlankLines(at: index, keep: required)
}
}
previousWasFunction = token.isFunctionKeyword
previousWasType = token.isTypeKeyword
blankLineCount = 0
}
} examples: {
"""
```diff
-func foo() {}
-func bar() {}
+func foo() {}
+func bar() {}
-class A {}
-class B {}
+class A {}
+class B {}
```
"""
}
}
// MARK: - Formatter扩展(工具方法)
private extension Formatter {
func insertBlankLines(at index: Int, count: Int) {
guard count > 0 else { return }
let linebreaks = Array(repeating: Token.linebreak, count: count)
insert(linebreaks, at: index)
}
func removeExtraBlankLines(at index: Int, keep: Int) {
var currentIndex = index - 1
var removed = 0
while currentIndex >= 0, tokens[currentIndex].isLinebreak {
if removed >= keep {
removeToken(at: currentIndex)
} else {
removed += 1
}
currentIndex -= 1
}
}
}
// MARK: - Token扩展(辅助判断)
private extension Token {
var isFunctionKeyword: Bool { self == .keyword("func") }
var isTypeKeyword: Bool {
["class", "struct", "enum", "protocol", "extension"].contains(string)
}
var isDeclarationKeyword: Bool { isFunctionKeyword || isTypeKeyword }
}
3.1.2 规则注册
在RuleRegistry.generated.swift中添加:
"blankLineEnforcer": .blankLineEnforcer,
3.2 规则选项系统详解
SwiftFormat的选项系统允许规则接收配置参数,实现灵活定制。以下是选项定义的完整流程:
- 定义选项描述符(在OptionDescriptor.swift中):
static let minFunctionBlankLines = OptionDescriptor<Int>(
name: "min-function-blank-lines",
help: "函数间最小空行数",
defaultValue: 1,
valueRange: 1...5
)
static let minTypeBlankLines = OptionDescriptor<Int>(
name: "min-type-blank-lines",
help: "类型定义前后最小空行数",
defaultValue: 2,
valueRange: 1...5
)
- 在FormatOptions中添加属性:
public var minFunctionBlankLines: Int?
public var minTypeBlankLines: Int?
- 在规则中访问选项:
let minFunctionLines = formatter.options.minFunctionBlankLines ?? 1
3.3 Token流处理核心技术
3.3.1 Token遍历与过滤
// 遍历所有标识符和关键字
formatter.forEachToken(where: { $0.isIdentifier || $0.isKeyword }) { index, token in
// 处理逻辑
}
// 查找特定模式的token序列
if formatter.token(at: index) == .keyword("class"),
let nameIndex = formatter.index(of: .identifier, after: index),
let braceIndex = formatter.index(of: .startOfScope("{"), after: nameIndex) {
// 找到类定义
}
3.3.2 Token修改操作
// 替换token
formatter.replaceToken(at: index, with: .identifier(newName))
// 插入token
formatter.insert(.linebreak, at: index + 1)
// 删除token范围
formatter.removeTokens(in: startIndex ... endIndex)
// 批量替换
formatter.replaceTokens(in: range, with: [.keyword("let"), .space(" "), .identifier("newVar")])
四、高级规则开发技巧
4.1 语法结构解析(Declaration API)
SwiftFormat提供了强大的声明解析API,可以直接操作类、函数、属性等语法结构:
// 获取所有类型声明
let types = formatter.declarations(ofType: .class, .struct, .enum)
// 遍历类的属性
for type in types {
guard let body = type.body else { continue }
for declaration in body {
if case .property(let property) = declaration.kind {
// 处理属性
if property.isStatic, property.visibility == .private {
formatter.markAsUnused(property.range)
}
}
}
}
4.2 性能优化指南
4.2.1 避免不必要的遍历
// 不佳:多次遍历同一token流
formatter.forEachToken(where: { $0.isClassKeyword }) { ... }
formatter.forEachToken(where: { $0.isStructKeyword }) { ... }
// 优化:单次遍历分类处理
var classes = [Int]()
var structs = [Int]()
formatter.forEachToken { index, token in
if token.isClassKeyword { classes.append(index) }
else if token.isStructKeyword { structs.append(index) }
}
4.2.2 使用作用域感知遍历
// 只处理特定作用域内的token
formatter.forEachToken(inScope: .function) { index, token in
// 仅函数体内的token会被处理
}
4.3 条件禁用与指令支持
实现类似// swiftformat:disable的指令支持:
private var disabledUntilIndex: Int?
formatter.forEachToken(where: { $0.isComment }) { index, token in
if let comment = token.commentText,
comment.contains("swiftformat:disable:next blankLineEnforcer") {
disabledUntilIndex = formatter.nextNonCommentIndex(after: index)
}
}
// 在主处理逻辑中检查
guard formatter.isEnabled, disabledUntilIndex ?? 0 <= index else { return }
五、测试与调试体系
5.1 单元测试编写规范
import XCTest
@testable import SwiftFormat
class BlankLineEnforcerTests: XCTestCase {
func testFunctionBlankLines() {
let input = """
func foo() {}
func bar() {}
"""
let expected = """
func foo() {}
func bar() {}
"""
testFormatting(for: input, expected, rule: .blankLineEnforcer)
}
func testCustomFunctionBlankLines() {
let input = """
func foo() {}
func bar() {}
"""
let expected = """
func foo() {}
func bar() {}
"""
testFormatting(
for: input, expected,
rule: .blankLineEnforcer,
options: FormatOptions(minFunctionBlankLines: 2)
)
}
}
5.2 使用test_rule.sh进行测试
# 运行单个规则测试
./Scripts/test_rule.sh blankLineEnforcer
# 运行带参数的测试
./Scripts/test_rule.sh blankLineEnforcer --options min-function-blank-lines=2
# 运行性能测试
./Scripts/test_rule.sh blankLineEnforcer --performance
5.3 调试技巧与工具
-
使用Xcode断点调试:
- 在规则的apply方法中设置断点
- 观察formatter.tokens数组状态
- 使用lldb命令
po formatter.tokens查看token流
-
Token可视化:
// 在规则中添加调试输出 print("Tokens from \(startIndex) to \(endIndex):") for i in startIndex...endIndex { print("\(i): \(formatter.token(at: i)!)") }
六、实战案例:5个实用自定义规则
6.1 驼峰命名自动修复器
功能:将下划线命名转换为驼峰命名,并处理特殊前缀。
public extension FormatRule {
static let camelCaseConverter = FormatRule(
help: "将下划线命名转换为驼峰命名",
options: ["preserve-prefixes"]
) { formatter in
let preservePrefixes = formatter.options.preservePrefixes ?? []
formatter.forEachToken(where: { $0.isIdentifier }) { index, token in
guard formatter.isEnabled else { return }
let originalName = token.string
let words = originalName.components(separatedBy: "_")
guard words.count > 1 else { return }
// 检查是否需要保留前缀
let prefix = words.first.flatMap { prefix in
preservePrefixes.first { originalName.hasPrefix("\($0)_") }
}
let startIndex = prefix != nil ? 2 : 1
let camelCaseWords = words[0...0] + words[startIndex...].map { $0.capitalized }
let newName = camelCaseWords.joined()
formatter.replaceToken(at: index, with: .identifier(newName))
}
} examples: {
"""
```diff
-let user_name = "John"
-var max_count = 100
-func calculate_total() {}
+let userName = "John"
+var maxCount = 100
+func calculateTotal() {}
```
"""
}
}
6.2 SwiftUI属性排序器
功能:按照特定顺序排列SwiftUI视图属性,提升可读性。
public extension FormatRule {
static let swiftUIPropertySorter = FormatRule(
help: "按特定顺序排列SwiftUI视图属性",
options: ["swiftui-property-order"]
) { formatter in
// 默认排序顺序
let defaultOrder = [
"body", "var", "let", "init", "func", "struct", "enum"
]
let customOrder = formatter.options.swiftUIPropertyOrder ?? defaultOrder
formatter.forEachDeclaration(ofType: .struct) { structDecl in
guard structDecl.name?.hasSuffix("View") ?? false,
let body = structDecl.body else { return }
// 按自定义顺序排序属性
let sortedBody = body.sorted { a, b in
let aCategory = category(for: a, order: customOrder)
let bCategory = category(for: b, order: customOrder)
if aCategory == bCategory {
return a.name ?? "" < b.name ?? ""
}
return customOrder.firstIndex(of: aCategory) ?? Int.max <
customOrder.firstIndex(of: bCategory) ?? Int.max
}
// 更新结构体内容
structDecl.updateBody(to: sortedBody)
}
}
}
private func category(for declaration: Declaration, order: [String]) -> String {
if declaration.name == "body" { return "body" }
if declaration.kind == .function { return "func" }
if declaration.kind == .property, declaration.isLet { return "let" }
if declaration.kind == .property, !declaration.isLet { return "var" }
if declaration.kind == .initializer { return "init" }
return order.last ?? "other"
}
6.3 日志语句标准化器
功能:统一日志函数调用格式,添加必要上下文信息。
public extension FormatRule {
static let logStatementNormalizer = FormatRule(
help: "标准化日志语句格式,确保包含文件名和行号",
options: ["log-function-name", "include-file-name", "include-line-number"]
) { formatter in
let logFunction = formatter.options.logFunctionName ?? "logger.log"
let includeFile = formatter.options.includeFileName ?? true
let includeLine = formatter.options.includeLineNumber ?? true
formatter.forEachToken(where: { $0.isFunctionCall(name: logFunction) }) { index, token in
guard let startIndex = formatter.index(of: .startOfScope("("), after: index),
let endIndex = formatter.index(of: .endOfScope(")"), after: startIndex) else { return }
var arguments = formatter.parseArguments(in: startIndex ... endIndex)
// 添加文件名参数
if includeFile {
let fileName = formatter.options.fileInfo.fileName ?? "unknown"
arguments.insert(.stringLiteral(fileName), at: 0)
}
// 添加行号参数
if includeLine {
let lineNumber = formatter.lineNumber(for: index)
arguments.append(.number(String(lineNumber)))
}
// 重构参数列表
formatter.replaceArguments(in: startIndex ... endIndex, with: arguments)
}
} examples: {
"""
```diff
-logger.log("User logged in")
+logger.log("AuthViewController.swift", "User logged in", 42)
```
"""
}
}
6.4 依赖注入检测器
功能:确保类初始化方法中的依赖参数有明确的注释说明。
public extension FormatRule {
static let dependencyInjectionChecker = FormatRule(
help: "确保依赖注入参数有明确注释",
disabledByDefault: true
) { formatter in
formatter.forEachDeclaration(ofType: .class, .struct) { typeDecl in
guard let initializer = typeDecl.body?.first(where: { $0.isInitializer }) else { return }
initializer.forEachParameter { param in
guard !param.hasComment else { return }
// 添加警告
formatter.reportWarning(
message: "依赖注入参数 '\(param.name)' 缺少注释",
at: param.range,
rule: .dependencyInjectionChecker
)
// 自动添加注释(可选)
if formatter.options.autoAddInjectionComments ?? false {
formatter.insertComment(
"// \(param.name): 依赖注入 - TODO: 添加具体说明",
before: param.range.lowerBound
)
}
}
}
}
}
6.5 代码复杂度检测器
功能:检测函数复杂度是否超过阈值,给出重构建议。
public extension FormatRule {
static let codeComplexityChecker = FormatRule(
help: "检测函数圈复杂度,超过阈值时警告",
options: ["max-cyclomatic-complexity"]
) { formatter in
let maxComplexity = formatter.options.maxCyclomaticComplexity ?? 10
formatter.forEachFunctionDeclaration { function in
let complexity = calculateComplexity(of: function)
if complexity > maxComplexity {
formatter.reportWarning(
message: "函数 '\(function.name ?? "unknown")' 圈复杂度过高 (\(complexity) > \(maxComplexity))",
at: function.range,
rule: .codeComplexityChecker
)
}
}
}
}
private func calculateComplexity(of function: FunctionDeclaration) -> Int {
var complexity = 1 // 基础复杂度
// 增加条件语句复杂度
complexity += function.body.count(where: {
$0.isKeyword("if") || $0.isKeyword("else") ||
$0.isKeyword("for") || $0.isKeyword("while") ||
$0.isKeyword("guard") || $0.isKeyword("switch") ||
$0.isKeyword("case") || $0.isKeyword("catch")
})
// 增加逻辑运算符复杂度
complexity += function.body.count(where: {
$0.isOperator("&&", .infix) || $0.isOperator("||", .infix)
})
return complexity
}
七、规则发布与集成
7.1 本地测试与验证
# 使用自定义规则格式化文件
swiftformat --rules+ blankLineEnforcer,swiftUIPropertySorter MyView.swift
# 查看规则帮助
swiftformat --help-rule blankLineEnforcer
# 生成规则文档
swiftformat --generate-rule-docs > CustomRules.md
7.2 团队共享方案
- 创建自定义规则包:
# 创建Swift包
mkdir SwiftFormatCustomRules
cd SwiftFormatCustomRules
swift package init --type library
# 将规则代码放入Sources目录
- 集成到项目:
# 在SwiftFormat源码中添加依赖
cd path/to/SwiftFormat
swift package edit SwiftFormatCustomRules --path /path/to/custom/rules/package
# 或使用本地依赖
// Package.swift
dependencies: [
.package(path: "../SwiftFormatCustomRules"),
]
- 注册规则:
// 在RuleRegistry.generated.swift中添加
"blankLineEnforcer": .blankLineEnforcer,
"swiftUIPropertySorter": .swiftUIPropertySorter,
八、最佳实践与常见陷阱
8.1 性能优化清单
- 避免在循环中使用复杂的正则表达式
- 对大型文件使用增量处理
- 使用
runOnceOnly: true标记无需重复执行的规则 - 利用
orderAfter指定规则执行顺序,避免冲突 - 复杂规则拆分为多个简单规则
8.2 规则冲突解决
// 正确设置规则执行顺序
static let myRule = FormatRule(
help: "...",
orderAfter: [.indent, .braces] // 在缩进和括号规则之后执行
) { ... }
// 使用临时禁用机制
formatter.disableRule(.someRule)
// 执行可能冲突的操作
formatter.enableRule(.someRule)
8.3 跨版本兼容性处理
// 检查SwiftFormat版本
if formatter.options.swiftFormatVersion >= "0.50.0" {
// 使用新版本API
} else {
// 提供兼容实现
}
// 处理不同Swift语法版本
if formatter.options.swiftVersion >= "5.5" {
// 处理async/await语法
}
九、总结与进阶学习
9.1 核心知识点回顾
- SwiftFormat规则基于
FormatRule类,通过操作token流实现格式化 - 规则开发涉及token处理、语法解析、选项配置三个核心层面
- 完善的测试是规则质量的关键保障
- 性能优化和冲突处理是高级规则开发的重点
9.2 进阶学习资源
-
官方源码分析:
- Rules目录下的官方规则实现
- Formatter类的核心API
-
推荐工具:
- SourceKit:Swift语法解析
- Swift AST Explorer:可视化Swift抽象语法树
- PerfInspector:性能分析工具
-
社区资源:
- SwiftFormat GitHub Discussions
- Swiftlint规则开发指南(思路相似)
- NSHipster关于Swift代码格式化的文章
9.3 贡献你的规则
如果你开发了通用且有用的规则,考虑贡献给SwiftFormat社区:
- Fork官方仓库
- 创建feature分支
- 实现规则及测试
- 更新文档
- 提交PR
附录:规则开发速查表
常用Token类型
| Token类型 | 示例 | 检测方法 |
|---|---|---|
| 标识符 | userName | $0.isIdentifier |
| 关键字 | class, func | $0.isKeyword |
| 操作符 | +, == | $0.isOperator |
| 分隔符 | (, {, , | $0.isDelimiter |
| 字符串 | "text" | $0.isStringLiteral |
| 注释 | // comment | $0.isComment |
| 空格 | | $0.isSpace |
| 换行 | \n | $0.isLinebreak |
常用Formatter方法
| 方法 | 用途 |
|---|---|
token(at:) | 获取指定位置的token |
forEachToken(where:body:) | 遍历符合条件的token |
index(of:after:) | 查找下一个指定token |
replaceToken(at:with:) | 替换单个token |
insert(_:at:) | 插入token |
removeToken(at:) | 删除token |
endOfScope(at:) | 查找作用域结束位置 |
currentIndentForLine(at:) | 获取行缩进 |
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



