Skip to content

Commit 1919d99

Browse files
committed
ci(swiftlint): 启用 --strict 并清理规则豁免
1 parent 4efb90d commit 1919d99

24 files changed

Lines changed: 121 additions & 132 deletions

‎.github/workflows/swift.yml‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,28 +30,30 @@ jobs:
3030
brew install swiftlint
3131
fi
3232
swiftlint version
33-
# 不加 --strict:warning 不阻断 CI,仅 .swiftlint.yml 中明确为 error 级别的规则会失败构建。
34-
# 待存量 warning 清零后可改为 --strict 提升门槛。
35-
swiftlint --reporter github-actions-logging
33+
# --strict:所有 warning 提升为 error。已清零规则(force_unwrapping、modifier_order、
34+
# convenience_type、computed_accessors_order 等)一旦回归即阻断构建。
35+
# missing_docs 存量未清零,已从 opt_in_rules 移入 disabled_rules(见 .swiftlint.yml),
36+
# 补齐文档后恢复并继续由 --strict 守护。
37+
swiftlint --strict --reporter github-actions-logging
3638
3739
- name: Resolve SPM dependencies
38-
run: xcodebuild -resolvePackageDependencies -scheme STBaseProject-Package
40+
run: xcodebuild -resolvePackageDependencies -scheme STBaseProject
3941

4042
- name: Build (iOS Simulator) with compile log
4143
run: |
4244
# 完整编译日志供 swiftlint analyze 使用(analyzer_rules 依赖 type-checked AST)。
4345
# 不使用增量构建缓存,确保日志完整。
4446
rm -rf ~/Library/Developer/Xcode/DerivedData
4547
xcodebuild \
46-
-scheme STBaseProject-Package \
48+
-scheme STBaseProject \
4749
-destination 'generic/platform=iOS Simulator' \
4850
clean build | tee xcodebuild.log
4951
5052
- name: SwiftLint Analyze (unused_import / unused_declaration)
5153
run: |
5254
# analyzer_rules 仅在 `swiftlint analyze` 下执行;普通 `swiftlint lint` 不会跑。
5355
# 需完整编译日志(上方 Build 步骤产出 xcodebuild.log)。
54-
swiftlint analyze \
56+
swiftlint analyze --strict \
5557
--compiler-log-path xcodebuild.log \
5658
--reporter github-actions-logging
5759

‎.swiftlint.yml‎

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ disabled_rules:
2525
# 注意:line_length 是默认开启规则,不在 disabled_rules 中关闭,
2626
# 直接在下方 line_length: 配置块生效(置于 disabled_rules 会导致同名配置块被忽略)
2727
- force_try # 由 st_no_force_try_outside_markdown 精准限制,允许 STMarkdown 静态正则
28-
- blanket_disable_command # 基础库大量历史 disable 注释,不卡"禁用过多/需 re-enable"风格
29-
- superfluous_disable_command # 同上:保留历史 disable 豁免,不因区域内未触发而告警
28+
- missing_docs # 存量 882 个公开 API 缺文档,暂不纳入 --strict 门槛;文档补齐专项后再恢复 opt_in_rules
3029

3130
opt_in_rules:
3231
- empty_count
@@ -59,7 +58,7 @@ opt_in_rules:
5958
- redundant_string_enum_value
6059
- sorted_imports
6160
- toggle_bool
62-
- missing_docs # ★ 公开 API 缺文档报警(先 warning 观察存量,后续可收紧)
61+
- computed_accessors_order
6362
# === 公共基础库 Bug Prevention(第一批,低风险高价值)===
6463
- weak_delegate # ★ delegate 必须为 weak,否则循环引用
6564
- discarded_notification_center_observer # ★ block observer token 应被持有,便于按生命周期移除
@@ -74,11 +73,11 @@ analyzer_rules:
7473
- unused_import
7574
- unused_declaration
7675

77-
# 公开 API 文档缺失检查:先 warning 观察存量,待清零后可改为 error 收紧。
78-
# 仅检查 public 级别(open/internal/private 豁免),避免内部实现噪音。
79-
# CI 不使用 --strict,因此当前 warning 不阻断;使用 --strict 时会提升为 error。
80-
missing_docs:
81-
warning: public
76+
# 公开 API 文档缺失检查:存量 882 个未清零,暂移出 opt_in_rules(见 disabled_rules),
77+
# 待文档补齐专项后再恢复并重新纳入 --strict 门槛。仅检查 public 级别。
78+
# 恢复方式:从 disabled_rules 移回 opt_in_rules,并重新启用下方配置块:
79+
# missing_docs:
80+
# warning: public
8281

