Skip to content

Commit

Permalink
Merge pull request #1888 from marcelofabri/override-extension
Browse files Browse the repository at this point in the history
Add override_in_extension rule
  • Loading branch information
marcelofabri authored Oct 9, 2017
2 parents 390e5ca + a45a7da commit 98c0fdd
Show file tree
Hide file tree
Showing 9 changed files with 227 additions and 46 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@

##### Enhancements

* None.
* Add `override_in_extension` opt-in rule that warns against overriding
declarations in an `extension`.
[Marcelo Fabri](https://github.com/marcelofabri)
[#1884](https://github.com/realm/SwiftLint/issues/1884)

##### Bug Fixes

Expand Down
75 changes: 75 additions & 0 deletions Rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
* [Operator Usage Whitespace](#operator-usage-whitespace)
* [Operator Function Whitespace](#operator-function-whitespace)
* [Overridden methods call super](#overridden-methods-call-super)
* [Override in Extension](#override-in-extension)
* [Pattern Matching Keywords](#pattern-matching-keywords)
* [Private Outlets](#private-outlets)
* [Private over fileprivate](#private-over-fileprivate)
Expand Down Expand Up @@ -8484,6 +8485,80 @@ class VC: UIViewController {



## Override in Extension

Identifier | Enabled by default | Supports autocorrection | Kind
--- | --- | --- | ---
`override_in_extension` | Disabled | No | lint

Extensions shouldn't override declarations.

### Examples

<details>
<summary>Non Triggering Examples</summary>

```swift
extension Person {
var age: Int { return 42 }
}

```

```swift
extension Person {
func celebrateBirthday() {}
}

```

```swift
class Employee: Person {
override func celebrateBirthday() {}
}

```

```swift
class Foo: NSObject {}
extension Foo {
override var description: String { return "" }
}

```

```swift
struct Foo {
class Bar: NSObject {}
}
extension Foo.Bar {
override var description: String { return "" }
}

```

</details>
<details>
<summary>Triggering Examples</summary>

```swift
extension Person {
override var age: Int { return 42 }
}

```

```swift
extension Person {
override func celebrateBirthday() {}
}

```

</details>



## Pattern Matching Keywords

Identifier | Enabled by default | Supports autocorrection | Kind
Expand Down
64 changes: 64 additions & 0 deletions Source/SwiftLintFramework/Helpers/NamespaceCollector.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// NamespaceCollector.swift
// SwiftLint
//
// Created by Marcelo Fabri on 10/07/17.
// Copyright © 2017 Realm. All rights reserved.
//

import Foundation
import SourceKittenFramework

struct NamespaceCollector {
struct Element {
let name: String
let kind: SwiftDeclarationKind
let offset: Int
let dictionary: [String: SourceKitRepresentable]

init?(dictionary: [String: SourceKitRepresentable], namespace: [String]) {
guard let name = dictionary.name,
let kind = dictionary.kind.flatMap(SwiftDeclarationKind.init),
let offset = dictionary.offset else {
return nil
}

self.name = (namespace + [name]).joined(separator: ".")
self.kind = kind
self.offset = offset
self.dictionary = dictionary
}
}

private let dictionary: [String: SourceKitRepresentable]

init(dictionary: [String: SourceKitRepresentable]) {
self.dictionary = dictionary
}

func findAllElements(of types: Set<SwiftDeclarationKind>,
namespace: [String] = []) -> [Element] {
return findAllElements(in: dictionary, of: types, namespace: namespace)
}

private func findAllElements(in dictionary: [String: SourceKitRepresentable],
of types: Set<SwiftDeclarationKind>,
namespace: [String] = []) -> [Element] {

return dictionary.substructure.flatMap { subDict -> [Element] in

var elements: [Element] = []
guard let element = Element(dictionary: subDict, namespace: namespace) else {
return elements
}

if types.contains(element.kind) {
elements.append(element)
}

elements += findAllElements(in: subDict, of: types, namespace: [element.name])

return elements
}
}
}
1 change: 1 addition & 0 deletions Source/SwiftLintFramework/Models/MasterRuleList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ public let masterRuleList = RuleList(rules: [
OperatorFunctionWhitespaceRule.self,
OperatorUsageWhitespaceRule.self,
OverriddenSuperCallRule.self,
OverrideInExtensionRule.self,
PatternMatchingKeywordsRule.self,
PrivateOutletRule.self,
PrivateOverFilePrivateRule.self,
Expand Down
47 changes: 2 additions & 45 deletions Source/SwiftLintFramework/Rules/NoGroupingExtensionRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,8 @@ public struct NoGroupingExtensionRule: OptInRule, ConfigurationProviderRule {
)

public func validate(file: File) -> [StyleViolation] {

let elements = findAllElements(in: file.structure.dictionary,
of: [.class, .enum, .struct, .extension])
let collector = NamespaceCollector(dictionary: file.structure.dictionary)
let elements = collector.findAllElements(of: [.class, .enum, .struct, .extension])

let susceptibleNames = Set(elements.flatMap { $0.kind != .extension ? $0.name : nil })

Expand All @@ -46,46 +45,4 @@ public struct NoGroupingExtensionRule: OptInRule, ConfigurationProviderRule {
location: Location(file: file, byteOffset: $0.offset))
}
}

private func findAllElements(in dictionary: [String: SourceKitRepresentable],
of types: Set<SwiftDeclarationKind>,
namespace: [String] = []) -> [Element] {

return dictionary.substructure.flatMap { subDict -> [Element] in

var elements: [Element] = []
guard let element = Element(dictionary: subDict, namespace: namespace) else {
return elements
}

if types.contains(element.kind) {
elements.append(element)
}

elements += findAllElements(in: subDict, of: types, namespace: [element.name])

return elements
}
}
}

private struct Element {

let name: String
let kind: SwiftDeclarationKind
let offset: Int

init?(dictionary: [String: SourceKitRepresentable], namespace: [String]) {

guard let name = dictionary.name,
let kind = dictionary.kind.flatMap(SwiftDeclarationKind.init),
let offset = dictionary.offset
else {
return nil
}

self.name = (namespace + [name]).joined(separator: ".")
self.kind = kind
self.offset = offset
}
}
68 changes: 68 additions & 0 deletions Source/SwiftLintFramework/Rules/OverrideInExtensionRule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//
// OverrideInExtensionRule.swift
// SwiftLint
//
// Created by Marcelo Fabri on 10/05/17.
// Copyright © 2017 Realm. All rights reserved.
//

import Foundation
import SourceKittenFramework

public struct OverrideInExtensionRule: ConfigurationProviderRule, OptInRule {
public var configuration = SeverityConfiguration(.warning)

public init() {}

public static let description = RuleDescription(
identifier: "override_in_extension",
name: "Override in Extension",
description: "Extensions shouldn't override declarations.",
kind: .lint,
nonTriggeringExamples: [
"extension Person {\n var age: Int { return 42 }\n}\n",
"extension Person {\n func celebrateBirthday() {}\n}\n",
"class Employee: Person {\n override func celebrateBirthday() {}\n}\n",
"class Foo: NSObject {}\n" +
"extension Foo {\n" +
" override var description: String { return \"\" }\n" +
"}\n",
"struct Foo {\n" +
" class Bar: NSObject {}\n" +
"}\n" +
"extension Foo.Bar {\n" +
" override var description: String { return \"\" }\n" +
"}\n"
],
triggeringExamples: [
"extension Person {\n override ↓var age: Int { return 42 }\n}\n",
"extension Person {\n override ↓func celebrateBirthday() {}\n}\n"
]
)

public func validate(file: File) -> [StyleViolation] {
let collector = NamespaceCollector(dictionary: file.structure.dictionary)
let elements = collector.findAllElements(of: [.class, .struct, .enum, .extension])

let susceptibleNames = Set(elements.flatMap { $0.kind == .class ? $0.name : nil })

return elements
.filter { $0.kind == .extension && !susceptibleNames.contains($0.name) }
.flatMap { element in
return element.dictionary.substructure.flatMap { element -> Int? in
guard element.kind.flatMap(SwiftDeclarationKind.init) != nil,
element.enclosedSwiftAttributes.contains("source.decl.attribute.override"),
let offset = element.offset else {
return nil
}

return offset
}
}
.map {
StyleViolation(ruleDescription: type(of: self).description,
severity: configuration.severity,
location: Location(file: file, byteOffset: $0))
}
}
}
8 changes: 8 additions & 0 deletions SwiftLint.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@
D40AD08A1E032F9700F48C30 /* UnusedClosureParameterRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D40AD0891E032F9700F48C30 /* UnusedClosureParameterRule.swift */; };
D40E041C1F46E3B30043BC4E /* SuperfluousDisableCommandRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D40E041B1F46E3B30043BC4E /* SuperfluousDisableCommandRule.swift */; };
D40F83881DE9179200524C62 /* TrailingCommaConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D40F83871DE9179200524C62 /* TrailingCommaConfiguration.swift */; };
D40FE89D1F867BFF006433E2 /* OverrideInExtensionRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D40FE89C1F867BFF006433E2 /* OverrideInExtensionRule.swift */; };
D4130D971E16183F00242361 /* IdentifierNameRuleExamples.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4130D961E16183F00242361 /* IdentifierNameRuleExamples.swift */; };
D4130D991E16CC1300242361 /* TypeNameRuleExamples.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4130D981E16CC1300242361 /* TypeNameRuleExamples.swift */; };
D41B57781ED8CEE0007B0470 /* ExtensionAccessModifierRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D41B57771ED8CEE0007B0470 /* ExtensionAccessModifierRule.swift */; };
Expand Down Expand Up @@ -180,6 +181,7 @@
D4998DE71DF191380006E05D /* AttributesRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4998DE61DF191380006E05D /* AttributesRuleTests.swift */; };
D4998DE91DF194F20006E05D /* FileHeaderRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4998DE81DF194F20006E05D /* FileHeaderRuleTests.swift */; };
D4A893351E15824100BF954D /* SwiftVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4A893341E15824100BF954D /* SwiftVersion.swift */; };
D4AB0EA21F8993DD00CEC380 /* NamespaceCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4AB0EA11F8993DD00CEC380 /* NamespaceCollector.swift */; };
D4B0226F1E0C75F9007E5297 /* VerticalParameterAlignmentRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4B0226E1E0C75F9007E5297 /* VerticalParameterAlignmentRule.swift */; };
D4B0228E1E0CC608007E5297 /* ClassDelegateProtocolRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4B0228D1E0CC608007E5297 /* ClassDelegateProtocolRule.swift */; };
D4B022961E0EF80C007E5297 /* RedundantOptionalInitializationRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4B022951E0EF80C007E5297 /* RedundantOptionalInitializationRule.swift */; };
Expand Down Expand Up @@ -485,6 +487,7 @@
D40AD0891E032F9700F48C30 /* UnusedClosureParameterRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnusedClosureParameterRule.swift; sourceTree = "<group>"; };
D40E041B1F46E3B30043BC4E /* SuperfluousDisableCommandRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SuperfluousDisableCommandRule.swift; sourceTree = "<group>"; };
D40F83871DE9179200524C62 /* TrailingCommaConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrailingCommaConfiguration.swift; sourceTree = "<group>"; };
D40FE89C1F867BFF006433E2 /* OverrideInExtensionRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverrideInExtensionRule.swift; sourceTree = "<group>"; };
D4130D961E16183F00242361 /* IdentifierNameRuleExamples.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentifierNameRuleExamples.swift; sourceTree = "<group>"; };
D4130D981E16CC1300242361 /* TypeNameRuleExamples.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypeNameRuleExamples.swift; sourceTree = "<group>"; };
D41B57771ED8CEE0007B0470 /* ExtensionAccessModifierRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtensionAccessModifierRule.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -528,6 +531,7 @@
D4998DE61DF191380006E05D /* AttributesRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttributesRuleTests.swift; sourceTree = "<group>"; };
D4998DE81DF194F20006E05D /* FileHeaderRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileHeaderRuleTests.swift; sourceTree = "<group>"; };
D4A893341E15824100BF954D /* SwiftVersion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftVersion.swift; sourceTree = "<group>"; };
D4AB0EA11F8993DD00CEC380 /* NamespaceCollector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NamespaceCollector.swift; sourceTree = "<group>"; };
D4B0226E1E0C75F9007E5297 /* VerticalParameterAlignmentRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalParameterAlignmentRule.swift; sourceTree = "<group>"; };
D4B0228D1E0CC608007E5297 /* ClassDelegateProtocolRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClassDelegateProtocolRule.swift; sourceTree = "<group>"; };
D4B022951E0EF80C007E5297 /* RedundantOptionalInitializationRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantOptionalInitializationRule.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -725,6 +729,7 @@
isa = PBXGroup;
children = (
4DCB8E7D1CBE43640070FCF0 /* RegexHelpers.swift */,
D4AB0EA11F8993DD00CEC380 /* NamespaceCollector.swift */,
);
path = Helpers;
sourceTree = "<group>";
Expand Down Expand Up @@ -1052,6 +1057,7 @@
692B1EB11BD7E00F00EAABFF /* OpeningBraceRule.swift */,
E5A167C81B25A0B000CF2D03 /* OperatorFunctionWhitespaceRule.swift */,
D4FBADCF1E00DA0400669C73 /* OperatorUsageWhitespaceRule.swift */,
D40FE89C1F867BFF006433E2 /* OverrideInExtensionRule.swift */,
78F032441D7C877800BE709A /* OverriddenSuperCallRule.swift */,
D403A4A21F4DB5510020CA02 /* PatternMatchingKeywordsRule.swift */,
094385021D5D4F78009168CF /* PrivateOutletRule.swift */,
Expand Down Expand Up @@ -1509,6 +1515,7 @@
D4A893351E15824100BF954D /* SwiftVersion.swift in Sources */,
D4470D5D1EB8004B008A1B2E /* VerticalParameterAlignmentOnCallRule.swift in Sources */,
D4DABFD31E29B4A5009617B6 /* DiscardedNotificationCenterObserverRule.swift in Sources */,
D4AB0EA21F8993DD00CEC380 /* NamespaceCollector.swift in Sources */,
D4B022B21E10B613007E5297 /* RedundantVoidReturnRule.swift in Sources */,
3BCC04D21C4F56D3006073C3 /* NameConfiguration.swift in Sources */,
D4C27BFE1E12D53F00DF713E /* Version.swift in Sources */,
Expand Down Expand Up @@ -1547,6 +1554,7 @@
E881985B1BEA974E00333A11 /* StatementPositionRule.swift in Sources */,
B58AEED61C492C7B00E901FD /* ForceUnwrappingRule.swift in Sources */,
1EF115921EB2AD5900E30140 /* ExplicitTopLevelACLRule.swift in Sources */,
D40FE89D1F867BFF006433E2 /* OverrideInExtensionRule.swift in Sources */,
D41E7E0B1DF9DABB0065259A /* RedundantStringEnumValueRule.swift in Sources */,
E88DEA711B09847500A66CB0 /* ViolationSeverity.swift in Sources */,
1E3C2D711EE36C6F00C8386D /* PrivateOverFilePrivateRule.swift in Sources */,
Expand Down
1 change: 1 addition & 0 deletions Tests/LinuxMain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,7 @@ extension RulesTests {
("testOpeningBrace", testOpeningBrace),
("testOperatorFunctionWhitespace", testOperatorFunctionWhitespace),
("testOperatorUsageWhitespace", testOperatorUsageWhitespace),
("testOverrideInExtension", testOverrideInExtension),
("testPatternMatchingKeywords", testPatternMatchingKeywords),
("testPrivateOutlet", testPrivateOutlet),
("testPrivateUnitTest", testPrivateUnitTest),
Expand Down
4 changes: 4 additions & 0 deletions Tests/SwiftLintFrameworkTests/RulesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,10 @@ class RulesTests: XCTestCase {
verifyRule(OperatorUsageWhitespaceRule.description)
}

func testOverrideInExtension() {
verifyRule(OverrideInExtensionRule.description)
}

func testPatternMatchingKeywords() {
verifyRule(PatternMatchingKeywordsRule.description)
}
Expand Down

0 comments on commit 98c0fdd

Please sign in to comment.