diff --git a/CHANGELOG.md b/CHANGELOG.md index b103e0ae6a..8da750caf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,11 @@ [Scott Hoyt](https://github.com/scottrhoyt) [#123](https://github.com/realm/SwiftLint/issues/123) +* Added opt-in `ForceUnwrappingRule` to issue warnings for all forced + unwrappings. + [Benjamin Otto](https://github.com/Argent) + [#55](https://github.com/realm/SwiftLint/issues/55) + ##### Bug Fixes * Fix several false positives in `ValidDocsRule`. diff --git a/Source/SwiftLintFramework/Models/MasterRuleList.swift b/Source/SwiftLintFramework/Models/MasterRuleList.swift index 522a90a98c..5c08344150 100644 --- a/Source/SwiftLintFramework/Models/MasterRuleList.swift +++ b/Source/SwiftLintFramework/Models/MasterRuleList.swift @@ -30,6 +30,7 @@ public let masterRuleList = RuleList( rules: ClosingBraceRule.self, FileLengthRule.self, ForceCastRule.self, ForceTryRule.self, + ForceUnwrappingRule.self, FunctionBodyLengthRule.self, LeadingWhitespaceRule.self, LegacyConstantRule.self, diff --git a/Source/SwiftLintFramework/Rules/ForceUnwrappingRule.swift b/Source/SwiftLintFramework/Rules/ForceUnwrappingRule.swift new file mode 100644 index 0000000000..9cc754ae8d --- /dev/null +++ b/Source/SwiftLintFramework/Rules/ForceUnwrappingRule.swift @@ -0,0 +1,40 @@ +// +// ForceUnwrappingRule.swift +// SwiftLint +// +// Created by Benjamin Otto on 14/01/16. +// Copyright (c) 2015 Realm. All rights reserved. +// + +import SourceKittenFramework + +public struct ForceUnwrappingRule: OptInRule, ConfigProviderRule { + + public var config = SeverityConfig(.Warning) + + public init() {} + + public static let description = RuleDescription( + identifier: "force_unwrapping", + name: "Force Unwrapping", + description: "Force unwrapping should be avoided.", + nonTriggeringExamples: [ + "if let url = NSURL(string: query)", + "navigationController?.pushViewController(viewController, animated: true)" + ], + triggeringExamples: [ + "let url = NSURL(string: query)↓!", + "navigationController↓!.pushViewController(viewController, animated: true)", + "let unwrapped = optional↓!", + "return cell↓!" + ] + ) + + public func validateFile(file: File) -> [StyleViolation] { + return file.matchPattern("[\\w)]+!", withSyntaxKinds: [.Identifier]).map { + StyleViolation(ruleDescription: self.dynamicType.description, + severity: config.severity, + location: Location(file: file, characterOffset: NSMaxRange($0) - 1)) + } + } +} diff --git a/Source/SwiftLintFrameworkTests/RulesTests.swift b/Source/SwiftLintFrameworkTests/RulesTests.swift index cecca17681..140d1cf819 100644 --- a/Source/SwiftLintFrameworkTests/RulesTests.swift +++ b/Source/SwiftLintFrameworkTests/RulesTests.swift @@ -50,6 +50,10 @@ class RulesTests: XCTestCase { verifyRule(ForceTryRule.description) } + func testForceUnwrapping() { + verifyRule(ForceUnwrappingRule.description) + } + func testFunctionBodyLength() { verifyRule(FunctionBodyLengthRule.description) } diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj index 752659dbab..c352d18598 100644 --- a/SwiftLint.xcodeproj/project.pbxproj +++ b/SwiftLint.xcodeproj/project.pbxproj @@ -35,6 +35,7 @@ 69F88BF71BDA38A6005E7CAE /* OpeningBraceRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 692B1EB11BD7E00F00EAABFF /* OpeningBraceRule.swift */; }; 83894F221B0C928A006214E1 /* RulesCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83894F211B0C928A006214E1 /* RulesCommand.swift */; }; 83D71E281B131ECE000395DE /* RuleDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83D71E261B131EB5000395DE /* RuleDescription.swift */; }; + B58AEED61C492C7B00E901FD /* ForceUnwrappingRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58AEED51C492C7B00E901FD /* ForceUnwrappingRule.swift */; }; D0AAAB5019FB0960007B24B3 /* SwiftLintFramework.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D0D1216D19E87B05005E4BAA /* SwiftLintFramework.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D0D1217819E87B05005E4BAA /* SwiftLintFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D1216D19E87B05005E4BAA /* SwiftLintFramework.framework */; }; D0E7B65319E9C6AD00EDBA4D /* SwiftLintFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D1216D19E87B05005E4BAA /* SwiftLintFramework.framework */; }; @@ -191,6 +192,7 @@ 695BE9CE1BDFD92B0071E985 /* CommaRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommaRule.swift; sourceTree = ""; }; 83894F211B0C928A006214E1 /* RulesCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RulesCommand.swift; sourceTree = ""; }; 83D71E261B131EB5000395DE /* RuleDescription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuleDescription.swift; sourceTree = ""; }; + B58AEED51C492C7B00E901FD /* ForceUnwrappingRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForceUnwrappingRule.swift; sourceTree = ""; }; D0D1211B19E87861005E4BAA /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; usesTabs = 0; }; D0D1212419E878CC005E4BAA /* Common.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Common.xcconfig; sourceTree = ""; }; D0D1212619E878CC005E4BAA /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; @@ -545,6 +547,7 @@ E88DEA891B0992B300A66CB0 /* FileLengthRule.swift */, E88DEA7F1B09903300A66CB0 /* ForceCastRule.swift */, E816194D1BFBFEAB00946723 /* ForceTryRule.swift */, + B58AEED51C492C7B00E901FD /* ForceUnwrappingRule.swift */, E88DEA8F1B099A3100A66CB0 /* FunctionBodyLengthRule.swift */, E88DEA7D1B098F2A00A66CB0 /* LeadingWhitespaceRule.swift */, 006ECFC31C44E99E00EF6364 /* LegacyConstantRule.swift */, @@ -851,6 +854,7 @@ E88198421BEA929F00333A11 /* NestingRule.swift in Sources */, 3BB47D851C51D80000AE6A10 /* NSRegularExpression+SwiftLint.swift in Sources */, E881985B1BEA974E00333A11 /* StatementPositionRule.swift in Sources */, + B58AEED61C492C7B00E901FD /* ForceUnwrappingRule.swift in Sources */, E88DEA711B09847500A66CB0 /* ViolationSeverity.swift in Sources */, 3BD9CD3D1C37175B009A5D25 /* YamlParser.swift in Sources */, E88DEA8C1B0999A000A66CB0 /* ASTRule.swift in Sources */,