-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #809 from angelolloqui/feature/super_rule
Implemented super call rule (#803)
- Loading branch information
Showing
7 changed files
with
285 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
114 changes: 114 additions & 0 deletions
114
Source/SwiftLintFramework/Rules/OverridenSuperCallRule.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
// | ||
// OverridenSuperCallRule.swift | ||
// SwiftLint | ||
// | ||
// Created by Angel Garcia on 04/09/16. | ||
// Copyright © 2016 Realm. All rights reserved. | ||
// | ||
|
||
import SourceKittenFramework | ||
|
||
public struct OverridenSuperCallRule: ConfigurationProviderRule, ASTRule, OptInRule { | ||
public var configuration = OverridenSuperCallConfiguration() | ||
|
||
public init() { } | ||
|
||
public static let description = RuleDescription( | ||
identifier: "overridden_super_call", | ||
name: "Overridden methods call super", | ||
description: "Some Overridden methods should always call super", | ||
nonTriggeringExamples: [ | ||
"class VC: UIViewController {\n" + | ||
"\toverride func viewWillAppear(_ animated: Bool) {\n" + | ||
"\t\tsuper.viewWillAppear(animated)\n" + | ||
"\t}\n" + | ||
"}\n", | ||
"class VC: UIViewController {\n" + | ||
"\toverride func viewWillAppear(_ animated: Bool) {\n" + | ||
"\t\tself.method1()\n" + | ||
"\t\tsuper.viewWillAppear(animated)\n" + | ||
"\t\tself.method2()\n" + | ||
"\t}\n" + | ||
"}\n", | ||
"class VC: UIViewController {\n" + | ||
"\toverride func loadView() {\n" + | ||
"\t}\n" + | ||
"}\n", | ||
"class Some {\n" + | ||
"\tfunc viewWillAppear(_ animated: Bool) {\n" + | ||
"\t}\n" + | ||
"}\n" | ||
], | ||
triggeringExamples: [ | ||
"class VC: UIViewController {\n" + | ||
"\toverride func viewWillAppear(_ animated: Bool) ↓{\n" + | ||
"\t\t//Not calling to super\n" + | ||
"\t\tself.method()\n" + | ||
"\t}\n" + | ||
"}\n", | ||
"class VC: UIViewController {\n" + | ||
"\toverride func viewWillAppear(_ animated: Bool) ↓{\n" + | ||
"\t\tsuper.viewWillAppear(animated)\n" + | ||
"\t\t//Other code\n" + | ||
"\t\tsuper.viewWillAppear(animated)\n" + | ||
"\t}\n" + | ||
"}\n", | ||
"class VC: UIViewController {\n" + | ||
"\toverride func didReceiveMemoryWarning() ↓{\n" + | ||
"\t}\n" + | ||
"}\n" | ||
] | ||
) | ||
|
||
public func validateFile(file: File, | ||
kind: SwiftDeclarationKind, | ||
dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] { | ||
|
||
guard let offset = dictionary["key.bodyoffset"] as? Int64, | ||
let name = dictionary["key.name"] as? String | ||
else { return [] } | ||
|
||
let substructure = (dictionary["key.substructure"] as? [SourceKitRepresentable]) ?? [] | ||
guard kind == .FunctionMethodInstance && | ||
configuration.resolvedMethodNames.contains(name) && | ||
extractAttributes(dictionary).contains("source.decl.attribute.override") | ||
else { return [] } | ||
|
||
let callsToSuper = extractCallsToSuper(name, substructure: substructure) | ||
|
||
if callsToSuper.isEmpty { | ||
return [StyleViolation(ruleDescription: self.dynamicType.description, | ||
severity: configuration.severity, | ||
location: Location(file: file, byteOffset: Int(offset)), | ||
reason: "Method '\(name)' should call to super function")] | ||
} else if callsToSuper.count > 1 { | ||
return [StyleViolation(ruleDescription: self.dynamicType.description, | ||
severity: configuration.severity, | ||
location: Location(file: file, byteOffset: Int(offset)), | ||
reason: "Method '\(name)' should call to super only once")] | ||
} | ||
return [] | ||
} | ||
|
||
private func extractAttributes(dictionary: [String: SourceKitRepresentable]) -> [String] { | ||
guard let attributesDict = dictionary["key.attributes"] as? [SourceKitRepresentable] | ||
else { return [] } | ||
return attributesDict.flatMap { | ||
($0 as? [String: SourceKitRepresentable])?["key.attribute"] as? String | ||
} | ||
} | ||
|
||
private func extractCallsToSuper(name: String, | ||
substructure: [SourceKitRepresentable]) -> [String] { | ||
let superCall = "super.\(name)" | ||
return substructure.flatMap { | ||
guard let elems = $0 as? [String: SourceKitRepresentable], | ||
type = elems["key.kind"] as? String, | ||
name = elems["key.name"] as? String | ||
where type == "source.lang.swift.expr.call" && | ||
superCall.containsString(name) | ||
else { return nil } | ||
return name | ||
} | ||
} | ||
} |
101 changes: 101 additions & 0 deletions
101
Source/SwiftLintFramework/Rules/RuleConfigurations/OverridenSuperCallConfiguration.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
// | ||
// OverridenSuperCallConfiguration.swift | ||
// SwiftLint | ||
// | ||
// Created by Angel Garcia on 05/09/16. | ||
// Copyright © 2016 Realm. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
public struct OverridenSuperCallConfiguration: RuleConfiguration, Equatable { | ||
var defaultIncluded = [ | ||
//NSObject | ||
"awakeFromNib()", | ||
"prepareForInterfaceBuilder()", | ||
//UICollectionViewLayout | ||
"invalidateLayout()", | ||
"invalidateLayout(with:)", | ||
"invalidateLayoutWithContext(_:)", | ||
//UIView | ||
"prepareForReuse()", | ||
"updateConstraints()", | ||
//UIViewController | ||
"addChildViewController(_:)", | ||
"decodeRestorableState(with:)", | ||
"decodeRestorableStateWithCoder(_:)", | ||
"didReceiveMemoryWarning()", | ||
"encodeRestorableState(with:)", | ||
"encodeRestorableStateWithCoder(_:)", | ||
"removeFromParentViewController()", | ||
"setEditing(_:animated:)", | ||
"transition(from:to:duration:options:animations:completion:)", | ||
"transitionCoordinator()", | ||
"transitionFromViewController(_:toViewController:duration:options:animations:completion:)", | ||
"viewDidAppear(_:)", | ||
"viewDidDisappear(_:)", | ||
"viewDidLoad()", | ||
"viewWillAppear(_:)", | ||
"viewWillDisappear(_:)", | ||
//XCTestCase | ||
"setUp()", | ||
"tearDown()" | ||
] | ||
|
||
var severityConfiguration = SeverityConfiguration(.Warning) | ||
var excluded: [String] = [] | ||
var included: [String] = ["*"] | ||
|
||
public private(set) var resolvedMethodNames: [String] | ||
|
||
init() { | ||
resolvedMethodNames = defaultIncluded | ||
} | ||
|
||
public var consoleDescription: String { | ||
return severityConfiguration.consoleDescription + | ||
", excluded: [\(excluded)]" + | ||
", included: [\(included)]" | ||
} | ||
|
||
public mutating func applyConfiguration(configuration: AnyObject) throws { | ||
guard let configuration = configuration as? [String: AnyObject] else { | ||
throw ConfigurationError.UnknownConfiguration | ||
} | ||
|
||
if let severityString = configuration["severity"] as? String { | ||
try severityConfiguration.applyConfiguration(severityString) | ||
} | ||
|
||
if let excluded = [String].arrayOf(configuration["excluded"]) { | ||
self.excluded = excluded | ||
} | ||
|
||
if let included = [String].arrayOf(configuration["included"]) { | ||
self.included = included | ||
} | ||
|
||
self.resolvedMethodNames = calculateResolvedMethodNames() | ||
} | ||
|
||
public var severity: ViolationSeverity { | ||
return severityConfiguration.severity | ||
} | ||
|
||
private func calculateResolvedMethodNames() -> [String] { | ||
var names: [String] = [] | ||
if included.contains("*") && !excluded.contains("*") { | ||
names += defaultIncluded | ||
} | ||
names += included.filter({ $0 != "*" }) | ||
names = names.filter { !excluded.contains($0) } | ||
return names | ||
} | ||
} | ||
|
||
public func == (lhs: OverridenSuperCallConfiguration, | ||
rhs: OverridenSuperCallConfiguration) -> Bool { | ||
return lhs.excluded == rhs.excluded && | ||
lhs.included == rhs.included && | ||
lhs.severityConfiguration == rhs.severityConfiguration | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters