-
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.
Add back return_value_from_void_function rule (#3882)
- Loading branch information
1 parent
291b500
commit a786e31
Showing
8 changed files
with
334 additions
and
15 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
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
72 changes: 72 additions & 0 deletions
72
Source/SwiftLintFramework/Rules/Idiomatic/ReturnValueFromVoidFunctionRule.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,72 @@ | ||
import SourceKittenFramework | ||
import SwiftSyntax | ||
|
||
public struct ReturnValueFromVoidFunctionRule: ConfigurationProviderRule, OptInRule, AutomaticTestableRule { | ||
public var configuration = SeverityConfiguration(.warning) | ||
|
||
public init() {} | ||
|
||
public static let description = RuleDescription( | ||
identifier: "return_value_from_void_function", | ||
name: "Return Value from Void Function", | ||
description: "Returning values from Void functions should be avoided.", | ||
kind: .idiomatic, | ||
minSwiftVersion: .fiveDotOne, | ||
nonTriggeringExamples: ReturnValueFromVoidFunctionRuleExamples.nonTriggeringExamples, | ||
triggeringExamples: ReturnValueFromVoidFunctionRuleExamples.triggeringExamples | ||
) | ||
|
||
public func validate(file: SwiftLintFile) -> [StyleViolation] { | ||
guard let tree = file.syntaxTree else { | ||
return [] | ||
} | ||
|
||
let visitor = ReturnValueFromVoidFunctionVisitor() | ||
visitor.walk(tree) | ||
return visitor.violations(for: self, in: file) | ||
} | ||
} | ||
|
||
private final class ReturnValueFromVoidFunctionVisitor: SyntaxVisitor { | ||
private var positions = [AbsolutePosition]() | ||
|
||
override func visitPost(_ node: ReturnStmtSyntax) { | ||
if node.expression != nil, | ||
let functionNode = Syntax(node).enclosingFunction(), | ||
functionNode.returnsVoid { | ||
positions.append(node.positionAfterSkippingLeadingTrivia) | ||
} | ||
} | ||
|
||
func violations(for rule: ReturnValueFromVoidFunctionRule, in file: SwiftLintFile) -> [StyleViolation] { | ||
return positions.map { position in | ||
StyleViolation(ruleDescription: type(of: rule).description, | ||
severity: rule.configuration.severity, | ||
location: Location(file: file, byteOffset: ByteCount(position.utf8Offset))) | ||
} | ||
} | ||
} | ||
|
||
private extension Syntax { | ||
func enclosingFunction() -> FunctionDeclSyntax? { | ||
if let node = self.as(FunctionDeclSyntax.self) { | ||
return node | ||
} | ||
|
||
if self.is(ClosureExprSyntax.self) || self.is(VariableDeclSyntax.self) { | ||
return nil | ||
} | ||
|
||
return parent?.enclosingFunction() | ||
} | ||
} | ||
|
||
private extension FunctionDeclSyntax { | ||
var returnsVoid: Bool { | ||
if let type = signature.output?.returnType.as(SimpleTypeIdentifierSyntax.self) { | ||
return type.name.text == "Void" | ||
} | ||
|
||
return signature.output?.returnType == nil | ||
} | ||
} |
234 changes: 234 additions & 0 deletions
234
Source/SwiftLintFramework/Rules/Idiomatic/ReturnValueFromVoidFunctionRuleExamples.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,234 @@ | ||
// swiftlint:disable:next type_body_length | ||
internal struct ReturnValueFromVoidFunctionRuleExamples { | ||
static let nonTriggeringExamples = [ | ||
Example(""" | ||
func foo() { | ||
return | ||
} | ||
"""), | ||
Example(""" | ||
func foo() { | ||
return /* a comment */ | ||
} | ||
"""), | ||
Example(""" | ||
func foo() -> Int { | ||
return 1 | ||
} | ||
"""), | ||
Example(""" | ||
func foo() -> Void { | ||
if condition { | ||
return | ||
} | ||
bar() | ||
} | ||
"""), | ||
Example(""" | ||
func foo() { | ||
return; | ||
bar() | ||
} | ||
"""), | ||
Example("func test() {}"), | ||
Example(""" | ||
init?() { | ||
guard condition else { | ||
return nil | ||
} | ||
} | ||
"""), | ||
Example(""" | ||
init?(arg: String?) { | ||
guard arg != nil else { | ||
return nil | ||
} | ||
} | ||
"""), | ||
Example(""" | ||
func test() { | ||
guard condition else { | ||
return | ||
} | ||
} | ||
"""), | ||
Example(""" | ||
func test() -> Result<String, Error> { | ||
func other() {} | ||
func otherVoid() -> Void {} | ||
} | ||
"""), | ||
Example(""" | ||
func test() -> Int? { | ||
return nil | ||
} | ||
"""), | ||
Example(""" | ||
func test() { | ||
if bar { | ||
print("") | ||
return | ||
} | ||
let foo = [1, 2, 3].filter { return true } | ||
return | ||
} | ||
"""), | ||
Example(""" | ||
func test() { | ||
guard foo else { | ||
bar() | ||
return | ||
} | ||
} | ||
"""), | ||
Example(""" | ||
func spec() { | ||
var foo: Int { | ||
return 0 | ||
} | ||
""") | ||
] | ||
|
||
static let triggeringExamples = [ | ||
Example(""" | ||
func foo() { | ||
↓return bar() | ||
} | ||
"""), | ||
Example(""" | ||
func foo() { | ||
↓return self.bar() | ||
} | ||
"""), | ||
Example(""" | ||
func foo() -> Void { | ||
↓return bar() | ||
} | ||
"""), | ||
Example(""" | ||
func foo() -> Void { | ||
↓return /* comment */ bar() | ||
} | ||
"""), | ||
Example(""" | ||
func foo() { | ||
↓return | ||
self.bar() | ||
} | ||
"""), | ||
Example(""" | ||
func foo() { | ||
variable += 1 | ||
↓return | ||
variable += 1 | ||
} | ||
"""), | ||
Example(""" | ||
func initThing() { | ||
guard foo else { | ||
↓return print("") | ||
} | ||
} | ||
"""), | ||
Example(""" | ||
// Leading comment | ||
func test() { | ||
guard condition else { | ||
↓return assertionfailure("") | ||
} | ||
} | ||
"""), | ||
Example(""" | ||
func test() -> Result<String, Error> { | ||
func other() { | ||
guard false else { | ||
↓return assertionfailure("") | ||
} | ||
} | ||
func otherVoid() -> Void {} | ||
} | ||
"""), | ||
Example(""" | ||
func test() { | ||
guard conditionIsTrue else { | ||
sideEffects() | ||
return // comment | ||
} | ||
guard otherCondition else { | ||
↓return assertionfailure("") | ||
} | ||
differentSideEffect() | ||
} | ||
"""), | ||
Example(""" | ||
func test() { | ||
guard otherCondition else { | ||
↓return assertionfailure(""); // comment | ||
} | ||
differentSideEffect() | ||
} | ||
"""), | ||
Example(""" | ||
func test() { | ||
if x { | ||
↓return foo() | ||
} | ||
bar() | ||
} | ||
"""), | ||
Example(""" | ||
func test() { | ||
switch x { | ||
case .a: | ||
↓return foo() // return to skip baz() | ||
case .b: | ||
bar() | ||
} | ||
baz() | ||
} | ||
"""), | ||
Example(""" | ||
func test() { | ||
if check { | ||
if otherCheck { | ||
↓return foo() | ||
} | ||
} | ||
bar() | ||
} | ||
"""), | ||
Example(""" | ||
func test() { | ||
↓return foo() | ||
} | ||
"""), | ||
Example(""" | ||
func test() { | ||
↓return foo({ | ||
return bar() | ||
}) | ||
} | ||
"""), | ||
Example(""" | ||
func test() { | ||
guard x else { | ||
↓return foo() | ||
} | ||
bar() | ||
} | ||
"""), | ||
Example(""" | ||
func test() { | ||
let closure: () -> () = { | ||
return assert() | ||
} | ||
if check { | ||
if otherCheck { | ||
return // comments are fine | ||
} | ||
} | ||
↓return foo() | ||
} | ||
""") | ||
] | ||
} |
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
Oops, something went wrong.