Skip to content

Commit

Permalink
Merge pull request realm#2396 from marcelofabri/static_operator
Browse files Browse the repository at this point in the history
Add static_operator opt-in rule
  • Loading branch information
marcelofabri authored Sep 14, 2018
2 parents 60f98ec + b92b236 commit bf85625
Show file tree
Hide file tree
Showing 7 changed files with 216 additions and 0 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@
[Dalton Claybrook](https://github.com/daltonclaybrook)
[#2326](https://github.com/realm/SwiftLint/issues/2326)

* Add `static_operator` opt-in rule to enforce that operators are declared as
static functions instead of free functions.
[Marcelo Fabri](https://github.com/marcelofabri)
[#2395](https://github.com/realm/SwiftLint/issues/2395)

#### Bug Fixes

* Fix `comma` rule false positives on object literals (for example, images).
Expand Down
92 changes: 92 additions & 0 deletions Rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
* [Min or Max over Sorted First or Last](#min-or-max-over-sorted-first-or-last)
* [Sorted Imports](#sorted-imports)
* [Statement Position](#statement-position)
* [Static Operator](#static-operator)
* [Strict fileprivate](#strict-fileprivate)
* [Superfluous Disable Command](#superfluous-disable-command)
* [Switch and Case Statement Alignment](#switch-and-case-statement-alignment)
Expand Down Expand Up @@ -15871,6 +15872,97 @@ catch {
## Static Operator
Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version
--- | --- | --- | --- | --- | ---
`static_operator` | Disabled | No | idiomatic | No | 3.0.0
Operators should be declared as static functions, not free functions.
### Examples
<details>
<summary>Non Triggering Examples</summary>
```swift
class A: Equatable {
static func == (lhs: A, rhs: A) -> Bool {
return false
}
```
```swift
class A<T>: Equatable {
static func == <T>(lhs: A<T>, rhs: A<T>) -> Bool {
return false
}
```
```swift
public extension Array where Element == Rule {
static func == (lhs: Array, rhs: Array) -> Bool {
if lhs.count != rhs.count { return false }
return !zip(lhs, rhs).contains { !$0.0.isEqualTo($0.1) }
}
}
```
```swift
private extension Optional where Wrapped: Comparable {
static func < (lhs: Optional, rhs: Optional) -> Bool {
switch (lhs, rhs) {
case let (lhs?, rhs?):
return lhs < rhs
case (nil, _?):
return true
default:
return false
}
}
}
```
</details>
<details>
<summary>Triggering Examples</summary>
```swift
↓func == (lhs: A, rhs: A) -> Bool {
return false
}
```
```swift
↓func == <T>(lhs: A<T>, rhs: A<T>) -> Bool {
return false
}
```
```swift
↓func == (lhs: [Rule], rhs: [Rule]) -> Bool {
if lhs.count != rhs.count { return false }
return !zip(lhs, rhs).contains { !$0.0.isEqualTo($0.1) }
}
```
```swift
private ↓func < <T: Comparable>(lhs: T?, rhs: T?) -> Bool {
switch (lhs, rhs) {
case let (lhs?, rhs?):
return lhs < rhs
case (nil, _?):
return true
default:
return false
}
}
```
</details>
## Strict fileprivate
Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | 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 @@ -119,6 +119,7 @@ public let masterRuleList = RuleList(rules: [
SortedFirstLastRule.self,
SortedImportsRule.self,
StatementPositionRule.self,
StaticOperatorRule.self,
StrictFilePrivateRule.self,
SuperfluousDisableCommandRule.self,
SwitchCaseAlignmentRule.self,
Expand Down
101 changes: 101 additions & 0 deletions Source/SwiftLintFramework/Rules/Idiomatic/StaticOperatorRule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import Foundation
import SourceKittenFramework

public struct StaticOperatorRule: ASTRule, ConfigurationProviderRule, OptInRule, AutomaticTestableRule {
public var configuration = SeverityConfiguration(.warning)

public init() {}

public static let description = RuleDescription(
identifier: "static_operator",
name: "Static Operator",
description: "Operators should be declared as static functions, not free functions.",
kind: .idiomatic,
nonTriggeringExamples: [
"""
class A: Equatable {
static func == (lhs: A, rhs: A) -> Bool {
return false
}
""",
"""
class A<T>: Equatable {
static func == <T>(lhs: A<T>, rhs: A<T>) -> Bool {
return false
}
""",
"""
public extension Array where Element == Rule {
static func == (lhs: Array, rhs: Array) -> Bool {
if lhs.count != rhs.count { return false }
return !zip(lhs, rhs).contains { !$0.0.isEqualTo($0.1) }
}
}
""",
"""
private extension Optional where Wrapped: Comparable {
static func < (lhs: Optional, rhs: Optional) -> Bool {
switch (lhs, rhs) {
case let (lhs?, rhs?):
return lhs < rhs
case (nil, _?):
return true
default:
return false
}
}
}
"""
],
triggeringExamples: [
"""
↓func == (lhs: A, rhs: A) -> Bool {
return false
}
""",
"""
↓func == <T>(lhs: A<T>, rhs: A<T>) -> Bool {
return false
}
""",
"""
↓func == (lhs: [Rule], rhs: [Rule]) -> Bool {
if lhs.count != rhs.count { return false }
return !zip(lhs, rhs).contains { !$0.0.isEqualTo($0.1) }
}
""",
"""
private ↓func < <T: Comparable>(lhs: T?, rhs: T?) -> Bool {
switch (lhs, rhs) {
case let (lhs?, rhs?):
return lhs < rhs
case (nil, _?):
return true
default:
return false
}
}
"""
]
)

public func validate(file: File, kind: SwiftDeclarationKind,
dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
guard kind == .functionFree,
let offset = dictionary.offset,
let name = dictionary.name?.split(separator: "(").first.flatMap(String.init) else {
return []
}

let characterSet = CharacterSet(charactersIn: name)
guard characterSet.isDisjoint(with: .alphanumerics) else {
return []
}

return [
StyleViolation(ruleDescription: type(of: self).description,
severity: configuration.severity,
location: Location(file: file, byteOffset: offset))
]
}
}
4 changes: 4 additions & 0 deletions SwiftLint.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@
D4CA758F1E2DEEA500A40E8A /* NumberSeparatorRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4CA758E1E2DEEA500A40E8A /* NumberSeparatorRuleTests.swift */; };
D4CFC5D2209EC95A00668488 /* FunctionDefaultParameterAtEndRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4CFC5D1209EC95A00668488 /* FunctionDefaultParameterAtEndRule.swift */; };
D4D1B9BB1EAC2C910028BE6A /* AccessControlLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4D1B9B91EAC2C870028BE6A /* AccessControlLevel.swift */; };
D4D383852145F550000235BD /* StaticOperatorRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4D383842145F550000235BD /* StaticOperatorRule.swift */; };
D4D5A5FF1E1F3A1C00D15E0C /* ShorthandOperatorRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4D5A5FE1E1F3A1C00D15E0C /* ShorthandOperatorRule.swift */; };
D4DA1DF41E17511D0037413D /* CompilerProtocolInitRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DA1DF31E17511D0037413D /* CompilerProtocolInitRule.swift */; };
D4DA1DFA1E18D6200037413D /* LargeTupleRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DA1DF91E18D6200037413D /* LargeTupleRule.swift */; };
Expand Down Expand Up @@ -710,6 +711,7 @@
D4CA758E1E2DEEA500A40E8A /* NumberSeparatorRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumberSeparatorRuleTests.swift; sourceTree = "<group>"; };
D4CFC5D1209EC95A00668488 /* FunctionDefaultParameterAtEndRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FunctionDefaultParameterAtEndRule.swift; sourceTree = "<group>"; };
D4D1B9B91EAC2C870028BE6A /* AccessControlLevel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessControlLevel.swift; sourceTree = "<group>"; };
D4D383842145F550000235BD /* StaticOperatorRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticOperatorRule.swift; sourceTree = "<group>"; };
D4D5A5FE1E1F3A1C00D15E0C /* ShorthandOperatorRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShorthandOperatorRule.swift; sourceTree = "<group>"; };
D4DA1DF31E17511D0037413D /* CompilerProtocolInitRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompilerProtocolInitRule.swift; sourceTree = "<group>"; };
D4DA1DF91E18D6200037413D /* LargeTupleRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LargeTupleRule.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1120,6 +1122,7 @@
D41E7E0A1DF9DABB0065259A /* RedundantStringEnumValueRule.swift */,
6208ED4E20C297AC004E78D1 /* RedundantTypeAnnotationRule.swift */,
D4B022B11E10B613007E5297 /* RedundantVoidReturnRule.swift */,
D4D383842145F550000235BD /* StaticOperatorRule.swift */,
D42B45D81F0AF5E30086B683 /* StrictFilePrivateRule.swift */,
D44254251DB9C12300492EA4 /* SyntacticSugarRule.swift */,
7551DF6C21382C9A00AA1F4D /* ToggleBoolRule.swift */,
Expand Down Expand Up @@ -1835,6 +1838,7 @@
E889D8C71F1D357B00058332 /* Configuration+Merging.swift in Sources */,
D44254271DB9C15C00492EA4 /* SyntacticSugarRule.swift in Sources */,
D4EA77C81F817FD200C315FB /* UnneededBreakInSwitchRule.swift in Sources */,
D4D383852145F550000235BD /* StaticOperatorRule.swift in Sources */,
006204DC1E1E492F00FFFBE1 /* VerticalWhitespaceConfiguration.swift in Sources */,
E88198441BEA93D200333A11 /* ColonRule.swift in Sources */,
623675B21F962FC4009BE6F3 /* QuickDiscouragedPendingTestRuleExamples.swift in Sources */,
Expand Down
7 changes: 7 additions & 0 deletions Tests/LinuxMain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1077,6 +1077,12 @@ extension StatementPositionRuleTests {
]
}

extension StaticOperatorRuleTests {
static var allTests: [(String, (StaticOperatorRuleTests) -> () throws -> Void)] = [
("testWithDefaultConfiguration", testWithDefaultConfiguration)
]
}

extension StrictFilePrivateRuleTests {
static var allTests: [(String, (StrictFilePrivateRuleTests) -> () throws -> Void)] = [
("testWithDefaultConfiguration", testWithDefaultConfiguration)
Expand Down Expand Up @@ -1415,6 +1421,7 @@ XCTMain([
testCase(SortedImportsRuleTests.allTests),
testCase(SourceKitCrashTests.allTests),
testCase(StatementPositionRuleTests.allTests),
testCase(StaticOperatorRuleTests.allTests),
testCase(StrictFilePrivateRuleTests.allTests),
testCase(SwitchCaseAlignmentRuleTests.allTests),
testCase(SwitchCaseOnNewlineRuleTests.allTests),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,12 @@ class SortedImportsRuleTests: XCTestCase {
}
}

class StaticOperatorRuleTests: XCTestCase {
func testWithDefaultConfiguration() {
verifyRule(StaticOperatorRule.description)
}
}

class StrictFilePrivateRuleTests: XCTestCase {
func testWithDefaultConfiguration() {
verifyRule(StrictFilePrivateRule.description)
Expand Down

0 comments on commit bf85625

Please sign in to comment.