8382
# === 规则参数化 ===
8483
line_length:
@@ -113,6 +112,14 @@ nesting:
113112

114113
# === 自定义规则 ===
115114
custom_rules:
115+
st_no_swiftlint_disable:
116+
name: "SwiftLint disable comments are forbidden"
117+
regex: 'swiftlint\s*:\s*disable(?:\s|:|$)'
118+
match_kinds:
119+
- comment
120+
message: "禁止使用 swiftlint:disable 规避规则;请修复真实问题或调整项目级规则配置。"
121+
severity: error
122+
116123
st_no_force_try_outside_markdown:
117124
name: "try! outside STMarkdown is forbidden"
118125
regex: '\\btry!'

‎Sources/STBaseModel/STBaseModel.swift‎

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -707,11 +707,9 @@ private struct STPropertyType {
707707
// attributes 形如:T@"NSString",&,N,V_name
708708
// 第一段以 'T' 开头,描述类型编码。
709709
var typeEncoding = ""
710-
for component in attributes.split(separator: ",") {
711-
if component.first == "T" {
712-
typeEncoding = String(component.dropFirst())
713-
break
714-
}
710+
for component in attributes.split(separator: ",") where component.first == "T" {
711+
typeEncoding = String(component.dropFirst())
712+
break
715713
}
716714
self.kind = STPropertyType.parseKind(typeEncoding)
717715
switch self.kind {
@@ -766,7 +764,7 @@ private struct STPropertyType {
766764
}
767765
}
768766

769-
/// 把传入值尝试转换为属性接受的形式;不兼容时返回 nil。
767+
// 把传入值尝试转换为属性接受的形式;不兼容时返回 nil。
770768
func coerce(_ value: Any) -> Any? {
771769
switch kind {
772770
case .object(let className):
@@ -799,7 +797,7 @@ private struct STPropertyType {
799797
return value
800798
}
801799
}
802-
800+
803801
private static func coerceObject(_ value: Any, expectedClassName: String?) -> Any? {
804802
guard let className = expectedClassName else { return value }
805803
let resolvedClass: AnyClass? = NSClassFromString(className)

‎Sources/STBaseView/STBaseView.swift‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,10 @@ extension STBaseView {
572572
self.tableViewStyle = style
573573
if self._tableView != nil, self._isInternallyCreatedTableView {
574574
#if DEBUG
575-
assertionFailure("STBaseView.st_tableViewStyle(_:) called after the internal tableView was created. All table configuration (delegate/dataSource/cell registration/pull-to-refresh/load-more) will be lost and must be re-applied.")
575+
assertionFailure(
576+
"STBaseView.st_tableViewStyle(_:) called after the internal tableView was created. "
577+
+ "All table configuration (delegate/dataSource/cell registration/pull-to-refresh/load-more) will be lost and must be re-applied."
578+
)
576579
#endif
577580
self.st_removePullToRefresh()
578581
self.st_removeLoadMore()

‎Sources/STHUD/STAlertController.swift‎

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,6 @@ open class STAlertController: UIViewController {
184184

185185
/// 是否在点击动作后自动关闭,默认 true
186186
@available(*, deprecated, renamed: "setAutoDismiss(_:)")
187-
// swiftlint:disable:next st_avoid_bool_flag_param
188187
public func setAutoDismissOnAction(_ enabled: Bool) {
189188
self.autoDismissOnAction = enabled
190189
}
@@ -250,7 +249,7 @@ open class STAlertController: UIViewController {
250249
].compactMap { $0 })
251250
}
252251

253-
if self.alertInfo.title.text != "" && self.alertInfo.message.text != "" {
252+
if !self.alertInfo.title.text.isEmpty && !self.alertInfo.message.text.isEmpty {
254253
self.titleLabel.text = self.alertInfo.title.text
255254
self.messageLabel.text = self.alertInfo.message.text
256255
self.alertView.addSubview(self.titleLabel)
@@ -265,15 +264,15 @@ open class STAlertController: UIViewController {
265264
NSLayoutConstraint(item: self.messageLabel, attribute: .left, relatedBy: .equal, toItem: self.titleLabel, attribute: .left, multiplier: 1, constant: STAlertLayoutConstant.contentHorizontal),
266265
NSLayoutConstraint(item: self.messageLabel, attribute: .right, relatedBy: .equal, toItem: self.titleLabel, attribute: .right, multiplier: 1, constant: -STAlertLayoutConstant.contentHorizontal)
267266
])
268-
} else if self.alertInfo.title.text != "" && self.alertInfo.message.text == "" {
267+
} else if !self.alertInfo.title.text.isEmpty && self.alertInfo.message.text.isEmpty {
269268
self.titleLabel.text = self.alertInfo.title.text
270269
self.alertView.addSubview(self.titleLabel)
271270
self.view.addConstraints([
272271
NSLayoutConstraint(item: self.titleLabel, attribute: .top, relatedBy: .equal, toItem: self.alertView, attribute: .top, multiplier: 1, constant: STAlertLayoutConstant.contentTop),
273272
NSLayoutConstraint(item: self.titleLabel, attribute: .left, relatedBy: .equal, toItem: self.alertView, attribute: .left, multiplier: 1, constant: STAlertLayoutConstant.contentHorizontal),
274273
NSLayoutConstraint(item: self.titleLabel, attribute: .right, relatedBy: .equal, toItem: self.alertView, attribute: .right, multiplier: 1, constant: -STAlertLayoutConstant.contentHorizontal)
275274
])
276-
} else if self.alertInfo.title.text == "" && self.alertInfo.message.text != "" {
275+
} else if self.alertInfo.title.text.isEmpty && !self.alertInfo.message.text.isEmpty {
277276
self.messageLabel.text = self.alertInfo.message.text
278277
self.alertView.addSubview(self.messageLabel)
279278
self.view.addConstraints([
@@ -420,9 +419,9 @@ open class STAlertController: UIViewController {
420419
])
421420
}
422421
self.view.layoutIfNeeded()
423-
if self.alertInfo.message.text != "" {
422+
if !self.alertInfo.message.text.isEmpty {
424423
self.newConstraint?.constant = self.messageLabel.frame.maxY + 54
425-
} else if self.alertInfo.title.text != "" {
424+
} else if !self.alertInfo.title.text.isEmpty {
426425
self.newConstraint?.constant = self.titleLabel.frame.maxY + 54
427426
}
428427
UIView.animate(withDuration: 0.3) {

‎Sources/STHUD/STProgressHUD.swift‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,6 @@ public class STProgressHUD: UIView {
172172

173173
@available(*, deprecated, renamed: "show(addedToView:animation:)")
174174
@discardableResult
175-
// swiftlint:disable:next st_avoid_bool_flag_param
176175
public class func show(addedToView view: UIView, animated: Bool) -> STProgressHUD {
177176
return show(addedToView: view, animation: animated ? .fade : .none)
178177
}
@@ -187,7 +186,6 @@ public class STProgressHUD: UIView {
187186

188187
@available(*, deprecated, renamed: "hide(addedToView:animation:)")
189188
@discardableResult
190-
// swiftlint:disable:next st_avoid_bool_flag_param
191189
public class func hide(addedToView view: UIView, animated: Bool) -> Bool {
192190
guard let hud = hudForView(view) else { return false }
193191
hud.removeFromSuperViewOnHide = true
@@ -205,7 +203,6 @@ public class STProgressHUD: UIView {
205203
}
206204

207205
@available(*, deprecated, renamed: "show(animation:)")
208-
// swiftlint:disable:next st_avoid_bool_flag_param
209206
public func show(animated: Bool) {
210207
self.showCore(animated: animated)
211208
}
@@ -216,7 +213,6 @@ public class STProgressHUD: UIView {
216213
}
217214

218215
@available(*, deprecated, renamed: "hide(animation:)")
219-
// swiftlint:disable:next st_avoid_bool_flag_param
220216
public func hide(animated: Bool) {
221217
self.hideCore(animated: animated)
222218
}
@@ -226,7 +222,6 @@ public class STProgressHUD: UIView {
226222
}
227223

228224
@available(*, deprecated, renamed: "hide(animation:afterDelay:)")
229-
// swiftlint:disable:next st_avoid_bool_flag_param
230225
public func hide(animated: Bool, afterDelay delay: TimeInterval) {
231226
self.hideCore(animation: animated ? self.animationType.stHUDAnimation : .none, afterDelay: delay)
232227
}
@@ -615,6 +610,7 @@ private extension STProgressHUD {
615610

616611
func unregisterFromNotifications() {
617612
#if !os(tvOS)
613+
// 与 registerForNotifications() 配对的注销方法,观察者生命周期由注册/注销配对管理。
618614
NotificationCenter.default.removeObserver(self)
619615
#endif
620616
}

‎Sources/STMedia/STScanView.swift‎

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -275,20 +275,20 @@ public class STScanView: UIView {
275275
var newFrame = scanLineView.frame
276276
newFrame.origin.y = endY
277277
scanLineView.frame = newFrame
278-
}
279-
) { [weak self] _ in
280-
guard let self, !self.isAnimationStopped else { return }
281-
UIView.animate(withDuration: 0.2, animations: {
282-
scanLineView.alpha = 0
283-
}) { _ in
284-
guard !self.isAnimationStopped else { return }
285-
let item = DispatchWorkItem { [weak self] in
286-
self?.startAnimation()
287-
}
288-
self.animationStartWorkItem = item
289-
DispatchQueue.main.asyncAfter(deadline: .now() + self.configuration.animationInterval, execute: item)
290-
}
291-
}
278+
},
279+
completion: { [weak self] _ in
280+
guard let self, !self.isAnimationStopped else { return }
281+
UIView.animate(withDuration: 0.2, animations: {
282+
scanLineView.alpha = 0
283+
}, completion: { _ in
284+
guard !self.isAnimationStopped else { return }
285+
let item = DispatchWorkItem { [weak self] in
286+
self?.startAnimation()
287+
}
288+
self.animationStartWorkItem = item
289+
DispatchQueue.main.asyncAfter(deadline: .now() + self.configuration.animationInterval, execute: item)
290+
})
291+
})
292292
}
293293

294294
private func stopAnimation() {

‎Sources/STMedia/STScreenshot.swift‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import UIKit
1010
public final class STScreenshot: NSObject {
1111

1212
@MainActor
13-
private class func captureData() -> Data {
13+
private static func captureData() -> Data {
1414
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
1515
return Data()
1616
}

‎Sources/STNetwork/STHTTPSession.swift‎

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,6 @@ open class STHTTPSession: NSObject {
679679
}
680680

681681
// MARK: - 任务注册
682-
683682
private func register(
684683
_ request: STRequest,
685684
task: URLSessionTask,
@@ -842,11 +841,9 @@ extension STHTTPSession: URLSessionDelegate, URLSessionDataDelegate, URLSessionT
842841

843842
let serverCertificates = self.serverCertificates(from: serverTrust)
844843
if hasCertificatePins {
845-
for serverCertificate in serverCertificates {
846-
if self.sslPinningConfig.certificates.contains(serverCertificate) {
847-
completionHandler(.useCredential, URLCredential(trust: serverTrust))
848-
return
849-
}
844+
for serverCertificate in serverCertificates where self.sslPinningConfig.certificates.contains(serverCertificate) {
845+
completionHandler(.useCredential, URLCredential(trust: serverTrust))
846+
return
850847
}
851848
}
852849

‎Sources/STSecurity/STEncrypt.swift‎

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,11 @@ public extension String {
8585
return st_hmac(key: key, algorithm: .sha512)
8686
}
8787

88-
/// AES-256-GCM 加密
89-
/// - Parameters:
90-
/// - key: 密钥字符串
91-
/// - nonce: 随机数(可选,自动生成)
92-
/// - Returns: 加密结果,包含密文、随机数和认证标签
88+
// AES-256-GCM 加密
89+
// - Parameters:
90+
// - key: 密钥字符串
91+
// - nonce: 随机数(可选,自动生成)
92+
// - Returns: 加密结果,包含密文、随机数和认证标签
9393
func st_encryptAES256GCM(key: String, nonce: AES.GCM.Nonce? = nil) throws -> (ciphertext: Data, nonce: AES.GCM.Nonce, tag: Data) {
9494
let keyData = Data(key.utf8)
9595
guard keyData.count == 32 else {
@@ -201,11 +201,11 @@ public extension Data {
201201
}
202202
}
203203

204-
/// AES-256-GCM 加密
205-
/// - Parameters:
206-
/// - key: 密钥数据
207-
/// - nonce: 随机数(可选,自动生成)
208-
/// - Returns: 加密结果,包含密文、随机数和认证标签
204+
// AES-256-GCM 加密
205+
// - Parameters:
206+
// - key: 密钥数据
207+
// - nonce: 随机数(可选,自动生成)
208+
// - Returns: 加密结果,包含密文、随机数和认证标签
209209
func st_encryptAES256GCM(key: Data, nonce: AES.GCM.Nonce? = nil) throws -> (ciphertext: Data, nonce: AES.GCM.Nonce, tag: Data) {
210210
guard key.count == 32 else {
211211
throw STCryptoError.invalidKey

0 commit comments

Comments
 (0)