Skip to content

Commit

Permalink
Rewrite overridden_super_call and prohibited_super_call with Swif…
Browse files Browse the repository at this point in the history
…tSyntax (realm#4493)

* Rewrite `overridden_super_call` with SwiftSyntax

* Rewrite `prohibited_super_call` too
  • Loading branch information
marcelofabri authored Oct 31, 2022
1 parent 697eaa7 commit 20bfe26
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 52 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@
- `number_separator`
- `operator_whitespace`
- `optional_enum_case_matching`
- `overridden_super_call`
- `override_in_extension`
- `prefer_nimble`
- `prefer_self_type_over_type_of_self`
Expand All @@ -141,6 +142,7 @@
- `private_subject`
- `private_unit_test`
- `prohibited_interface_builder`
- `prohibited_super_call`
- `protocol_property_accessors_order`
- `quick_discouraged_focused_test`
- `quick_discouraged_pending_test`
Expand Down
15 changes: 0 additions & 15 deletions Source/SwiftLintFramework/Extensions/Dictionary+SwiftLint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -186,21 +186,6 @@ public struct SourceKittenDictionary {
let array = value["key.inheritedtypes"] as? [SourceKitRepresentable] ?? []
return array.compactMap { ($0 as? [String: String]).flatMap { $0["key.name"] } }
}

internal func extractCallsToSuper(methodName: String) -> [String] {
guard let methodNameWithoutArguments = methodName.split(separator: "(").first else {
return []
}
let superCall = "super.\(methodNameWithoutArguments)"
return substructure.flatMap { elems -> [String] in
guard let type = elems.expressionKind,
let name = elems.name,
type == .call && superCall == name else {
return elems.extractCallsToSuper(methodName: methodName)
}
return [name]
}
}
}

extension SourceKittenDictionary {
Expand Down
45 changes: 45 additions & 0 deletions Source/SwiftLintFramework/Extensions/SwiftSyntax+SwiftLint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,31 @@ extension FunctionDeclSyntax {
attr.as(AttributeSyntax.self)?.attributeName.tokenKind == .identifier("IBAction")
} ?? false
}

/// Returns the signature including arguments, e.g "setEditing(_:animated:)"
func resolvedName() -> String {
var name = self.identifier.text
name += "("

let params = signature.input.parameterList.compactMap { param in
(param.firstName ?? param.secondName)?.text.appending(":")
}

name += params.joined()
name += ")"
return name
}

/// How many times this function calls the `super` implementation in its body.
/// Returns 0 if the function has no body.
func numberOfCallsToSuper() -> Int {
guard let body = body else {
return 0
}

return SuperCallVisitor(expectedFunctionName: identifier.text)
.walk(tree: body, handler: \.superCallsCount)
}
}

extension AccessorBlockSyntax {
Expand Down Expand Up @@ -230,3 +255,23 @@ private extension String {
return Float(number) == 0
}
}

private class SuperCallVisitor: SyntaxVisitor {
private let expectedFunctionName: String
private(set) var superCallsCount = 0

init(expectedFunctionName: String) {
self.expectedFunctionName = expectedFunctionName
super.init(viewMode: .sourceAccurate)
}

override func visitPost(_ node: FunctionCallExprSyntax) {
guard let expr = node.calledExpression.as(MemberAccessExprSyntax.self),
expr.base?.as(SuperRefExprSyntax.self) != nil,
expr.name.text == expectedFunctionName else {
return
}

superCallsCount += 1
}
}
61 changes: 39 additions & 22 deletions Source/SwiftLintFramework/Rules/Lint/OverriddenSuperCallRule.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import SourceKittenFramework
import SwiftSyntax

public struct OverriddenSuperCallRule: ConfigurationProviderRule, ASTRule, OptInRule {
public struct OverriddenSuperCallRule: ConfigurationProviderRule, SwiftSyntaxRule, OptInRule {
public var configuration = OverriddenSuperCallConfiguration()

public init() {}
Expand Down Expand Up @@ -76,28 +76,45 @@ public struct OverriddenSuperCallRule: ConfigurationProviderRule, ASTRule, OptIn
]
)

public func validate(file: SwiftLintFile, kind: SwiftDeclarationKind,
dictionary: SourceKittenDictionary) -> [StyleViolation] {
guard let offset = dictionary.bodyOffset,
let name = dictionary.name,
kind == .functionMethodInstance,
configuration.resolvedMethodNames.contains(name),
dictionary.enclosedSwiftAttributes.contains(.override)
else { return [] }
public func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(resolvedMethodNames: configuration.resolvedMethodNames)
}
}

let callsToSuper = dictionary.extractCallsToSuper(methodName: name)
private extension OverriddenSuperCallRule {
final class Visitor: ViolationsSyntaxVisitor {
private let resolvedMethodNames: [String]

if callsToSuper.isEmpty {
return [StyleViolation(ruleDescription: Self.description,
severity: configuration.severity,
location: Location(file: file, byteOffset: offset),
reason: "Method '\(name)' should call to super function")]
} else if callsToSuper.count > 1 {
return [StyleViolation(ruleDescription: Self.description,
severity: configuration.severity,
location: Location(file: file, byteOffset: offset),
reason: "Method '\(name)' should call to super only once")]
override var skippableDeclarations: [DeclSyntaxProtocol.Type] {
[ProtocolDeclSyntax.self]
}

init(resolvedMethodNames: [String]) {
self.resolvedMethodNames = resolvedMethodNames
super.init(viewMode: .sourceAccurate)
}

override func visitPost(_ node: FunctionDeclSyntax) {
guard let body = node.body,
node.modifiers.containsOverride,
!node.modifiers.containsStaticOrClass,
case let name = node.resolvedName(),
resolvedMethodNames.contains(name) else {
return
}

let superCallsCount = node.numberOfCallsToSuper()
if superCallsCount == 0 {
violations.append(ReasonedRuleViolation(
position: body.leftBrace.endPositionBeforeTrailingTrivia,
reason: "Method '\(name)' should call to super function"
))
} else if superCallsCount > 1 {
violations.append(ReasonedRuleViolation(
position: body.leftBrace.endPositionBeforeTrailingTrivia,
reason: "Method '\(name)' should call to super only once"
))
}
}
return []
}
}
49 changes: 34 additions & 15 deletions Source/SwiftLintFramework/Rules/Lint/ProhibitedSuperRule.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import SourceKittenFramework
import SwiftSyntax

public struct ProhibitedSuperRule: ConfigurationProviderRule, ASTRule, OptInRule {
public struct ProhibitedSuperRule: ConfigurationProviderRule, SwiftSyntaxRule, OptInRule {
public var configuration = ProhibitedSuperConfiguration()

public init() {}
Expand Down Expand Up @@ -72,19 +72,38 @@ public struct ProhibitedSuperRule: ConfigurationProviderRule, ASTRule, OptInRule
]
)

public func validate(file: SwiftLintFile, kind: SwiftDeclarationKind,
dictionary: SourceKittenDictionary) -> [StyleViolation] {
guard let offset = dictionary.bodyOffset,
let name = dictionary.name,
kind == .functionMethodInstance,
configuration.resolvedMethodNames.contains(name),
dictionary.enclosedSwiftAttributes.contains(.override),
dictionary.extractCallsToSuper(methodName: name).isNotEmpty
else { return [] }
public func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(resolvedMethodNames: configuration.resolvedMethodNames)
}
}

private extension ProhibitedSuperRule {
final class Visitor: ViolationsSyntaxVisitor {
private let resolvedMethodNames: [String]

override var skippableDeclarations: [DeclSyntaxProtocol.Type] {
[ProtocolDeclSyntax.self]
}

init(resolvedMethodNames: [String]) {
self.resolvedMethodNames = resolvedMethodNames
super.init(viewMode: .sourceAccurate)
}

override func visitPost(_ node: FunctionDeclSyntax) {
guard let body = node.body,
node.modifiers.containsOverride,
!node.modifiers.containsStaticOrClass,
case let name = node.resolvedName(),
resolvedMethodNames.contains(name),
node.numberOfCallsToSuper() > 0 else {
return
}

return [StyleViolation(ruleDescription: Self.description,
severity: configuration.severity,
location: Location(file: file, byteOffset: offset),
reason: "Method '\(name)' should not call to super function")]
violations.append(ReasonedRuleViolation(
position: body.leftBrace.endPositionBeforeTrailingTrivia,
reason: "Method '\(name)' should not call to super function"
))
}
}
}

0 comments on commit 20bfe26

Please sign in to comment.