nenadvulic/solid-like-a-rock

GitHub: nenadvulic/solid-like-a-rock

一款基于 SwiftSyntax 的 Swift 架构 lint CLI 工具,通过强制分层 import 规则和安全模式检查来防止架构腐化和不安全代码模式混入。

Stars: 25 | Forks: 1

Solid Like A Rock — import rules for Swift, via SwiftSyntax

CI Latest release Swift versions License: MIT

# SolidLikeARock **AI agent 生成代码的速度已经超过了人类审查代码的速度。** 今天一个违规的 import 混进了 PR —— 网络层的 `UIKit`,或者一个直接调用了 数据工具包的 ViewModel。在代码审查中没有人发现它。 今天的一个架构违规,明天就会变成一百个,你的 Clean Architecture 最终会变成 wiki 上的一个图表,而代码在几个月前就不再遵循它了。 而且 AI 生成的代码发布不安全模式的速度与架构违规一样快 —— UserDefaults 里的 token,MD5 哈希,被悄悄禁用的 TLS 验证。 SolidLikeARock 是**一个 Swift CLI 中的双重护栏**:它在每次构建和每个 PR 时强制执行你的 架构 import 规则 *并* 检查不安全的模式,因此这两种代码偏移在出现的瞬间就会被发现 —— 无论代码是由人类编写的还是由 AI agent 生成的: ``` Sources/Presentation/HomeView.swift:5: error: SolidLikeARock: layer 'Presentation' must not import 'Data' Sources/Auth/Session.swift:42: error: SolidLikeARock: [tokenInUserDefaults] credential stored in UserDefaults under 'authToken' — UserDefaults is cleartext on disk; use the Keychain ```

Demo: a forbidden import is caught by solid-like-a-rock, fixed, and the lint goes green

