Skip to content

Commit

Permalink
Add NimbleOperatorRule
Browse files Browse the repository at this point in the history
Fixes #881
  • Loading branch information
marcelofabri authored and jpsim committed Nov 25, 2016
1 parent 58eb0f6 commit 0c5ff7f
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 0 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Expand Up @@ -26,6 +26,13 @@
[JP Simard](https://github.com/jpsim)
[#861](https://github.com/realm/SwiftLint/issues/861)

* Add `NimbleOperatorRule` opt-in rule that enforces using
[operator overloads](https://github.com/Quick/Nimble/#operator-overloads)
instead of free matcher functions when using
[Nimble](https://github.com/Quick/Nimble).
[Marcelo Fabri](https://github.com/marcelofabri)
[#881](https://github.com/realm/SwiftLint/issues/881)

##### Bug Fixes


Expand Down
1 change: 1 addition & 0 deletions Source/SwiftLintFramework/Models/MasterRuleList.swift
Expand Up @@ -67,6 +67,7 @@ public let masterRuleList = RuleList(rules:
MarkRule.self,
MissingDocsRule.self,
NestingRule.self,
NimbleOperatorRule.self,
OpeningBraceRule.self,
OperatorFunctionWhitespaceRule.self,
OverriddenSuperCallRule.self,
Expand Down
63 changes: 63 additions & 0 deletions Source/SwiftLintFramework/Rules/NimbleOperatorRule.swift
@@ -0,0 +1,63 @@
//
// NimbleOperatorRule.swift
// SwiftLint
//
// Created by Marcelo Fabri on 20/11/16.
// Copyright © 2016 Realm. All rights reserved.
//

import SourceKittenFramework

public struct NimbleOperatorRule: ConfigurationProviderRule, OptInRule {
public var configuration = SeverityConfiguration(.Warning)

public init() {}

public static let description = RuleDescription(
identifier: "nimble_operator",
name: "Nimble Operator",
description: "Prefer Nimble operator overloads over free matcher functions.",
nonTriggeringExamples: [
"expect(seagull.squawk) != \"Hi!\"\n",
"expect(\"Hi!\") == \"Hi!\"\n",
"expect(10) > 2\n",
"expect(10) >= 10\n",
"expect(10) < 11\n",
"expect(10) <= 10\n",
"expect(x) === x",
"expect(10) == 10",
"expect(object.asyncFunction()).toEventually(equal(1))\n",
"expect(actual).to(haveCount(expected))\n"
],
triggeringExamples: [
"↓expect(seagull.squawk).toNot(equal(\"Hi\"))\n",
"↓expect(12).toNot(equal(10))\n",
"↓expect(10).to(equal(10))\n",
"↓expect(10).to(beGreaterThan(8))\n",
"↓expect(10).to(beGreaterThanOrEqualTo(10))\n",
"↓expect(10).to(beLessThan(11))\n",
"↓expect(10).to(beLessThanOrEqualTo(10))\n",
"↓expect(x).to(beIdenticalTo(x))\n",
"expect(10) > 2\n ↓expect(10).to(beGreaterThan(2))\n",
]
)

public func validateFile(file: File) -> [StyleViolation] {
let operators = ["equal", "beIdenticalTo", "beGreaterThan",
"beGreaterThanOrEqualTo", "beLessThan", "beLessThanOrEqualTo"]
let operatorsPattern = "(" + operators.joinWithSeparator("|") + ")"
let pattern = "expect\\((.(?!expect\\())+?\\)\\.to(Not)?\\(\(operatorsPattern)\\("
let excludingKinds = SyntaxKind.commentKinds()

let matches = file.matchPattern(pattern).filter {
// excluding comment kinds and making sure first token (`expect`) is an identifier
$0.1.filter(excludingKinds.contains).isEmpty && $0.1.first == .Identifier
}

return matches.map {
StyleViolation(ruleDescription: self.dynamicType.description,
severity: configuration.severity,
location: Location(file: file, byteOffset: $0.0.location))
}
}
}
4 changes: 4 additions & 0 deletions SwiftLint.xcodeproj/project.pbxproj
Expand Up @@ -77,6 +77,7 @@
D44254271DB9C15C00492EA4 /* SyntacticSugarRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44254251DB9C12300492EA4 /* SyntacticSugarRule.swift */; };
D44AD2761C0AA5350048F7B0 /* LegacyConstructorRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44AD2741C0AA3730048F7B0 /* LegacyConstructorRule.swift */; };
D47A510E1DB29EEB00A4CC21 /* SwitchCaseOnNewlineRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47A510D1DB29EEB00A4CC21 /* SwitchCaseOnNewlineRule.swift */; };
D4DAE8BC1DE14E8F00B0AE7A /* NimbleOperatorRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DAE8BB1DE14E8F00B0AE7A /* NimbleOperatorRule.swift */; };
DAD3BE4A1D6ECD9500660239 /* PrivateOutletRuleConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAD3BE491D6ECD9500660239 /* PrivateOutletRuleConfiguration.swift */; };
E57B23C11B1D8BF000DEA512 /* ReturnArrowWhitespaceRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E57B23C01B1D8BF000DEA512 /* ReturnArrowWhitespaceRule.swift */; };
E802ED001C56A56000A35AE1 /* Benchmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = E802ECFF1C56A56000A35AE1 /* Benchmark.swift */; };
Expand Down Expand Up @@ -276,6 +277,7 @@
D44254251DB9C12300492EA4 /* SyntacticSugarRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyntacticSugarRule.swift; sourceTree = "<group>"; };
D44AD2741C0AA3730048F7B0 /* LegacyConstructorRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyConstructorRule.swift; sourceTree = "<group>"; };
D47A510D1DB29EEB00A4CC21 /* SwitchCaseOnNewlineRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwitchCaseOnNewlineRule.swift; sourceTree = "<group>"; };
D4DAE8BB1DE14E8F00B0AE7A /* NimbleOperatorRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NimbleOperatorRule.swift; sourceTree = "<group>"; };
DAD3BE491D6ECD9500660239 /* PrivateOutletRuleConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivateOutletRuleConfiguration.swift; sourceTree = "<group>"; };
E57B23C01B1D8BF000DEA512 /* ReturnArrowWhitespaceRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReturnArrowWhitespaceRule.swift; sourceTree = "<group>"; };
E5A167C81B25A0B000CF2D03 /* OperatorFunctionWhitespaceRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperatorFunctionWhitespaceRule.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -644,6 +646,7 @@
856651A61D6B395F005E6B29 /* MarkRule.swift */,
E849FF271BF9481A009AE999 /* MissingDocsRule.swift */,
E88DEA951B099CF200A66CB0 /* NestingRule.swift */,
D4DAE8BB1DE14E8F00B0AE7A /* NimbleOperatorRule.swift */,
692B1EB11BD7E00F00EAABFF /* OpeningBraceRule.swift */,
E5A167C81B25A0B000CF2D03 /* OperatorFunctionWhitespaceRule.swift */,
78F032441D7C877800BE709A /* OverriddenSuperCallRule.swift */,
Expand Down Expand Up @@ -971,6 +974,7 @@
E88198581BEA956C00333A11 /* FunctionBodyLengthRule.swift in Sources */,
E88DEA751B09852000A66CB0 /* File+SwiftLint.swift in Sources */,
3BCC04D11C4F56D3006073C3 /* SeverityLevelsConfiguration.swift in Sources */,
D4DAE8BC1DE14E8F00B0AE7A /* NimbleOperatorRule.swift in Sources */,
6CB514E91C760C6900FA02C4 /* Structure+SwiftLint.swift in Sources */,
E86396C51BADAC15002C9E88 /* XcodeReporter.swift in Sources */,
094385011D5D2894009168CF /* WeakDelegateRule.swift in Sources */,
Expand Down
4 changes: 4 additions & 0 deletions Tests/SwiftLintFramework/RulesTests.swift
Expand Up @@ -181,6 +181,10 @@ class RulesTests: XCTestCase {
verifyRule(NestingRule.description)
}

func testNimbleOperator() {
verifyRule(NimbleOperatorRule.description)
}

func testVerticalWhitespace() {
verifyRule(VerticalWhitespaceRule.description)
}
Expand Down

0 comments on commit 0c5ff7f

Please sign in to comment.