-
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
void_function_in_ternary
opt-in rule
Fixes #2358
- Loading branch information
1 parent
8d9c501
commit f8ac10e
Showing
6 changed files
with
132 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
110 changes: 110 additions & 0 deletions
110
Source/SwiftLintFramework/Rules/Idiomatic/VoidFunctionInTernaryConditionRule.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,110 @@ | ||
import Foundation | ||
import SourceKittenFramework | ||
#if canImport(SwiftSyntax) | ||
import SwiftSyntax | ||
#endif | ||
|
||
public struct VoidFunctionInTernaryConditionRule: ConfigurationProviderRule, SyntaxRule, AutomaticTestableRule { | ||
public var configuration = SeverityConfiguration(.warning) | ||
|
||
public init() {} | ||
|
||
public static let description = RuleDescription( | ||
identifier: "void_function_in_ternary", | ||
name: "VoidFunctionInTernaryConditionRule", | ||
description: "Returning values Void functions should be avoided.", | ||
kind: .idiomatic, | ||
minSwiftVersion: .fiveDotOne, | ||
nonTriggeringExamples: [ | ||
Example("let result = success ? foo() : bar()"), | ||
Example(""" | ||
if success { | ||
askQuestion() | ||
} else { | ||
exit() | ||
} | ||
"""), | ||
Example(""" | ||
var price: Double { | ||
return hasDiscount ? calculatePriceWithDiscount() : calculateRegularPrice() | ||
} | ||
"""), | ||
Example("foo(x == 2 ? a() : b())"), | ||
Example(""" | ||
chevronView.image = collapsed ? .icon(.mediumChevronDown) : .icon(.mediumChevronUp) | ||
"""), | ||
Example(""" | ||
array.map { elem in | ||
elem.isEmpty() ? .emptyValue() : .number(elem) | ||
} | ||
""") | ||
], | ||
triggeringExamples: [ | ||
Example("success ↓? askQuestion() : exit()"), | ||
Example(""" | ||
perform { elem in | ||
elem.isEmpty() ↓? .emptyValue() : .number(elem) | ||
return 1 | ||
} | ||
"""), | ||
Example(""" | ||
DispatchQueue.main.async { | ||
self.sectionViewModels[section].collapsed.toggle() | ||
self.sectionViewModels[section].collapsed | ||
↓? self.tableView.deleteRows(at: [IndexPath(row: 0, section: section)], with: .automatic) | ||
: self.tableView.insertRows(at: [IndexPath(row: 0, section: section)], with: .automatic) | ||
self.tableView.scrollToRow(at: IndexPath(row: NSNotFound, section: section), at: .top, animated: true) | ||
} | ||
""") | ||
] | ||
) | ||
|
||
public func validate(file: SwiftLintFile) -> [StyleViolation] { | ||
#if canImport(SwiftSyntax) | ||
return validate(file: file, visitor: TernaryVisitor()) | ||
#else | ||
return [] | ||
#endif | ||
} | ||
} | ||
|
||
#if canImport(SwiftSyntax) | ||
private class TernaryVisitor: SyntaxRuleVisitor { | ||
private var positions = [AbsolutePosition]() | ||
|
||
func visit(_ node: TernaryExprSyntax) -> SyntaxVisitorContinueKind { | ||
if node.firstChoice is FunctionCallExprSyntax, node.secondChoice is FunctionCallExprSyntax, | ||
let parent = node.parent as? ExprListSyntax, !parent.containsAssignment, | ||
parent.parent is SequenceExprSyntax, | ||
let blockItem = parent.parent?.parent as? CodeBlockItemSyntax, !blockItem.isClosureImplictReturn { | ||
positions.append(node.questionMark.positionAfterSkippingLeadingTrivia) | ||
} | ||
|
||
return .visitChildren | ||
} | ||
|
||
func violations(for rule: VoidFunctionInTernaryConditionRule, 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 ExprListSyntax { | ||
var containsAssignment: Bool { | ||
return children.contains(where: { $0 is AssignmentExprSyntax }) | ||
} | ||
} | ||
|
||
private extension CodeBlockItemSyntax { | ||
var isClosureImplictReturn: Bool { | ||
guard let parent = parent as? CodeBlockItemListSyntax else { | ||
return false | ||
} | ||
|
||
return Array(parent.children).count == 1 && parent.parent is ClosureExprSyntax | ||
} | ||
} | ||
#endif |
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