diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000000..a55e7a179b --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index e9cd3dab78..69c6bab060 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ #### Enhancements +* Adds `subclass` opt-in rule to prohibit + subclassing. + [Mikhail Yakushin](https://github.com/driver733) + [#2079](https://github.com/realm/SwiftLint/issues/2079) + * Add a new `excluded` config parameter to the `explicit_type_interface` rule to exempt certain types of variables from the rule. [Rounak Jain](https://github.com/rounak) diff --git a/Rules.md b/Rules.md index 4f45dd5d56..3bee2c8674 100644 --- a/Rules.md +++ b/Rules.md @@ -99,6 +99,7 @@ * [Sorted Imports](#sorted-imports) * [Statement Position](#statement-position) * [Strict fileprivate](#strict-fileprivate) +* [Subclass](#subclass) * [Superfluous Disable Command](#superfluous-disable-command) * [Switch and Case Statement Alignment](#switch-and-case-statement-alignment) * [Switch Case on Newline](#switch-case-on-newline) @@ -12119,6 +12120,16 @@ struct Inter { +## Subclass + +Identifier | Enabled by default | Supports autocorrection | Kind +--- | --- | --- | --- +`subclass` | Disabled | No | style + +Subclassing is prohibited. + + + ## Superfluous Disable Command Identifier | Enabled by default | Supports autocorrection | Kind diff --git a/Source/SwiftLintFramework/Models/MasterRuleList.swift b/Source/SwiftLintFramework/Models/MasterRuleList.swift index 34caef2f63..58cd93454d 100644 --- a/Source/SwiftLintFramework/Models/MasterRuleList.swift +++ b/Source/SwiftLintFramework/Models/MasterRuleList.swift @@ -107,6 +107,7 @@ public let masterRuleList = RuleList(rules: [ SortedImportsRule.self, StatementPositionRule.self, StrictFilePrivateRule.self, + SubClassRule.self, SuperfluousDisableCommandRule.self, SwitchCaseAlignmentRule.self, SwitchCaseOnNewlineRule.self, diff --git a/Source/SwiftLintFramework/Rules/SubClassRule.swift b/Source/SwiftLintFramework/Rules/SubClassRule.swift new file mode 100644 index 0000000000..b436edd64d --- /dev/null +++ b/Source/SwiftLintFramework/Rules/SubClassRule.swift @@ -0,0 +1,47 @@ +// +// SubClassRule.swift +// SwiftLint +// +// Created by Mikhail Yakushin on 03/02/18. +// Copyright © 2018 Realm. All rights reserved. +// + +import SourceKittenFramework + +public struct SubClassRule: ASTRule, ConfigurationProviderRule, OptInRule { + public var configuration = SeverityLevelsConfiguration(warning: 0, error: 0) + + public init() {} + + public static let description = RuleDescription( + identifier: "subclass", + name: "Subclass", + description: "Subclassing is prohibited.", + kind: .style + ) + + public func validate(file: File, kind: SwiftDeclarationKind, + dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] { + guard SwiftDeclarationKind.functionKinds.contains(kind), + let offset = dictionary.offset, + case let contentsNSString = file.contents.bridge() + else { + return [] + } + + if contentsNSString.contains("super.") || contentsNSString.contains("super()") { + return [ + StyleViolation( + ruleDescription: type(of: self).description, + severity: configuration.params.first!.severity, + location: Location(file: file, byteOffset: offset), + reason: "Subclassing is prohibited." + ) + ] + } else { + return [] + } + + } + +} diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj index e9dd4b188a..f5d84c336b 100644 --- a/SwiftLint.xcodeproj/project.pbxproj +++ b/SwiftLint.xcodeproj/project.pbxproj @@ -142,6 +142,8 @@ B89F3BCF1FD5EE1400931E59 /* RequiredEnumCaseRuleConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B89F3BC71FD5ED7D00931E59 /* RequiredEnumCaseRuleConfiguration.swift */; }; BB00B4E91F5216090079869F /* MultipleClosuresWithTrailingClosureRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB00B4E71F5216070079869F /* MultipleClosuresWithTrailingClosureRule.swift */; }; BFF028AE1CBCF8A500B38A9D /* TrailingWhitespaceConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF48D2D61CBCCA5F0080BDAE /* TrailingWhitespaceConfiguration.swift */; }; + C2E0926C065C57CD27EAACC7 /* SubClassRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E0932F4A37171BCB5D5D27 /* SubClassRule.swift */; }; + C2E09BB35101215D85319786 /* SubClassRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E09BACD1C291F22F7A49C1 /* SubClassRuleTests.swift */; }; C328A2F71E6759AE00A9E4D7 /* ExplicitTypeInterfaceRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C328A2F51E67595500A9E4D7 /* ExplicitTypeInterfaceRule.swift */; }; C3DE5DAC1E7DF9CA00761483 /* FatalErrorMessageRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DE5DAA1E7DF99B00761483 /* FatalErrorMessageRule.swift */; }; C946FECB1EAE67EE007DD778 /* LetVarWhitespaceRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C946FEC91EAE5E20007DD778 /* LetVarWhitespaceRule.swift */; }; @@ -492,6 +494,8 @@ B89F3BCB1FD5EDA900931E59 /* RequiredEnumCaseRuleTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequiredEnumCaseRuleTestCase.swift; sourceTree = ""; }; BB00B4E71F5216070079869F /* MultipleClosuresWithTrailingClosureRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultipleClosuresWithTrailingClosureRule.swift; sourceTree = ""; }; BF48D2D61CBCCA5F0080BDAE /* TrailingWhitespaceConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrailingWhitespaceConfiguration.swift; sourceTree = ""; }; + C2E0932F4A37171BCB5D5D27 /* SubClassRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubClassRule.swift; sourceTree = ""; }; + C2E09BACD1C291F22F7A49C1 /* SubClassRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubClassRuleTests.swift; sourceTree = ""; }; C328A2F51E67595500A9E4D7 /* ExplicitTypeInterfaceRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExplicitTypeInterfaceRule.swift; sourceTree = ""; }; C3DE5DAA1E7DF99B00761483 /* FatalErrorMessageRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FatalErrorMessageRule.swift; sourceTree = ""; }; C946FEC91EAE5E20007DD778 /* LetVarWhitespaceRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LetVarWhitespaceRule.swift; sourceTree = ""; }; @@ -971,6 +975,7 @@ D0D1212219E878CC005E4BAA /* Configuration */, 3B12C9BE1C3209AC000B423F /* Resources */, D0D1217C19E87B05005E4BAA /* Supporting Files */, + C2E09BACD1C291F22F7A49C1 /* SubClassRuleTests.swift */, ); name = SwiftLintFrameworkTests; path = Tests/SwiftLintFrameworkTests; @@ -1162,6 +1167,7 @@ 094384FF1D5D2382009168CF /* WeakDelegateRule.swift */, 626D02961F31CBCC0054788D /* XCTFailMessageRule.swift */, 1872906F1FC37A9B0016BEA2 /* YodaConditionRule.swift */, + C2E0932F4A37171BCB5D5D27 /* SubClassRule.swift */, ); path = Rules; sourceTree = ""; @@ -1702,6 +1708,7 @@ A73469421FB121BA009B57C7 /* CallPairRule.swift in Sources */, D47079AF1DFE520000027086 /* VoidReturnRule.swift in Sources */, B3935EE74B1E8E14FBD65E7F /* String+XML.swift in Sources */, + C2E0926C065C57CD27EAACC7 /* SubClassRule.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1755,6 +1762,7 @@ 3B63D46F1E1F09DF0057BE35 /* LineLengthRuleTests.swift in Sources */, 3BCC04D41C502BAB006073C3 /* RuleConfigurationTests.swift in Sources */, E809EDA31B8A73FB00399043 /* ConfigurationTests.swift in Sources */, + C2E09BB35101215D85319786 /* SubClassRuleTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 14ee5fb055..d1731ce259 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -504,6 +504,14 @@ extension RulesTests { ] } +extension SubClassRuleTests { + static var allTests: [(String, (SubClassRuleTests) -> () throws -> Void)] = [ + ("testSubClassRuleSuperDot", testSubClassRuleSuperDot), + ("testSubClassRuleSuper", testSubClassRuleSuper), + ("testSubClassRuleTestsValid", testSubClassRuleTestsValid) + ] +} + extension SourceKitCrashTests { static var allTests: [(String, (SourceKitCrashTests) -> () throws -> Void)] = [ ("testAssertHandlerIsNotCalledOnNormalFile", testAssertHandlerIsNotCalledOnNormalFile), diff --git a/Tests/SwiftLintFrameworkTests/SubClassRuleTests.swift b/Tests/SwiftLintFrameworkTests/SubClassRuleTests.swift new file mode 100644 index 0000000000..c3f4a86de6 --- /dev/null +++ b/Tests/SwiftLintFrameworkTests/SubClassRuleTests.swift @@ -0,0 +1,43 @@ +// +// SubClassRuleTests.swift +// SwiftLint +// +// Created by Mikhail Yakushin on 03/02/18. +// Copyright © 2018 Realm. All rights reserved. +// + +import SwiftLintFramework +import XCTest + +class SubClassRuleTests: XCTestCase { + + func testSubClassRuleSuperDot() { + let nonTriggeringExamples = + "class MyType: SuperType {" + + "public init() { super.init() }" + + "}" + XCTAssertNotEqual(violations(nonTriggeringExamples), []) + } + + func testSubClassRuleSuper() { + let nonTriggeringExamples = + "class MyType: SuperType {" + + "public init() { super() }" + + "}" + XCTAssertNotEqual(violations(nonTriggeringExamples), []) + } + + func testSubClassRuleTestsValid() { + let nonTriggeringExamples = + "class MyType: SuperType {" + + "public init() { this() }" + + "}" + XCTAssertEqual(violations(nonTriggeringExamples), []) + } + + private func violations(_ string: String) -> [StyleViolation] { + let config = makeConfig(nil, SubClassRule.description.identifier)! + return SwiftLintFrameworkTests.violations(string, config: config) + } + +}