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)
+ }
+
+}