diff --git a/CHANGELOG.md b/CHANGELOG.md
index a32b74b58a..265d825810 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,11 @@
##### Enhancements
+* Added `OperatorFunctionWhitespaceRule` to make sure that
+ you use whitespace around operators when defining them.
+ [Akira Hirakawa](https://github.com/akirahrkw)
+ [#60](https://github.com/realm/SwiftLint/issues/60)
+
* Added `ReturnArrowWhitespaceRule` to make sure that
you have 1 space before return arrow and return type.
[Akira Hirakawa](https://github.com/akirahrkw)
diff --git a/Source/SwiftLintFramework/Linter.swift b/Source/SwiftLintFramework/Linter.swift
index 80c0e52c5e..e04e60c532 100644
--- a/Source/SwiftLintFramework/Linter.swift
+++ b/Source/SwiftLintFramework/Linter.swift
@@ -19,6 +19,7 @@ public struct Linter {
TrailingWhitespaceRule(),
ReturnArrowWhitespaceRule(),
TrailingNewlineRule(),
+ OperatorFunctionWhitespaceRule(),
ForceCastRule(),
FileLengthRule(),
TodoRule(),
diff --git a/Source/SwiftLintFramework/Location.swift b/Source/SwiftLintFramework/Location.swift
index 3640c04bcd..9cb786dce2 100644
--- a/Source/SwiftLintFramework/Location.swift
+++ b/Source/SwiftLintFramework/Location.swift
@@ -48,7 +48,7 @@ Returns true if `lhs` Location is equal to `rhs` Location.
:returns: True if `lhs` Location is equal to `rhs` Location.
*/
-public func ==(lhs: Location, rhs: Location) -> Bool {
+public func == (lhs: Location, rhs: Location) -> Bool {
return lhs.file == rhs.file &&
lhs.line == rhs.line &&
lhs.character == rhs.character
diff --git a/Source/SwiftLintFramework/Rules/OperatorFunctionWhitespaceRule.swift b/Source/SwiftLintFramework/Rules/OperatorFunctionWhitespaceRule.swift
new file mode 100644
index 0000000000..931c9a52fe
--- /dev/null
+++ b/Source/SwiftLintFramework/Rules/OperatorFunctionWhitespaceRule.swift
@@ -0,0 +1,87 @@
+//
+// OperatorWhitespaceRule.swift
+// SwiftLint
+//
+// Created by Akira Hirakawa on 8/6/15.
+// Copyright (c) 2015 Realm. All rights reserved.
+//
+
+import SourceKittenFramework
+import SwiftXPC
+
+public struct OperatorFunctionWhitespaceRule: ASTRule {
+ public init() {}
+
+ public let identifier = "operator_whitespace"
+
+ public func validateFile(file: File) -> [StyleViolation] {
+ return validateFile(file, dictionary: file.structure.dictionary)
+ }
+
+ public func validateFile(file: File, dictionary: XPCDictionary) -> [StyleViolation] {
+ return (dictionary["key.substructure"] as? XPCArray ?? []).flatMap { subItem in
+ var violations = [StyleViolation]()
+ if let subDict = subItem as? XPCDictionary,
+ let kindString = subDict["key.kind"] as? String,
+ let kind = flatMap(kindString, { SwiftDeclarationKind(rawValue: $0) }) {
+ violations.extend(validateFile(file, dictionary: subDict))
+ violations.extend(validateFile(file, kind: kind, dictionary: subDict))
+ }
+ return violations
+ }
+ }
+
+ public func validateFile(file: File,
+ kind: SwiftDeclarationKind,
+ dictionary: XPCDictionary) -> [StyleViolation] {
+ let functionKinds: [SwiftDeclarationKind] = [
+ .FunctionFree,
+ ]
+ if !contains(functionKinds, kind) {
+ return []
+ }
+ var violations = [StyleViolation]()
+ if let nameOffset = flatMap(dictionary["key.nameoffset"] as? Int64, { Int($0) }),
+ let nameLength = flatMap(dictionary["key.namelength"] as? Int64, { Int($0) }),
+ let offset = flatMap(dictionary["key.offset"] as? Int64, { Int($0) }) {
+
+ let location = Location(file: file, offset: offset)
+ let startAdvance = advance(file.contents.startIndex, nameOffset)
+ let endAdvance = advance(startAdvance, nameLength)
+ let range = Range(start: startAdvance, end: endAdvance)
+ let definition = file.contents.substringWithRange(range)
+
+ let ope1 = ["/", "=", "-", "+", "!", "*", "|", "^", "~", "?", "."].map({"\\\($0)"})
+ let ope2 = ["%", "<", ">", "&"]
+ let ope = "".join(ope1 + ope2)
+ let pattern = "^[\(ope)]+(<[A-Z]+>)?\\("
+
+ if let regex = NSRegularExpression(pattern: pattern, options: nil, error: nil) {
+ let matchRange = NSRange(location: 0, length: count(definition.utf16))
+ let matches = regex.matchesInString(definition, options: nil, range: matchRange)
+
+ if matches.count > 0 {
+ violations.append(StyleViolation(type: .OperatorFunctionWhitespace,
+ location: location,
+ severity: .Medium,
+ reason: "Use whitespace around operators when defining them"))
+ }
+ }
+ }
+ return violations
+ }
+
+ public let example = RuleExample(
+ ruleName: "Operator Function Whitespace Rule",
+ ruleDescription: "Use whitespace around operators when defining them.",
+ nonTriggeringExamples: [
+ "func <| (lhs: Int, rhs: Int) -> Int {}\n",
+ "func <|< (lhs: A, rhs: A) -> A {}\n",
+ "func abc(lhs: Int, rhs: Int) -> Int {}\n"
+ ],
+ triggeringExamples: [
+ "func <|(lhs: Int, rhs: Int) -> Int {}\n",
+ "func <|<(lhs: A, rhs: A) -> A {}\n"
+ ]
+ )
+}
diff --git a/Source/SwiftLintFramework/StyleViolation.swift b/Source/SwiftLintFramework/StyleViolation.swift
index 8bb046b325..1fe1dfb082 100644
--- a/Source/SwiftLintFramework/StyleViolation.swift
+++ b/Source/SwiftLintFramework/StyleViolation.swift
@@ -44,7 +44,7 @@ Returns true if `lhs` StyleViolation is equal to `rhs` StyleViolation.
:returns: True if `lhs` StyleViolation is equal to `rhs` StyleViolation.
*/
-public func ==(lhs: StyleViolation, rhs: StyleViolation) -> Bool {
+public func == (lhs: StyleViolation, rhs: StyleViolation) -> Bool {
return lhs.type == rhs.type &&
lhs.location == rhs.location &&
lhs.severity == rhs.severity &&
diff --git a/Source/SwiftLintFramework/StyleViolationType.swift b/Source/SwiftLintFramework/StyleViolationType.swift
index f98d2aac6b..0d7036e8ef 100644
--- a/Source/SwiftLintFramework/StyleViolationType.swift
+++ b/Source/SwiftLintFramework/StyleViolationType.swift
@@ -7,17 +7,18 @@
//
public enum StyleViolationType: String, Printable {
- case NameFormat = "Name Format"
- case Length = "Length"
- case TrailingNewline = "Trailing Newline"
- case LeadingWhitespace = "Leading Whitespace"
- case TrailingWhitespace = "Trailing Whitespace"
- case ReturnArrowWhitespace = "Return Arrow Whitespace"
- case ForceCast = "Force Cast"
- case TODO = "TODO or FIXME"
- case Colon = "Colon"
- case Nesting = "Nesting"
- case ControlStatement = "Control Statement Parentheses"
+ case NameFormat = "Name Format"
+ case Length = "Length"
+ case TrailingNewline = "Trailing Newline"
+ case LeadingWhitespace = "Leading Whitespace"
+ case TrailingWhitespace = "Trailing Whitespace"
+ case ReturnArrowWhitespace = "Return Arrow Whitespace"
+ case OperatorFunctionWhitespace = "Operator Function Whitespace"
+ case ForceCast = "Force Cast"
+ case TODO = "TODO or FIXME"
+ case Colon = "Colon"
+ case Nesting = "Nesting"
+ case ControlStatement = "Control Statement Parentheses"
public var description: String { return rawValue }
}
diff --git a/Source/SwiftLintFrameworkTests/ASTRuleTests.swift b/Source/SwiftLintFrameworkTests/ASTRuleTests.swift
index 3c6eb9f151..7ae87892b6 100644
--- a/Source/SwiftLintFrameworkTests/ASTRuleTests.swift
+++ b/Source/SwiftLintFrameworkTests/ASTRuleTests.swift
@@ -126,6 +126,10 @@ class ASTRuleTests: XCTestCase {
}
}
+ func testOperatorFunctionWhitespace() {
+ verifyRule(OperatorFunctionWhitespaceRule().example, type: .OperatorFunctionWhitespace)
+ }
+
func testNesting() {
verifyRule(NestingRule().example, type: .Nesting, commentDoesntViolate: false)
}
diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj
index 141a59aa70..a0bcc7866c 100644
--- a/SwiftLint.xcodeproj/project.pbxproj
+++ b/SwiftLint.xcodeproj/project.pbxproj
@@ -18,6 +18,7 @@
D0E7B65319E9C6AD00EDBA4D /* SwiftLintFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D1216D19E87B05005E4BAA /* SwiftLintFramework.framework */; };
D0E7B65619E9C76900EDBA4D /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D1211B19E87861005E4BAA /* main.swift */; };
E57B23C11B1D8BF000DEA512 /* ReturnArrowWhitespaceRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E57B23C01B1D8BF000DEA512 /* ReturnArrowWhitespaceRule.swift */; };
+ E5A167C91B25A0B000CF2D03 /* OperatorFunctionWhitespaceRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5A167C81B25A0B000CF2D03 /* OperatorFunctionWhitespaceRule.swift */; };
E812249A1B04F85B001783D2 /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = E81224991B04F85B001783D2 /* TestHelpers.swift */; };
E812249C1B04FADC001783D2 /* Linter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E812249B1B04FADC001783D2 /* Linter.swift */; };
E832F10B1B17E2F5003F265F /* NSFileManager+SwiftLint.swift in Sources */ = {isa = PBXBuildFile; fileRef = E832F10A1B17E2F5003F265F /* NSFileManager+SwiftLint.swift */; };
@@ -143,6 +144,7 @@
D0E7B63219E9C64500EDBA4D /* swiftlint.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = swiftlint.app; sourceTree = BUILT_PRODUCTS_DIR; };
D0E7B65819E9CA0800EDBA4D /* swiftlint */ = {isa = PBXFileReference; lastKnownFileType = text; path = swiftlint; sourceTree = BUILT_PRODUCTS_DIR; };
E57B23C01B1D8BF000DEA512 /* ReturnArrowWhitespaceRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReturnArrowWhitespaceRule.swift; sourceTree = ""; };
+ E5A167C81B25A0B000CF2D03 /* OperatorFunctionWhitespaceRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperatorFunctionWhitespaceRule.swift; sourceTree = ""; };
E81224991B04F85B001783D2 /* TestHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestHelpers.swift; sourceTree = ""; };
E812249B1B04FADC001783D2 /* Linter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Linter.swift; sourceTree = ""; };
E832F10A1B17E2F5003F265F /* NSFileManager+SwiftLint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSFileManager+SwiftLint.swift"; sourceTree = ""; };
@@ -415,6 +417,7 @@
E88DEA911B099B1F00A66CB0 /* TypeNameRule.swift */,
E88DEA931B099C0900A66CB0 /* VariableNameRule.swift */,
E57B23C01B1D8BF000DEA512 /* ReturnArrowWhitespaceRule.swift */,
+ E5A167C81B25A0B000CF2D03 /* OperatorFunctionWhitespaceRule.swift */,
);
path = Rules;
sourceTree = "";
@@ -607,6 +610,7 @@
E88DEA901B099A3100A66CB0 /* FunctionBodyLengthRule.swift in Sources */,
E88DEA6B1B0983FE00A66CB0 /* StyleViolation.swift in Sources */,
E832F10B1B17E2F5003F265F /* NSFileManager+SwiftLint.swift in Sources */,
+ E5A167C91B25A0B000CF2D03 /* OperatorFunctionWhitespaceRule.swift in Sources */,
E88DEA7E1B098F2A00A66CB0 /* LeadingWhitespaceRule.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;