mirror of
https://github.com/realm/SwiftLint.git
synced 2026-05-07 20:12:49 +00:00
184 lines
6.1 KiB
Swift
184 lines
6.1 KiB
Swift
import SwiftSyntax
|
|
|
|
@SwiftSyntaxRule(optIn: true)
|
|
struct ReduceIntoRule: Rule {
|
|
var configuration = SeverityConfiguration<Self>(.warning)
|
|
|
|
static let description = RuleDescription(
|
|
identifier: "reduce_into",
|
|
name: "Reduce into",
|
|
description: "Prefer `reduce(into:_:)` over `reduce(_:_:)` for copy-on-write types",
|
|
kind: .performance,
|
|
nonTriggeringExamples: [
|
|
Example("""
|
|
let foo = values.reduce(into: "abc") { $0 += "\\($1)" }
|
|
"""),
|
|
Example("""
|
|
values.reduce(into: Array<Int>()) { result, value in
|
|
result.append(value)
|
|
}
|
|
"""),
|
|
Example("""
|
|
let rows = violations.enumerated().reduce(into: "") { rows, indexAndViolation in
|
|
rows.append(generateSingleRow(for: indexAndViolation.1, at: indexAndViolation.0 + 1))
|
|
}
|
|
"""),
|
|
Example("""
|
|
zip(group, group.dropFirst()).reduce(into: []) { result, pair in
|
|
result.append(pair.0 + pair.1)
|
|
}
|
|
"""),
|
|
Example("""
|
|
let foo = values.reduce(into: [String: Int]()) { result, value in
|
|
result["\\(value)"] = value
|
|
}
|
|
"""),
|
|
Example("""
|
|
let foo = values.reduce(into: Dictionary<String, Int>.init()) { result, value in
|
|
result["\\(value)"] = value
|
|
}
|
|
"""),
|
|
Example("""
|
|
let foo = values.reduce(into: [Int](repeating: 0, count: 10)) { result, value in
|
|
result.append(value)
|
|
}
|
|
"""),
|
|
Example("""
|
|
let foo = values.reduce(MyClass()) { result, value in
|
|
result.handleValue(value)
|
|
return result
|
|
}
|
|
"""),
|
|
],
|
|
triggeringExamples: [
|
|
Example("""
|
|
let bar = values.↓reduce("abc") { $0 + "\\($1)" }
|
|
"""),
|
|
Example("""
|
|
values.↓reduce(Array<Int>()) { result, value in
|
|
result += [value]
|
|
}
|
|
"""),
|
|
Example("""
|
|
[1, 2, 3].↓reduce(Set<Int>()) { acc, value in
|
|
var result = acc
|
|
result.insert(value)
|
|
return result
|
|
}
|
|
"""),
|
|
Example("""
|
|
let rows = violations.enumerated().↓reduce("") { rows, indexAndViolation in
|
|
return rows + generateSingleRow(for: indexAndViolation.1, at: indexAndViolation.0 + 1)
|
|
}
|
|
"""),
|
|
Example("""
|
|
zip(group, group.dropFirst()).↓reduce([]) { result, pair in
|
|
result + [pair.0 + pair.1]
|
|
}
|
|
"""),
|
|
Example("""
|
|
let foo = values.↓reduce([String: Int]()) { result, value in
|
|
var result = result
|
|
result["\\(value)"] = value
|
|
return result
|
|
}
|
|
"""),
|
|
Example("""
|
|
let bar = values.↓reduce(Dictionary<String, Int>.init()) { result, value in
|
|
var result = result
|
|
result["\\(value)"] = value
|
|
return result
|
|
}
|
|
"""),
|
|
Example("""
|
|
let bar = values.↓reduce([Int](repeating: 0, count: 10)) { result, value in
|
|
return result + [value]
|
|
}
|
|
"""),
|
|
Example("""
|
|
extension Data {
|
|
var hexString: String {
|
|
return ↓reduce("") { (output, byte) -> String in
|
|
output + String(format: "%02x", byte)
|
|
}
|
|
}
|
|
}
|
|
"""),
|
|
]
|
|
)
|
|
}
|
|
|
|
private extension ReduceIntoRule {
|
|
final class Visitor: ViolationsSyntaxVisitor<ConfigurationType> {
|
|
override func visitPost(_ node: FunctionCallExprSyntax) {
|
|
guard let name = node.nameToken,
|
|
name.text == "reduce",
|
|
node.arguments.count == 2 || (node.arguments.count == 1 && node.trailingClosure != nil),
|
|
let firstArgument = node.arguments.first,
|
|
// would otherwise equal "into"
|
|
firstArgument.label == nil,
|
|
firstArgument.expression.isCopyOnWriteType else {
|
|
return
|
|
}
|
|
|
|
violations.append(name.positionAfterSkippingLeadingTrivia)
|
|
}
|
|
}
|
|
}
|
|
|
|
private extension FunctionCallExprSyntax {
|
|
var nameToken: TokenSyntax? {
|
|
if let expr = calledExpression.as(MemberAccessExprSyntax.self) {
|
|
return expr.declName.baseName
|
|
}
|
|
if let expr = calledExpression.as(DeclReferenceExprSyntax.self) {
|
|
return expr.baseName
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
private extension ExprSyntax {
|
|
var isCopyOnWriteType: Bool {
|
|
if `is`(StringLiteralExprSyntax.self) || `is`(DictionaryExprSyntax.self) || `is`(ArrayExprSyntax.self) {
|
|
return true
|
|
}
|
|
|
|
if let expr = `as`(FunctionCallExprSyntax.self) {
|
|
if let identifierExpr = expr.calledExpression.identifierExpr {
|
|
return identifierExpr.isCopyOnWriteType
|
|
}
|
|
if let memberAccesExpr = expr.calledExpression.as(MemberAccessExprSyntax.self),
|
|
memberAccesExpr.declName.baseName.text == "init",
|
|
let identifierExpr = memberAccesExpr.base?.identifierExpr {
|
|
return identifierExpr.isCopyOnWriteType
|
|
}
|
|
if expr.calledExpression.isCopyOnWriteType {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
var identifierExpr: DeclReferenceExprSyntax? {
|
|
if let identifierExpr = `as`(DeclReferenceExprSyntax.self) {
|
|
return identifierExpr
|
|
}
|
|
if let specializeExpr = `as`(GenericSpecializationExprSyntax.self) {
|
|
return specializeExpr.expression.identifierExpr
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
private extension DeclReferenceExprSyntax {
|
|
private static let copyOnWriteTypes: Set = ["Array", "Dictionary", "Set"]
|
|
|
|
var isCopyOnWriteType: Bool {
|
|
Self.copyOnWriteTypes.contains(baseName.text)
|
|
}
|
|
}
|