在底层,它是一个轻量级、无依赖的 Swift CLI,使用 [SwiftSyntax](https://github.com/swiftlang/swift-syntax) 解析每个 `.swift` 文件 (一个真正的语法树 —— 没有脆弱的 regex / `grep`),找到每一个 `import` 语句,确定文件属于哪个架构层,如果某个层 import 了不该 import 的内容就会 报错。将 Swift 架构验证作为构建步骤:这是一种在 CI 中强制执行 **依赖倒置原则**(SOLID 中的 *D*)和 Clean Architecture 边界的实用方法 —— 依赖必须向内指向。 ``` Domain <- Data <- Presentation (dependencies point inward) ``` 它补充完善了你已经在使用的工具,而不是替代它们: | 工具 | 目的 | |------|---------| | [SwiftLint](https://github.com/realm/SwiftLint) | 代码风格规则 | | [Periphery](https://github.com/peripheryapp/periphery) | 无用代码 | | **SolidLikeARock** | 架构与安全规则 | ## 为什么是现在? AI 编程助手生成代码的速度已经超过了人类审查代码的速度。 挑战不再在于编写代码。 挑战在于确保生成的代码符合你的架构 —— 并且不会在这个过程中悄悄发布不安全的模式。 SolidLikeARock 作为两者的护栏:它在每次构建和每个 PR 时强制执行你的 import 规则和安全检查,因此没有 AI 生成的捷径会悄无声息地破坏你的代码层或将凭证以明文存储。 ## 安装 **Homebrew(推荐):** ``` brew tap nenadvulic/solid-like-a-rock brew install solid-like-a-rock ``` **从源码构建:** ``` git clone https://github.com/nenadvulic/solid-like-a-rock.git cd solid-like-a-rock swift build -c release cp .build/release/solid-like-a-rock /usr/local/bin/ ``` 或者不安装直接运行: ``` swift run solid-like-a-rock --config .solid.yml Sources ``` ## 快速开始 **架构 lint**(分层 import 规则): ``` solid-like-a-rock init # analyse the project, generate .solid.yml solid-like-a-rock lint Sources ``` **仅安全检查 —— 无需架构配置:** ``` solid-like-a-rock init --security solid-like-a-rock lint Sources ``` ## 生成配置 (`init`) 在现有项目中手动编写 `.solid.yml` 是繁琐的。`init` 通过分析项目的**真实模块间 import 图**来生成 一个初始配置 —— 具有确定性,不使用 LLM。它为每个本地模块生成一个层;你可以随后将它们重新分组/重命名为业务层。 ``` # 冻结当前 architecture(最适合 legacy adoption): solid-like-a-rock init --freeze ./MyApp # Heuristic layering 提案,待审查: solid-like-a-rock init ./MyApp # 包含非标准 modules 目录的 Multi-package 项目: solid-like-a-rock init --packages-dir Modules . # TCA (The Composable Architecture) 项目 — 分组为 Models/Dependencies/Features/App: solid-like-a-rock init --tca ./MyTCAApp # 仅进行 Security 检查 — 不含 architecture 规则: solid-like-a-rock init --security ``` `--security` 在项目尚无配置时写入一个[仅安全预设](docs/configuration.md#security-checks-security),并将 `security:` 部分追加到 `init` 本应生成的配置中 —— 它可以与 `--tca` 和 `--freeze` 组合使用。 - **`--freeze`** —— 对于每个模块,禁止它今天未 import 的任何*其他*本地模块。结果:**目前违规数为零**,并且当出现**新**的跨模块依赖时,linter 会立即拦截。这是在现有代码库上最快的上手方式。

Demo: init --freeze generates a config with zero violations, then catches a new cross-module dependency

- **默认(启发式)** —— 根据模块在 import 图中的深度进行排序,仅禁止*向外*的依赖(指向更外层的依赖)。更加宽松;请自行审查。 它会自动检测布局(`Packages//Sources` 或 `Sources/`),仅扫描源码(绝不扫描 `Tests/`),忽略系统/第三方 import,并生成一个排序好、具有确定性且带有注释的文件。没有 `--force` 参数它不会覆盖现有文件。 需要手动编写或 AI 生成的配置吗(例如纯 Xcode/CocoaPods 项目)? 请参阅 **[配置](docs/configuration.md)**。 ## 运行 ``` solid-like-a-rock Sources ``` 输出使用 `file:line: error: message` 格式,因此违规行为会直接显示在 Xcode 和 CI 日志中: ``` Sources/Domain/User.swift:3: error: SolidLikeARock: layer 'Domain' is not allowed to import 'UIKit' Sources/Presentation/HomeView.swift:5: error: SolidLikeARock: layer 'Presentation' must not import 'Data' ❌ SolidLikeARock: 2 violation(s) found. ``` 当发现违规时,退出代码为非零值 —— 可以直接将其放入 CI 步骤或 Xcode 的“Run Script”构建阶段中。 ## 文档 深入研究的文档位于 [`docs/`](docs/): - **[配置](docs/configuration.md)** —— 编写和调整 `.solid.yml`:层、分层的 `dependencyOrder` 模式、可见性规则、安全检查,以及用于初始化配置的 AI prompt。 - **[在现有代码库中采用](docs/adoption.md)** —— 基准化(仅在出现*新*违规时报错)、内联 `// solid:ignore` 抑制以及警告级别的严重性。 - **[集成](docs/integrations.md)** —— Xcode、GitHub Action / CI、SwiftPM 命令和构建工具插件、Danger、Claude Code 以及架构图。 - **[配合 TCA 使用](docs/tca.md)** —— 在 The Composable Architecture 中强制执行 feature 同级隔离。 - **[安全规则参考](docs/security-rules.md)** —— 全部 14 条规则,每条规则的触发条件及其原因。 ## 示例项目 一个可运行的 4 层 Clean Architecture 示例位于 `Tests/SolidCoreTests/Fixtures/CleanArchSample`,同时它也是该工具的集成 测试。它包含五个行为良好的文件和三个故意跨越边界的文件: ``` CleanArchSample/ ├─ .solid.yml └─ Sources/ ├─ Domain/ User.swift, UserRepository.swift # pure, imports only Foundation ├─ Application/ FetchUserUseCase.swift # ✅ imports Domain │ BadUseCase.swift # ❌ imports Infrastructure ├─ Infrastructure/ CoreDataUserStore.swift # ✅ imports Domain (inward) │ BadGateway.swift # ❌ imports Presentation └─ Presentation/ UserView.swift # ✅ SwiftUI + Application LeakyView.swift # ❌ imports Infrastructure ``` 运行它: ``` cd Tests/SolidCoreTests/Fixtures/CleanArchSample solid-like-a-rock --config .solid.yml Sources ``` 预期输出 —— 刚好三个违规,退出代码为 1: ``` Sources/Application/BadUseCase.swift:2: error: SolidLikeARock: layer 'Application' is not allowed to import 'Infrastructure' Sources/Infrastructure/BadGateway.swift:2: error: SolidLikeARock: layer 'Infrastructure' must not import 'Presentation' Sources/Presentation/LeakyView.swift:2: error: SolidLikeARock: layer 'Presentation' must not import 'Infrastructure' ❌ SolidLikeARock: 3 violation(s) found. ``` `BadUseCase` 触发了白名单(`Application` 只能 import `Domain`),而 `BadGateway` 和 `LeakyView` 触发了黑名单 —— 后者是关键的 依赖倒置边界:UI 绝不能直接访问 `Infrastructure`。 ## 为什么使用 SwiftSyntax 而不是 regex? regex 会匹配到字符串、注释和 `#if` 块中的 `import`。SwiftSyntax 解析的是实际的语法,因此 `let s = "import Secrets"` 会被正确忽略,条件 import 的处理方式也与编译器看到的一致。 ## 真实案例 首次在一个生产环境的汽车共享 iOS 应用上运行(约 30 个 SPM 模块,Clean Architecture 分层,包含数年的开发历史):**25 个违规**,这些在代码审查中均未被发现 —— - 在 network provider(Data 层)内部 import 了 `UIKit` - 数据工具包中存在 SwiftUI 视图 - Presentation ViewModel 直接 import 了数据 provider 和工具包,绕过了 domain 所有 25 个违规都在同一天被加入了已提交的基准中;现在 CI 只会在出现*新*的违规时报错,团队按照自己的节奏逐步清除基准中的遗留问题。这就是在任何现有代码库中采用的路径:测量、冻结,然后逐步收紧。请参阅 **[在现有代码库中采用](docs/adoption.md)**。 ## 为 AI 辅助开发而生 编程 agent 非常擅长编写 Swift —— 但也同样擅长悄悄地无视你的架构。仓库中的 `.solid.yml` 将这些隐式规则转化为机器可以遵守的规范:agent 的输出会像其他任何代码一样被 lint,无论是在本地、CI 还是 PR 中。 无论你的团队使用什么助手都能兼容 —— Claude Code、Cursor、Codex、Windsurf —— 因为护栏存在于构建过程中,而不是编辑器中。将其与 [AI 配置 prompt](docs/configuration.md#generate-a-config-with-ai) 搭配使用来初始化规则,然后让 linter 监督每一位贡献者遵守规范,无论他们是人类还是 AI。 ## 性能 在一个真实项目 —— [isowords](https://github.com/pointfreeco/isowords),Point-Free 高度模块化的 SPM 应用(88 个本地模块)上测量: | | | |---|---| | 扫描的 Swift 文件 | 388 | | `init --freeze`(完整 import 图分析) | 0.18 秒 | | `lint`(3 次运行的中位数) | 0.86 秒 | | 设备 | Apple M1 —— Homebrew 二进制文件 v0.4.2 | 使用固定在特定 isowords commit 的一条命令亲自尝试: ``` scripts/benchmark.sh ``` 此次运行同时也是集成示例:`init --freeze` 为整个项目生成了可直接使用的 `.solid.yml`(每个模块一个层,第一天违规数为零),因此 linter 只会在出现*新*的跨模块依赖时才进行拦截。 ## 路线图 - [x] 架构规则 - [x] import 验证 - [x] 架构图可视化 - [x] 安全检查(Keychain 误用、明文 HTTP、弱加密、身份验证缺陷、日志中的 PII) ## License MIT
标签:Apache Flink, Clean Architecture, LNA, SOC Prime, Swift, 云安全监控, 代码规范检查, 开发工具, 文档结构分析, 架构约束, 静态分析