SwiftFormat自定义规则开发:从入门到精通

SwiftFormat自定义规则开发:从入门到精通

【免费下载链接】SwiftFormat A command-line tool and Xcode Extension for formatting Swift code 【免费下载链接】SwiftFormat 项目地址: https://gitcode.com/GitHub_Trending/sw/SwiftFormat

引言:为什么需要自定义规则?

你是否曾因团队代码风格不统一而头疼?是否在使用SwiftFormat时发现现有规则无法满足特定需求?作为iOS/macOS开发者,代码格式化工具SwiftFormat无疑是提升团队协作效率的利器。但官方规则库不可能覆盖所有场景——企业内部编码规范、遗留项目迁移需求、特殊业务逻辑格式化等问题,都需要通过自定义规则来解决。本文将带你从零开始掌握SwiftFormat规则开发全流程,从基础架构到高级技巧,最终实现专业级自定义规则。

读完本文你将获得:

  • 理解SwiftFormat规则引擎工作原理
  • 掌握自定义规则开发的完整技术栈
  • 学会编写可配置、高性能的格式化规则
  • 掌握规则测试与调试的专业方法
  • 获得5个实战案例的完整实现代码

一、SwiftFormat架构解析

1.1 核心组件关系图

mermaid

1.2 规则执行流程

mermaid

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的选项系统允许规则接收配置参数,实现灵活定制。以下是选项定义的完整流程:

  1. 定义选项描述符(在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
)
  1. 在FormatOptions中添加属性
public var minFunctionBlankLines: Int?
public var minTypeBlankLines: Int?
  1. 在规则中访问选项
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 调试技巧与工具

  1. 使用Xcode断点调试

    • 在规则的apply方法中设置断点
    • 观察formatter.tokens数组状态
    • 使用lldb命令po formatter.tokens查看token流
  2. 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 团队共享方案

  1. 创建自定义规则包
# 创建Swift包
mkdir SwiftFormatCustomRules
cd SwiftFormatCustomRules
swift package init --type library

# 将规则代码放入Sources目录
  1. 集成到项目
# 在SwiftFormat源码中添加依赖
cd path/to/SwiftFormat
swift package edit SwiftFormatCustomRules --path /path/to/custom/rules/package

# 或使用本地依赖
// Package.swift
dependencies: [
    .package(path: "../SwiftFormatCustomRules"),
]
  1. 注册规则
// 在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 进阶学习资源

  1. 官方源码分析

    • Rules目录下的官方规则实现
    • Formatter类的核心API
  2. 推荐工具

    • SourceKit:Swift语法解析
    • Swift AST Explorer:可视化Swift抽象语法树
    • PerfInspector:性能分析工具
  3. 社区资源

    • SwiftFormat GitHub Discussions
    • Swiftlint规则开发指南(思路相似)
    • NSHipster关于Swift代码格式化的文章

9.3 贡献你的规则

如果你开发了通用且有用的规则,考虑贡献给SwiftFormat社区:

  1. Fork官方仓库
  2. 创建feature分支
  3. 实现规则及测试
  4. 更新文档
  5. 提交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:)获取行缩进

【免费下载链接】SwiftFormat A command-line tool and Xcode Extension for formatting Swift code 【免费下载链接】SwiftFormat 项目地址: https://gitcode.com/GitHub_Trending/sw/SwiftFormat

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值