Dynamic Inline
标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
dynamic_inline |
是 |
否 |
lint |
否 |
3.0.0 |
dynamic
和@inline(__alway)
不要同时使用
示例
非触发
class C {
dynamic func f() {}}
class C {
@inline(__always) func f() {}}
class C {
@inline(never) dynamic func f() {}}
触发
class C {
@inline(__always) dynamic ↓func f() {}
}
class C {
@inline(__always) public dynamic ↓func f() {}
}
class C {
@inline(__always) dynamic internal ↓func f() {}
}
class C {
@inline(__always)
dynamic ↓func f() {}
}
class C {
@inline(__always)
dynamic
↓func f() {}
}
补充
深入理解Swift派发机制
Dynamic-Swift
@inline
内联函数
Empty Count
标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
empty_count |
否 |
否 |
performance |
否 |
3.0.0 |
判空首选isEmpty
而不是count
等于0
示例
非触发
var count = 0
[Int]().isEmpty
[Int]().count > 1
[Int]().count == 1
[Int]().count == 0xff
[Int]().count == 0b01
[Int]().count == 0o07
discount == 0
order.discount == 0
触发
[Int]().↓count == 0
[Int]().↓count > 0
[Int]().↓count != 0
[Int]().↓count == 0x0
[Int]().↓count == 0x00_00
[Int]().↓count == 0b00
[Int]().↓count == 0o00
↓count == 0
Empty Enum Arguments
标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
empty_enum_arguments |
是 |
是 |
style |
否 |
3.0.0 |
当枚举和关联类型匹配并没被使用时,可以省略参数。
示例
非触发
switch foo {
case .bar: break
}
switch foo {
case .bar(let x): break
}
switch foo {
case let .bar(x): break
}
switch (foo, bar) {
case (_, _): break
}
switch foo {
case "bar".uppercased(): break
}
switch (foo, bar) {
case (_, _) where !something: break
}
switch foo {
case (let f as () -> String)?: break
}
switch foo {
default: break
}
触发
switch foo {
case .bar↓(_): break
}
switch foo {
case .bar↓(): break
}
switch foo {
case .bar↓(_), .bar2↓(_): break
}
switch foo {
case .bar↓() where method() > 2: break
}
func example(foo: Foo) {
switch foo {
case case .bar↓(_):
break
}
}
Empty Parameters
标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
empty_parameters |
是 |
是 |
style |
否 |
3.0.0 |
首选()->
而不是Void->
示例
非触发
let abc: () -> Void = {}
func foo(completion: () -> Void)
func foo(completion: () thows -> Void)
let foo: (ConfigurationTests) -> Void throws -> Void)
let foo: (ConfigurationTests) -> Void throws -> Void)
let foo: (ConfigurationTests) ->Void throws -> Void)
触发
let abc: ↓(Void) -> Void = {}
func foo(completion: ↓(Void) -> Void)
func foo(completion: ↓(Void) throws -> Void)
let foo: ↓(Void) -> () throws -> Void)
Empty Parentheses with Trailing Closure
标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
empty_parentheses_with_trailing_closure |
是 |
是 |
style |
否 |
3.0.0 |
使用尾随闭包时,应当避免在方法调用后使用空括号
示例
非触发
[1, 2].map { $0 + 1 }
[1, 2].map({ $0 + 1 })
[1, 2].reduce(0) { $0 + $1 }
[1, 2].map { number in
number + 1
}
let isEmpty = [1, 2].isEmpty()
UIView.animateWithDuration(0.3, animations: {
self.disableInteractionRightView.alpha = 0
}, completion: { _ in
()
})
触发
[1, 2].map↓() { $0 + 1 }
[1, 2].map↓( ) { $0 + 1 }
[1, 2].map↓() { number in
number + 1
}
[1, 2].map↓( ) { number in
number + 1
}
func foo() -> [Int] {
return [1, 2].map↓() { $0 + 1 }
}
补充
为什么说是方法调用之后呢?
因为尾随闭包其实是调用方法的一个参数,但是可以写在外边,所以看起来反例里边的括号倒在闭包“之前”而非“之后”了。这里的之后指的是执行顺序而非书写顺序。
Empty String
标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
empty_string |
否 |
否 |
performance |
否 |
3.0.0 |
(判空)首选使用isEmpty
而不是比较string
和空字符串
示例
非触发
myString.isEmpty
!myString.isEmpy
触发
myString↓ == ""
myString↓ != ""
Empty XCTest Method
标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
empty_xctest_method |
否 |
否 |
lint |
否 |
3.0.0 |
避免使用空的测试方法
示例
非触发
class TotoTests: XCTestCase {
var foobar: Foobar?
override func setUp() {
super.setUp()
foobar = Foobar()
}
override func tearDown() {
foobar = nil
super.tearDown()
}
func testFoo() {
XCTAssertTrue(foobar?.foo)
}
func testBar() {
// comment...
XCTAssertFalse(foobar?.bar)
// comment...
}
}
class Foobar {
func setUp() {}
func tearDown() {}
func testFoo() {}
}
class TotoTests: XCTestCase {
func setUp(with object: Foobar) {}
func tearDown(object: Foobar) {}
func testFoo(_ foo: Foobar) {}
func testBar(bar: (String) -> Int) {}
}
class TotoTests: XCTestCase {
func testFoo() { XCTAssertTrue(foobar?.foo) }
func testBar() { XCTAssertFalse(foobar?.bar) }
}
触发
class TotoTests: XCTestCase {
override ↓func setUp() {
}
override ↓func tearDown() {
}
↓func testFoo() {
}
↓func testBar() {
}
func helperFunction() {
}
}
class TotoTests: XCTestCase {
override ↓func setUp() {}
override ↓func tearDown() {}
↓func testFoo() {}
func helperFunction() {}
}
class TotoTests: XCTestCase {
override ↓func setUp() {
// comment...
}
override ↓func tearDown() {
// comment...
// comment...
}
↓func testFoo() {
// comment...
// comment...
// comment...
}
↓func testBar() {
/*
* comment...
*
* comment...
*
* comment...
*/
}
func helperFunction() {
}
}
class FooTests: XCTestCase {
override ↓func setUp() {}
}
class BarTests: XCTestCase {
↓func testFoo() {}
}
Explicit ACL
标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
explicit_acl |
否 |
否 |
idiomatic |
否 |
3.0.0 |
所有的声明都应该指定访问控制符。
示例
非触发
internal enum A {}
public final class B {}
private struct C {}
internal enum A {
internal enum B {}
}
internal final class Foo {}
internal
class Foo { private let bar = 5 }
internal func a() { let a = }
private func a() { func innerFunction() { } }
private enum Foo { enum Bar { } }
private struct C { let d = 5 }
internal protocol A {
func b()
}
internal protocol A {
var b: Int
}
internal class A { deinit {} }
触发
enum A {}
final class B {}
internal struct C { let d = 5 }
public struct C { let d = 5 }
func a() {}
internal let a = 0
func b() {}
Explicit Enum Raw Value
标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
explicit_enum_raw_value |
否 |
否 |
idiomatic |
否 |
3.0.0 |
枚举类型最好指定明确的raw value
示例
非触发
enum Numbers {
case int(Int)
case short(Int16)
}
enum Numbers: Int {
case one = 1
case two = 2
}
enum Numbers: Double {
case one = 1.1
case two = 2.2
}
enum Numbers: String {
case one = "one"
case two = "two"
}
protocol Algebra {}
enum Numbers: Algebra {
case one
}
触发
enum Numbers: Int {
case one = 10, ↓two, three = 30
}
enum Numbers: NSInteger {
case ↓one
}
enum Numbers: String {
case ↓one
case ↓two
}
enum Numbers: String {
case ↓one, two = "two"
}
enum Numbers: Decimal {
case ↓one, ↓two
}
Explicit Init
标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
explicit init |
否 |
是 |
idiomatic |
否 |
3.0.0 |
应当避免直接使用.init()
示例
非触发
import Foundation;
class C: NSObject {
override init() {
super.init()
}
}
struct S {
let n: Int
};
extension S {
init() {
self.init(n: 1)
}
}
[1].flatMap(String.init)
[String.self].map { $0.init(1) }
[String.self].map { type in type.init(1) }
触发
[1].flatMap{String↓.init($0)}
[String.self].map { Type in Type↓.init(1) }
func foo() -> [String] {
return [1].flatMap { String↓.init($0) }
}
Explicit Self
标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
explicit self |
是 |
style |
style |
是 |
3.0.0 |
应当使用self.
调用自己的变量和方法
示例
非触发
struct A {
func f1() {}
func f2() {
self.f1()
}
}
struct A {
let p1: Int
func f1() {
_ = self.p1
}
}
触发
struct A {
func f1() {}
func f2() {
↓f1()
}
}
struct A {
let p1: Int
func f1() {
_ = ↓p1
}
}
补充
持保留意见、我们总是喜欢短小,表达力强的代码。满屏的self并不是我喜欢的。
Explicit Top Level ACL
标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
explicit_top_level_acl |
否 |
否 |
idiomatic |
否 |
3.0.0 |
顶级声明必须明确指定访问权限控制符
示例
非触发
internal enum A {}
public final class B {}
private struct C {}
internal enum A {
enum B {}
}
internal final class Foo {}
internal
class Foo {}
internal func a() {}
触发
enum A {}
final class B {}
struct C {}
func a() {}
internal let a = 0
func b() {}
Explict Type Interface
标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
explict type interface |
否 |
否 |
idiomatic |
否 |
3.0.0 |
属性最好声明类型。
示例
非触发
class Foo {
var myVar: Int? = 0
}
class Foo {
let myVar: Int? = 0
}
class Foo {
static var myVar: Int? = 0
}
class Foo {
class var myVar: Int? = 0
}
触发
class Foo {
↓var myVar = 0
}
class Foo {
↓let mylet = 0
}
class Foo {
↓static var myStaticVar = 0
}
class Foo {
↓class var myClassVar = 0
}
class Foo {
↓let myVar = Int(0)
}
class Foo {
↓let myVar = Set<Int>(0)
}
Extension Access Modifier
标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
extension_access_mofifier |
否 |
否 |
idiomatic |
否 |
3.0.0 |
推荐使用扩展访问修饰符。
示例
非触发
extension Foo: SomeProtocol {
public var bar: Int { return 1 }
}
extension Foo {
private var bar: Int { return 1 }
public var baz: Int { return 1 }
}
extension Foo {
private var bar: Int { return 1 }
public func baz() {}
}
extension Foo {
var bar: Int { return 1 }
var baz: Int { return 1 }
}
public extension Foo {
var bar: Int { return 1 }
var baz: Int { return 1 }
}
extension Foo {
private bar: Int { return 1 }
private baz: Int { return 1 }
}
extension Foo {
open bar: Int { return 1 }
open baz: Int { return 1 }
}
触发
↓extension Foo {
public var bar: Int { return 1 }
public var baz: Int { return 1 }
}
↓extension Foo {
public var bar: Int { return 1 }
public func baz() {}
}
public extension Foo {
public ↓func bar() {}
public ↓func baz() {}
}
补充
正例和反例有相同的地方、没能get到点子上。
Fallthrough
标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
fallthrough |
否 |
否 |
idomatic |
否 |
3.0.0 |
避免使用fallthough
示例
非触发
switch foo {
case .bar, .bar2, .bar3:
something()
}
触发
switch foo {
case .bar:
↓fallthrough
case .bar2:
something()
}
Fatal Error Message
标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
fatal_error_message |
否 |
否 |
idiomatic |
否 |
3.0.0 |
fatalError
方法最好提供message
示例
非触发
func foo() {
fatalError("Foo")
}
func foo() {
fatalError(x)
}
触发
func foo() {
↓fatalError("")
}
func foo() {
↓fatalError()
}
补充
fatalError
File Header
标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
file_header |
否 |
否 |
style |
否 |
3.0.0 |
头部描述应当和项目模式一致。SWIFTLINT_CURRENT_FILENAME占位符可以配置必须和禁止选项,这将被真实的文件名替换。
示例
非触发
let foo = "Copyright"
let foo = 2 // Copyright
let foo = 2
// Copyright
触发
// ↓Copyright
//
// ↓Copyright
//
// FileHeaderRule.swift
// SwiftLint
//
// Created by Marcelo Fabri on 27/11/16.
// ↓Copyright © 2016 Realm. All rights reserved.
//
补充
我能想到的适用场景就是在文件重命名之后,相应的文件顶部的注释也要一并改掉。
File Line Length
标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
file_length |
是 |
否 |
metrics |
否 |
3.0.0 |
文件不要超过太多行
示例
非触发
print("swiftlint")*400
触发
print("swiftlint")*401
补充
查看源码可知建议不超过400行,个人感觉不好控制,业务复杂可以多一些,业务简单少一些。关键是模块划分合理,层次结构分明。不必过分追求代码行数。仁者见仁,智者见智。
File Name
标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
file_name |
否 |
否 |
idiomatic |
否 |
3.0.0 |
文件名应当和文件中声明的类型或扩展一致(若存在)
First Where
标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
first_where |
否 |
否 |
performance |
否 |
3.0.0 |
在集合对象中优先使用.first(where:)
而不是.filter{}.first
示例
非触发
kinds.filter(excludingKinds.contains).isEmpty && kinds.first == .identifier
myList.first(where: { $0 % 2 == 0 })
match(pattern: pattern).filter { $0.first == .identifier }
(myList.filter { $0 == 1 }.suffix(2)).first
触发
↓myList.filter { $0 % 2 == 0 }.first
↓myList.filter({ $0 % 2 == 0 }).first
↓myList.map { $0 + 1 }.filter({ $0 % 2 == 0 }).first
↓myList.map { $0 + 1 }.filter({ $0 % 2 == 0 }).first?.something()
↓myList.filter(someFunction).first
↓myList.filter({ $0 % 2 == 0 })
.first
(↓myList.filter { $0 == 1 }).first
For Where
标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
for_where |
是 |
否 |
idiomatic |
否 |
3.0.0 |
where
修饰符优于在for
中使用if
示例
非触发
for user in users where user.id == 1 { }
for user in users {
if let id = user.id { }
}
for user in users {
if var id = user.id { }
}
for user in users {
if user.id == 1 { } else { }
}
for user in users {
if user.id == 1 {
} else if user.id == 2 { }
}
for user in users {
if user.id == 1 { }
print(user)
}
for user in users {
let id = user.id
if id == 1 { }
}
for user in users {
if user.id == 1 { }
return true
}
for user in users {
if user.id == 1 && user.age > 18 { }
}
for (index, value) in array.enumerated() {
if case .valueB(_) = value {
return index
}
}
触发
for user in users {
↓if user.id == 1 { return true }
}
Force Cast
标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
force_cast |
是 |
否 |
idiomatic |
否 |
3.0.0 |
应该避免强制转换
示例
非触发
NSNumber() as? Int
触发
NSNumber() ↓as! Int
Force Try
标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
force_try |
是 |
否 |
idiomatic |
否 |
3.0.0 |
应该避免强制try
示例
非触发
func a() throws {}; do { try a() } catch {}
触发
func a() throws {}; ↓try! a()
Force Unwrapping
标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
force_unwrapping |
否 |
否 |
idiomatic |
否 |
3.0.0 |
避免强制解包
示例
非触发
if let url = NSURL(string: query)
navigationController?.pushViewController(viewController, animated: true)
let s as! Test
try! canThrowErrors()
let object: Any!
@IBOutlet var constraints: [NSLayoutConstraint]!
setEditing(!editing, animated: true)
navigationController.setNavigationBarHidden(!navigationController.navigationBarHidden, animated: true)
if addedToPlaylist && (!self.selectedFilters.isEmpty || self.searchBar?.text?.isEmpty == false) {}
print("\(xVar)!")
var test = (!bar)
var a: [Int]!
private var myProperty: (Void -> Void)!
func foo(_ options: [AnyHashable: Any]!) {
func foo() -> [Int]!
func foo() -> [AnyHashable: Any]!
func foo() -> [Int]! { return [] }
触发
let url = NSURL(string: query)↓!
navigationController↓!.pushViewController(viewController, animated: true)
let unwrapped = optional↓!
return cell↓!
let url = NSURL(string: "http://www.google.com")↓!
let dict = ["Boooo": "👻"]func bla() -> String { return dict["Boooo"]↓! }
let dict = ["Boooo": "👻"]func bla() -> String { return dict["Boooo"]↓!.contains("B") }
let a = dict["abc"]↓!.contains("B")
dict["abc"]↓!.bar("B")
if dict["a"]↓!!!! {
var foo: [Bool]! = dict["abc"]↓!
context("abc") {
var foo: [Bool]! = dict["abc"]↓!
}
open var computed: String { return foo.bar↓! }
Function Body Length
标识符 |
默认开启 |
支持自动更正 |
类别 |
分析仪 |
最低swift编译版本 |
function_body_length |
是 |
否 |
metrics |
否 |
3.0.0 |
函数主体不要过长