diff --git a/CHANGELOG.md b/CHANGELOG.md index 452ef18d58..9bbb1f2541 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,11 @@ * Adds `allow_private_set` configuration for the `private_outlet` rule. [Rohan Dhaimade](https://github.com/HaloZero) +* Add ``ExplicitInitRule`` Opt-In rule to discourage calling ``init`` + directly. + [Matt Taube](https://github.com/mtaube) + [#715](https://github.com/realm/SwiftLint/pull/715) + ##### Bug Fixes * Fixed whitespace being added to TODO messages. diff --git a/Source/SwiftLintFramework/Models/MasterRuleList.swift b/Source/SwiftLintFramework/Models/MasterRuleList.swift index 2143aa665b..4569944415 100644 --- a/Source/SwiftLintFramework/Models/MasterRuleList.swift +++ b/Source/SwiftLintFramework/Models/MasterRuleList.swift @@ -49,6 +49,7 @@ public let masterRuleList = RuleList(rules: CustomRules.self, CyclomaticComplexityRule.self, EmptyCountRule.self, + ExplicitInitRule.self, FileLengthRule.self, ForceCastRule.self, ForceTryRule.self, diff --git a/Source/SwiftLintFramework/Rules/ExplicitInitRule.swift b/Source/SwiftLintFramework/Rules/ExplicitInitRule.swift new file mode 100644 index 0000000000..86d51d0bf3 --- /dev/null +++ b/Source/SwiftLintFramework/Rules/ExplicitInitRule.swift @@ -0,0 +1,75 @@ +// +// ExplicitInitRule.swift +// SwiftLint +// +// Created by Matt Taube on 7/2/16. +// Copyright (c) 2016 Realm. All rights reserved. +// + +import Foundation +import SourceKittenFramework + +public struct ExplicitInitRule: ConfigurationProviderRule, CorrectableRule, OptInRule { + + private let pattern = "\\b([A-Z][A-Za-z]*)\\.init\\(" + + public var configuration = SeverityConfiguration(.Warning) + + public init() {} + + public static let description = RuleDescription( + identifier: "explicit_init", + name: "Explicit Init", + description: "Explicitly calling .init() should be avoided.", + nonTriggeringExamples: [ + "self.init(", + "self.init", + "Abc.init", + "abc.init(", + "$0.init(" + ], + triggeringExamples: [ + "Abc.init(", + "Abc(NSURL.init(someString" + ], + corrections: [ + "Abc.init(": "Abc(", + "Abc(NSURL.init(someString": "Abc(NSURL(someString" + ] + ) + + public func validateFile(file: File) -> [StyleViolation] { + return violationRangesInFile(file).flatMap { range in + return StyleViolation(ruleDescription: self.dynamicType.description, + severity: configuration.severity, + location: Location(file: file, characterOffset: range.location)) + } + } + + private func violationRangesInFile(file: File) -> [NSRange] { + let excludingKinds = SyntaxKind.commentAndStringKinds() + + return file.matchPattern(pattern, excludingSyntaxKinds: excludingKinds) + } + + public func correctFile(file: File) -> [Correction] { + let matches = violationRangesInFile(file) + guard !matches.isEmpty else { return [] } + + let regularExpression = regex(pattern) + let description = self.dynamicType.description + var corrections = [Correction]() + var contents = file.contents + for range in matches.reverse() { + contents = regularExpression.stringByReplacingMatchesInString(contents, + options: [], + range: range, + withTemplate: "$1(") + let location = Location(file: file, characterOffset: range.location) + corrections.append(Correction(ruleDescription: description, location: location)) + } + + file.write(contents) + return corrections + } +} diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj index f64e416b6a..89cdac7535 100644 --- a/SwiftLint.xcodeproj/project.pbxproj +++ b/SwiftLint.xcodeproj/project.pbxproj @@ -53,6 +53,7 @@ 6CCFCF2F1CFEF73E003239EB /* SwiftyTextTable.framework in Embed Frameworks into SwiftLintFramework.framework */ = {isa = PBXBuildFile; fileRef = 3BBF2F9C1C640A0F006CD775 /* SwiftyTextTable.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 6CCFCF301CFEF742003239EB /* Yaml.framework in Embed Frameworks into SwiftLintFramework.framework */ = {isa = PBXBuildFile; fileRef = E89376AC1B8A701E0025708E /* Yaml.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 7250948A1D0859260039B353 /* StatementPositionConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 725094881D0855760039B353 /* StatementPositionConfiguration.swift */; }; + 7C0C2E7A1D2866CB0076435A /* ExplicitInitRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0C2E791D2866CB0076435A /* ExplicitInitRule.swift */; }; 83894F221B0C928A006214E1 /* RulesCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83894F211B0C928A006214E1 /* RulesCommand.swift */; }; 83D71E281B131ECE000395DE /* RuleDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83D71E261B131EB5000395DE /* RuleDescription.swift */; }; 85DA81321D6B471000951BC4 /* MarkRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 856651A61D6B395F005E6B29 /* MarkRule.swift */; }; @@ -222,6 +223,7 @@ 6CB514E81C760C6900FA02C4 /* Structure+SwiftLint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Structure+SwiftLint.swift"; sourceTree = ""; }; 6CC4259A1C77046200AEA885 /* SyntaxMap+SwiftLint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SyntaxMap+SwiftLint.swift"; sourceTree = ""; }; 725094881D0855760039B353 /* StatementPositionConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatementPositionConfiguration.swift; sourceTree = ""; }; + 7C0C2E791D2866CB0076435A /* ExplicitInitRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExplicitInitRule.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 = ""; }; 856651A61D6B395F005E6B29 /* MarkRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkRule.swift; sourceTree = ""; }; @@ -604,6 +606,7 @@ 3B1DF0111C5148140011BCED /* CustomRules.swift */, 2E02005E1C54BF680024D09D /* CyclomaticComplexityRule.swift */, E847F0A81BFBBABD00EA9363 /* EmptyCountRule.swift */, + 7C0C2E791D2866CB0076435A /* ExplicitInitRule.swift */, E88DEA891B0992B300A66CB0 /* FileLengthRule.swift */, E88DEA7F1B09903300A66CB0 /* ForceCastRule.swift */, E816194D1BFBFEAB00946723 /* ForceTryRule.swift */, @@ -946,6 +949,7 @@ B2902A0E1D6681F700BFCCF7 /* PrivateUnitTestConfiguration.swift in Sources */, E88DEA6F1B09843F00A66CB0 /* Location.swift in Sources */, 93E0C3CE1D67BD7F007FA25D /* ConditionalReturnsOnNewline.swift in Sources */, + 7C0C2E7A1D2866CB0076435A /* ExplicitInitRule.swift in Sources */, E88DEA771B098D0C00A66CB0 /* Rule.swift in Sources */, 24B4DF0D1D6DFDE90097803B /* RedundantNilCoalesingRule.swift in Sources */, 7250948A1D0859260039B353 /* StatementPositionConfiguration.swift in Sources */, diff --git a/Tests/SwiftLintFramework/RulesTests.swift b/Tests/SwiftLintFramework/RulesTests.swift index b25fad748e..46e626dced 100644 --- a/Tests/SwiftLintFramework/RulesTests.swift +++ b/Tests/SwiftLintFramework/RulesTests.swift @@ -109,6 +109,10 @@ class RulesTests: XCTestCase { verifyRule(EmptyCountRule.description) } + func testExplicitInit() { + verifyRule(ExplicitInitRule.description) + } + func testFileLength() { verifyRule(FileLengthRule.description, commentDoesntViolate: false) }