Skip to content

Commit

Permalink
Merge pull request #2169 from marcelofabri/unavailable_function
Browse files Browse the repository at this point in the history
Add unavailable_function opt-in rule
  • Loading branch information
marcelofabri authored Apr 25, 2018
2 parents f61b1cf + 428379c commit dcbc20e
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 0 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
61 changes: 61 additions & 0 deletions Rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions Source/SwiftLintFramework/Models/MasterRuleList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ public let masterRuleList = RuleList(rules: [
TrailingWhitespaceRule.self,
TypeBodyLengthRule.self,
TypeNameRule.self,
UnavailableFunctionRule.self,
UnneededBreakInSwitchRule.self,
UnneededParenthesesInClosureArgumentRule.self,
UntypedErrorInCatchRule.self,
Expand Down
100 changes: 100 additions & 0 deletions Source/SwiftLintFramework/Rules/UnavailableFunctionRule.swift
Original file line number Diff line number Diff line change
@@ -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")
}
}
}
4 changes: 4 additions & 0 deletions SwiftLint.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -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>"; };
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down
1 change: 1 addition & 0 deletions Tests/LinuxMain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,7 @@ extension RulesTests {
("testTrailingSemicolon", testTrailingSemicolon),
("testTrailingWhitespace", testTrailingWhitespace),
("testTypeBodyLength", testTypeBodyLength),
("testUnavailableFunction", testUnavailableFunction),
("testUnneededBreakInSwitch", testUnneededBreakInSwitch),
("testUnneededParenthesesInClosureArgument", testUnneededParenthesesInClosureArgument),
("testUntypedErrorInCatch", testUntypedErrorInCatch),
Expand Down
4 changes: 4 additions & 0 deletions Tests/SwiftLintFrameworkTests/RulesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,10 @@ class RulesTests: XCTestCase {
verifyRule(TypeBodyLengthRule.description)
}

func testUnavailableFunction() {
verifyRule(UnavailableFunctionRule.description)
}

func testUnneededBreakInSwitch() {
verifyRule(UnneededBreakInSwitchRule.description)
}
Expand Down

0 comments on commit dcbc20e

Please sign in to comment.