From 39d28924ca05a18cab6a541e285b3552211d62f8 Mon Sep 17 00:00:00 2001 From: driver733 Date: Fri, 2 Mar 2018 13:36:58 -0500 Subject: [PATCH] #2079 - Implemented subclass rule --- CHANGELOG.md | 5 ++ Rules.md | 11 +++++ .../Models/MasterRuleList.swift | 1 + .../Rules/SubClassRule.swift | 46 +++++++++++++++++++ SwiftLint.xcodeproj/project.pbxproj | 12 ++++- Tests/LinuxMain.swift | 8 ++++ .../SubClassRuleTests.swift | 43 +++++++++++++++++ 7 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 Source/SwiftLintFramework/Rules/SubClassRule.swift create mode 100644 Tests/SwiftLintFrameworkTests/SubClassRuleTests.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 787db0532d4..dc10c7de3d7 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) + * Adds `function_body_whitespace_lines` opt-in rule to prohibit whitespace lines in function bodies. [Mikhail Yakushin](https://github.com/driver733) diff --git a/Rules.md b/Rules.md index 66157c966a1..9cd63bffb21 100644 --- a/Rules.md +++ b/Rules.md @@ -101,6 +101,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) @@ -12141,6 +12142,16 @@ struct Inter { +## Subclass + +Identifier | Enabled by default | Supports autocorrection | Kind +--- | --- | --- | --- +`subclass` | Disabled | No | style + +Subclasses are 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 55de5da71a5..f64cbb045e6 100644 --- a/Source/SwiftLintFramework/Models/MasterRuleList.swift +++ b/Source/SwiftLintFramework/Models/MasterRuleList.swift @@ -109,6 +109,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 00000000000..ee5ec06eb95 --- /dev/null +++ b/Source/SwiftLintFramework/Rules/SubClassRule.swift @@ -0,0 +1,46 @@ +// +// 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: "Subclasses are 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: "Subclasses are prohibited." + ) + ] + } else { + return [] + } + + } + +} diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj index 372a2ba0b3a..84ec11636f0 100644 --- a/SwiftLint.xcodeproj/project.pbxproj +++ b/SwiftLint.xcodeproj/project.pbxproj @@ -143,9 +143,11 @@ 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 */; }; + C2E091D98F9EC15D97944DD3 /* SubClassRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E09B21D7086565C82975AD /* SubClassRuleTests.swift */; }; + C2E0977960D35B8963751DB1 /* FunctionBodyWhitespaceLinesRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E090541C2D83EA2987D15C /* FunctionBodyWhitespaceLinesRule.swift */; }; C2E099B1FFD283BEA0EBC5B0 /* FunctionBodyCommentsRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E09082757235CF0E6120EA /* FunctionBodyCommentsRuleTests.swift */; }; + C2E09A497C757CA47CE801AD /* SubClassRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E0983EFF1CA03CAD386C10 /* SubClassRule.swift */; }; C2E09BF91C6D1C09AFF557F4 /* FunctionBodyCommentsRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E099E6FF20FE33D0EC8488 /* FunctionBodyCommentsRule.swift */; }; - C2E0977960D35B8963751DB1 /* FunctionBodyWhitespaceLinesRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E090541C2D83EA2987D15C /* FunctionBodyWhitespaceLinesRule.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 */; }; @@ -497,9 +499,11 @@ 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 = ""; }; + C2E090541C2D83EA2987D15C /* FunctionBodyWhitespaceLinesRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionBodyWhitespaceLinesRule.swift; sourceTree = ""; }; C2E09082757235CF0E6120EA /* FunctionBodyCommentsRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionBodyCommentsRuleTests.swift; sourceTree = ""; }; + C2E0983EFF1CA03CAD386C10 /* SubClassRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubClassRule.swift; sourceTree = ""; }; C2E099E6FF20FE33D0EC8488 /* FunctionBodyCommentsRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionBodyCommentsRule.swift; sourceTree = ""; }; - C2E090541C2D83EA2987D15C /* FunctionBodyWhitespaceLinesRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionBodyWhitespaceLinesRule.swift; sourceTree = ""; }; + C2E09B21D7086565C82975AD /* 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 = ""; }; @@ -981,6 +985,7 @@ 3B12C9BE1C3209AC000B423F /* Resources */, D0D1217C19E87B05005E4BAA /* Supporting Files */, C2E09082757235CF0E6120EA /* FunctionBodyCommentsRuleTests.swift */, + C2E09B21D7086565C82975AD /* SubClassRuleTests.swift */, ); name = SwiftLintFrameworkTests; path = Tests/SwiftLintFrameworkTests; @@ -1174,6 +1179,7 @@ 1872906F1FC37A9B0016BEA2 /* YodaConditionRule.swift */, C2E099E6FF20FE33D0EC8488 /* FunctionBodyCommentsRule.swift */, C2E090541C2D83EA2987D15C /* FunctionBodyWhitespaceLinesRule.swift */, + C2E0983EFF1CA03CAD386C10 /* SubClassRule.swift */, ); path = Rules; sourceTree = ""; @@ -1716,6 +1722,7 @@ B3935EE74B1E8E14FBD65E7F /* String+XML.swift in Sources */, C2E09BF91C6D1C09AFF557F4 /* FunctionBodyCommentsRule.swift in Sources */, C2E0977960D35B8963751DB1 /* FunctionBodyWhitespaceLinesRule.swift in Sources */, + C2E09A497C757CA47CE801AD /* SubClassRule.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1771,6 +1778,7 @@ 3BCC04D41C502BAB006073C3 /* RuleConfigurationTests.swift in Sources */, E809EDA31B8A73FB00399043 /* ConfigurationTests.swift in Sources */, C2E099B1FFD283BEA0EBC5B0 /* FunctionBodyCommentsRuleTests.swift in Sources */, + C2E091D98F9EC15D97944DD3 /* SubClassRuleTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index fb4150b27c5..d87dfe1df6f 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -531,6 +531,14 @@ extension SourceKitCrashTests { ] } +extension SubClassRuleTests { + static var allTests: [(String, (SubClassRuleTests) -> () throws -> Void)] = [ + ("testSubClassRuleSuperDot", testSubClassRuleSuperDot), + ("testSubClassRuleSuper", testSubClassRuleSuper), + ("testSubClassRuleTestsValid", testSubClassRuleTestsValid) + ] +} + extension TodoRuleTests { static var allTests: [(String, (TodoRuleTests) -> () throws -> Void)] = [ ("testTodo", testTodo), diff --git a/Tests/SwiftLintFrameworkTests/SubClassRuleTests.swift b/Tests/SwiftLintFrameworkTests/SubClassRuleTests.swift new file mode 100644 index 00000000000..2c3bfdbb890 --- /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) + } + +}