diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ee3d3fb69..8c9ccfa11c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,10 @@ * Cache parsing to reduce execution time by more than 50%. [Nikolaj Schumacher](https://github.com/nschum) +* Added `ControlStatementRule` to make sure that if/for/while/do statements + do not wrap their conditionals in parentheses. + [Andrea Mazzini](https://github.com/andreamazz) + ##### Bug Fixes None. diff --git a/Source/SwiftLintFramework/Linter.swift b/Source/SwiftLintFramework/Linter.swift index af0a50007d..9641a3bdeb 100644 --- a/Source/SwiftLintFramework/Linter.swift +++ b/Source/SwiftLintFramework/Linter.swift @@ -26,7 +26,8 @@ public struct Linter { VariableNameRule(), TypeBodyLengthRule(), FunctionBodyLengthRule(), - NestingRule() + NestingRule(), + ControlStatementRule() ] public var styleViolations: [StyleViolation] { diff --git a/Source/SwiftLintFramework/Rules/ControlStatementRule.swift b/Source/SwiftLintFramework/Rules/ControlStatementRule.swift new file mode 100644 index 0000000000..4e3a3fd053 --- /dev/null +++ b/Source/SwiftLintFramework/Rules/ControlStatementRule.swift @@ -0,0 +1,59 @@ +// +// ControlStatementRule.swift +// SwiftLint +// +// Created by Andrea Mazzini on 26/05/15. +// Copyright (c) 2015 Realm. All rights reserved. +// + +import SourceKittenFramework + +public struct ControlStatementRule: Rule { + public init() {} + + public let identifier = "control_statement" + + public func validateFile(file: File) -> [StyleViolation] { + let ifStatement = file.matchPattern("\\s{0,}(if)\\s{0,}\\(", + withSyntaxKinds: [.Keyword]).map{ return ($0, "if") } + let forStatement = file.matchPattern("\\s{0,}(for)\\s{0,}\\(", + withSyntaxKinds: [.Keyword]).map{ return ($0, "for") } + let whileStatement = file.matchPattern("\\s{0,}(while)\\s{0,}\\(", + withSyntaxKinds: [.Keyword]).map{ return ($0, "while") } + return (ifStatement + forStatement + whileStatement).map { violation in + return StyleViolation(type: .ControlStatement, + location: Location(file: file, offset: violation.0.location), + severity: .Low, + reason: "\(violation.1) statements shouldn't wrap their conditionals in parentheses.") + } + } + + public let example = RuleExample( + ruleName: "Control Statement", + ruleDescription: "if,for,while,do statements shouldn't wrap their conditionals in parentheses.", + nonTriggeringExamples: [ + "if condition {\n", + "renderGif(data)\n", + "for item in collection {\n", + "for var index = 0; index < 42; index++ {\n", + "while condition {\n", + "} while condition {\n", + "do { ; } while condition {\n" + ], + triggeringExamples: [ + "if (condition) {\n", + "if(condition) {\n", + "for (item in collection) {\n", + "for (var index = 0; index < 42; index++) {\n", + "for(item in collection) {\n", + "for(var index = 0; index < 42; index++) {\n", + "while (condition) {\n", + "while(condition) {\n", + "} while (condition) {\n", + "} while(condition) {\n", + "do { ; } while(condition) {\n", + "do { ; } while (condition) {\n" + ] + ) +} + diff --git a/Source/SwiftLintFramework/StyleViolationType.swift b/Source/SwiftLintFramework/StyleViolationType.swift index 400faaaa13..c9cfac6e14 100644 --- a/Source/SwiftLintFramework/StyleViolationType.swift +++ b/Source/SwiftLintFramework/StyleViolationType.swift @@ -16,6 +16,7 @@ public enum StyleViolationType: String, Printable { 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/LinterTests.swift b/Source/SwiftLintFrameworkTests/LinterTests.swift index b367d07ff0..7c5dba3d47 100644 --- a/Source/SwiftLintFrameworkTests/LinterTests.swift +++ b/Source/SwiftLintFrameworkTests/LinterTests.swift @@ -157,7 +157,7 @@ class LinterTests: XCTestCase { } func testControlStatements() { - // TODO: if,for,while,do statements shouldn't wrap their conditionals in parentheses. + verifyRule(ControlStatementRule().example, type: .ControlStatement, commentDoesntViolate: true) } func testNumberOfFunctionsInAType() { diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj index 3471adecec..6aa4384d84 100644 --- a/SwiftLint.xcodeproj/project.pbxproj +++ b/SwiftLint.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 24E17F721B14BB3F008195BE /* File+Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24E17F701B1481FF008195BE /* File+Cache.swift */; }; + 65454F471B14D76500319A6C /* ControlStatementRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65454F451B14D73800319A6C /* ControlStatementRule.swift */; }; 83894F221B0C928A006214E1 /* RulesCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83894F211B0C928A006214E1 /* RulesCommand.swift */; }; 83D71E281B131ECE000395DE /* RuleExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83D71E261B131EB5000395DE /* RuleExample.swift */; }; 83D71E2B1B131EED000395DE /* AnsiCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83D71E231B131C11000395DE /* AnsiCode.swift */; }; @@ -106,6 +107,7 @@ 24E17F701B1481FF008195BE /* File+Cache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "File+Cache.swift"; sourceTree = ""; }; 5499CA961A2394B700783309 /* Components.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Components.plist; sourceTree = ""; }; 5499CA971A2394B700783309 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 65454F451B14D73800319A6C /* ControlStatementRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControlStatementRule.swift; sourceTree = ""; }; 83894F211B0C928A006214E1 /* RulesCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RulesCommand.swift; sourceTree = ""; }; 83894F231B0CAD41006214E1 /* StructuredText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = StructuredText.swift; path = ../swiftlint/StructuredText.swift; sourceTree = ""; }; 83D71E231B131C11000395DE /* AnsiCode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnsiCode.swift; sourceTree = ""; }; @@ -385,6 +387,7 @@ isa = PBXGroup; children = ( E88DEA831B0990F500A66CB0 /* ColonRule.swift */, + 65454F451B14D73800319A6C /* ControlStatementRule.swift */, E88DEA891B0992B300A66CB0 /* FileLengthRule.swift */, E88DEA7F1B09903300A66CB0 /* ForceCastRule.swift */, E88DEA8F1B099A3100A66CB0 /* FunctionBodyLengthRule.swift */, @@ -573,6 +576,7 @@ E88DEA941B099C0900A66CB0 /* VariableNameRule.swift in Sources */, E88DEA8A1B0992B300A66CB0 /* FileLengthRule.swift in Sources */, E88DEA751B09852000A66CB0 /* File+SwiftLint.swift in Sources */, + 65454F471B14D76500319A6C /* ControlStatementRule.swift in Sources */, E88DEA8E1B0999CD00A66CB0 /* TypeBodyLengthRule.swift in Sources */, E88DEA6F1B09843F00A66CB0 /* Location.swift in Sources */, E88DEA881B09924C00A66CB0 /* TrailingNewlineRule.swift in Sources */,