mirror of
https://github.com/realm/SwiftLint.git
synced 2026-06-06 20:18:40 +00:00
Merge pull request #2169 from marcelofabri/unavailable_function
Add unavailable_function opt-in rule
This commit is contained in:
@@ -11,6 +11,12 @@
|
||||
[Marcelo Fabri](https://github.com/marcelofabri)
|
||||
[#898](https://github.com/realm/SwiftLint/issues/898)
|
||||
|
||||
* Add `unavailable_function` opt-in rule to validate that functions that are
|
||||
currently unimplemented (using a placeholder `fatalError`) are marked with
|
||||
`@available(*, unavailable)`.
|
||||
[Marcelo Fabri](https://github.com/marcelofabri)
|
||||
[#2127](https://github.com/realm/SwiftLint/issues/2127)
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
* None.
|
||||
|
||||
@@ -115,6 +115,7 @@
|
||||
* [Trailing Whitespace](#trailing-whitespace)
|
||||
* [Type Body Length](#type-body-length)
|
||||
* [Type Name](#type-name)
|
||||
* [Unavailable Function](#unavailable-function)
|
||||
* [Unneeded Break in Switch](#unneeded-break-in-switch)
|
||||
* [Unneeded Parentheses in Closure Argument](#unneeded-parentheses-in-closure-argument)
|
||||
* [Untyped Error in Catch](#untyped-error-in-catch)
|
||||
@@ -17389,6 +17390,66 @@ protocol Foo {
|
||||
|
||||
|
||||
|
||||
## Unavailable Function
|
||||
|
||||
Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version
|
||||
--- | --- | --- | --- | ---
|
||||
`unavailable_function` | Disabled | No | idiomatic | 4.1.0
|
||||
|
||||
Unimplemented functions should be marked as unavailable.
|
||||
|
||||
### Examples
|
||||
|
||||
<details>
|
||||
<summary>Non Triggering Examples</summary>
|
||||
|
||||
```swift
|
||||
class ViewController: UIViewController {
|
||||
@available(*, unavailable)
|
||||
public required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
func jsonValue(_ jsonString: String) -> NSObject {
|
||||
let data = jsonString.data(using: .utf8)!
|
||||
let result = try! JSONSerialization.jsonObject(with: data, options: [])
|
||||
if let dict = (result as? [String: Any])?.bridge() {
|
||||
return dict
|
||||
} else if let array = (result as? [Any])?.bridge() {
|
||||
return array
|
||||
}
|
||||
fatalError()
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>Triggering Examples</summary>
|
||||
|
||||
```swift
|
||||
class ViewController: UIViewController {
|
||||
public required ↓init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
class ViewController: UIViewController {
|
||||
public required ↓init?(coder aDecoder: NSCoder) {
|
||||
let reason = "init(coder:) has not been implemented"
|
||||
fatalError(reason)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
## Unneeded Break in Switch
|
||||
|
||||
Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version
|
||||
|
||||
@@ -123,6 +123,7 @@ public let masterRuleList = RuleList(rules: [
|
||||
TrailingWhitespaceRule.self,
|
||||
TypeBodyLengthRule.self,
|
||||
TypeNameRule.self,
|
||||
UnavailableFunctionRule.self,
|
||||
UnneededBreakInSwitchRule.self,
|
||||
UnneededParenthesesInClosureArgumentRule.self,
|
||||
UntypedErrorInCatchRule.self,
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
//
|
||||
// UnavailableFunctionRule.swift
|
||||
// SwiftLint
|
||||
//
|
||||
// Created by Marcelo Fabri on 04/09/18.
|
||||
// Copyright © 2018 Realm. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SourceKittenFramework
|
||||
|
||||
public struct UnavailableFunctionRule: ASTRule, ConfigurationProviderRule, OptInRule {
|
||||
public var configuration = SeverityConfiguration(.warning)
|
||||
|
||||
public init() {}
|
||||
|
||||
public static let description = RuleDescription(
|
||||
identifier: "unavailable_function",
|
||||
name: "Unavailable Function",
|
||||
description: "Unimplemented functions should be marked as unavailable.",
|
||||
kind: .idiomatic,
|
||||
minSwiftVersion: .fourDotOne,
|
||||
nonTriggeringExamples: [
|
||||
"""
|
||||
class ViewController: UIViewController {
|
||||
@available(*, unavailable)
|
||||
public required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""
|
||||
func jsonValue(_ jsonString: String) -> NSObject {
|
||||
let data = jsonString.data(using: .utf8)!
|
||||
let result = try! JSONSerialization.jsonObject(with: data, options: [])
|
||||
if let dict = (result as? [String: Any])?.bridge() {
|
||||
return dict
|
||||
} else if let array = (result as? [Any])?.bridge() {
|
||||
return array
|
||||
}
|
||||
fatalError()
|
||||
}
|
||||
"""
|
||||
],
|
||||
triggeringExamples: [
|
||||
"""
|
||||
class ViewController: UIViewController {
|
||||
public required ↓init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""
|
||||
class ViewController: UIViewController {
|
||||
public required ↓init?(coder aDecoder: NSCoder) {
|
||||
let reason = "init(coder:) has not been implemented"
|
||||
fatalError(reason)
|
||||
}
|
||||
}
|
||||
"""
|
||||
]
|
||||
)
|
||||
|
||||
public func validate(file: File, kind: SwiftDeclarationKind,
|
||||
dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
|
||||
guard SwiftDeclarationKind.functionKinds.contains(kind) else {
|
||||
return []
|
||||
}
|
||||
|
||||
let containsFatalError = dictionary.substructure.contains { dict -> Bool in
|
||||
return dict.kind.flatMap(SwiftExpressionKind.init(rawValue:)) == .call && dict.name == "fatalError"
|
||||
}
|
||||
|
||||
guard let offset = dictionary.offset, containsFatalError,
|
||||
!isFunctionUnavailable(file: file, dictionary: dictionary),
|
||||
let bodyOffset = dictionary.bodyOffset, let bodyLength = dictionary.bodyLength,
|
||||
let range = file.contents.bridge().byteRangeToNSRange(start: bodyOffset, length: bodyLength),
|
||||
file.match(pattern: "\\breturn\\b", with: [.keyword], range: range).isEmpty else {
|
||||
return []
|
||||
}
|
||||
|
||||
return [
|
||||
StyleViolation(ruleDescription: type(of: self).description,
|
||||
severity: configuration.severity,
|
||||
location: Location(file: file, byteOffset: offset))
|
||||
]
|
||||
}
|
||||
|
||||
private func isFunctionUnavailable(file: File, dictionary: [String: SourceKitRepresentable]) -> Bool {
|
||||
return dictionary.swiftAttributes.contains { dict -> Bool in
|
||||
guard dict.attribute.flatMap(SwiftDeclarationAttributeKind.init(rawValue:)) == .available,
|
||||
let offset = dict.offset, let length = dict.length,
|
||||
let contents = file.contents.bridge().substringWithByteRange(start: offset, length: length) else {
|
||||
return false
|
||||
}
|
||||
|
||||
return contents.contains("unavailable")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -240,6 +240,7 @@
|
||||
D4DABFD91E2C59BC009617B6 /* NotificationCenterDetachmentRuleExamples.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DABFD81E2C59BC009617B6 /* NotificationCenterDetachmentRuleExamples.swift */; };
|
||||
D4DAE8BC1DE14E8F00B0AE7A /* NimbleOperatorRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DAE8BB1DE14E8F00B0AE7A /* NimbleOperatorRule.swift */; };
|
||||
D4DB92251E628898005DE9C1 /* TodoRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DB92241E628898005DE9C1 /* TodoRuleTests.swift */; };
|
||||
D4DE9133207B4750000FFAA8 /* UnavailableFunctionRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DE9131207B4731000FFAA8 /* UnavailableFunctionRule.swift */; };
|
||||
D4E2BA851F6CD77B00E8E184 /* ArrayInitRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4E2BA841F6CD77B00E8E184 /* ArrayInitRule.swift */; };
|
||||
D4EA77C81F817FD200C315FB /* UnneededBreakInSwitchRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4EA77C71F817FD200C315FB /* UnneededBreakInSwitchRule.swift */; };
|
||||
D4EA77CA1F81FACC00C315FB /* LiteralExpressionEndIdentationRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4EA77C91F81FACC00C315FB /* LiteralExpressionEndIdentationRule.swift */; };
|
||||
@@ -622,6 +623,7 @@
|
||||
D4DABFD81E2C59BC009617B6 /* NotificationCenterDetachmentRuleExamples.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationCenterDetachmentRuleExamples.swift; sourceTree = "<group>"; };
|
||||
D4DAE8BB1DE14E8F00B0AE7A /* NimbleOperatorRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NimbleOperatorRule.swift; sourceTree = "<group>"; };
|
||||
D4DB92241E628898005DE9C1 /* TodoRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TodoRuleTests.swift; sourceTree = "<group>"; };
|
||||
D4DE9131207B4731000FFAA8 /* UnavailableFunctionRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnavailableFunctionRule.swift; sourceTree = "<group>"; };
|
||||
D4E2BA841F6CD77B00E8E184 /* ArrayInitRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayInitRule.swift; sourceTree = "<group>"; };
|
||||
D4EA77C71F817FD200C315FB /* UnneededBreakInSwitchRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnneededBreakInSwitchRule.swift; sourceTree = "<group>"; };
|
||||
D4EA77C91F81FACC00C315FB /* LiteralExpressionEndIdentationRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiteralExpressionEndIdentationRule.swift; sourceTree = "<group>"; };
|
||||
@@ -1179,6 +1181,7 @@
|
||||
E88DEA8D1B0999CD00A66CB0 /* TypeBodyLengthRule.swift */,
|
||||
E88DEA911B099B1F00A66CB0 /* TypeNameRule.swift */,
|
||||
D4130D981E16CC1300242361 /* TypeNameRuleExamples.swift */,
|
||||
D4DE9131207B4731000FFAA8 /* UnavailableFunctionRule.swift */,
|
||||
D4EA77C71F817FD200C315FB /* UnneededBreakInSwitchRule.swift */,
|
||||
D46A317E1F1CEDCD00AF914A /* UnneededParenthesesInClosureArgumentRule.swift */,
|
||||
181D9E162038343D001F6887 /* UntypedErrorInCatchRule.swift */,
|
||||
@@ -1648,6 +1651,7 @@
|
||||
3BCC04D21C4F56D3006073C3 /* NameConfiguration.swift in Sources */,
|
||||
D4C27BFE1E12D53F00DF713E /* Version.swift in Sources */,
|
||||
B2902A0E1D6681F700BFCCF7 /* PrivateUnitTestConfiguration.swift in Sources */,
|
||||
D4DE9133207B4750000FFAA8 /* UnavailableFunctionRule.swift in Sources */,
|
||||
D47A510E1DB29EEB00A4CC21 /* SwitchCaseOnNewlineRule.swift in Sources */,
|
||||
D462021F1E15F52D0027AAD1 /* NumberSeparatorRuleExamples.swift in Sources */,
|
||||
D4DA1DF41E17511D0037413D /* CompilerProtocolInitRule.swift in Sources */,
|
||||
|
||||
@@ -513,6 +513,7 @@ extension RulesTests {
|
||||
("testTrailingSemicolon", testTrailingSemicolon),
|
||||
("testTrailingWhitespace", testTrailingWhitespace),
|
||||
("testTypeBodyLength", testTypeBodyLength),
|
||||
("testUnavailableFunction", testUnavailableFunction),
|
||||
("testUnneededBreakInSwitch", testUnneededBreakInSwitch),
|
||||
("testUnneededParenthesesInClosureArgument", testUnneededParenthesesInClosureArgument),
|
||||
("testUntypedErrorInCatch", testUntypedErrorInCatch),
|
||||
|
||||
@@ -433,6 +433,10 @@ class RulesTests: XCTestCase {
|
||||
verifyRule(TypeBodyLengthRule.description)
|
||||
}
|
||||
|
||||
func testUnavailableFunction() {
|
||||
verifyRule(UnavailableFunctionRule.description)
|
||||
}
|
||||
|
||||
func testUnneededBreakInSwitch() {
|
||||
verifyRule(UnneededBreakInSwitchRule.description)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user