SwiftLint/Source/SwiftLintBuiltInRules/Rules/Performance/SortedFirstLastRule.swift

71 lines
3.5 KiB
Swift

import SwiftSyntax
struct SortedFirstLastRule: SwiftSyntaxRule, OptInRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "sorted_first_last",
name: "Min or Max over Sorted First or Last",
description: "Prefer using `min()` or `max()` over `sorted().first` or `sorted().last`",
kind: .performance,
nonTriggeringExamples: [
Example("let min = myList.min()\n"),
Example("let min = myList.min(by: { $0 < $1 })\n"),
Example("let min = myList.min(by: >)\n"),
Example("let max = myList.max()\n"),
Example("let max = myList.max(by: { $0 < $1 })\n"),
Example("let message = messages.sorted(byKeyPath: #keyPath(Message.timestamp)).last"),
Example(#"let message = messages.sorted(byKeyPath: "timestamp", ascending: false).first"#),
Example("myList.sorted().firstIndex(of: key)"),
Example("myList.sorted().lastIndex(of: key)"),
Example("myList.sorted().firstIndex(where: someFunction)"),
Example("myList.sorted().lastIndex(where: someFunction)"),
Example("myList.sorted().firstIndex { $0 == key }"),
Example("myList.sorted().lastIndex { $0 == key }"),
Example("myList.sorted().first(where: someFunction)"),
Example("myList.sorted().last(where: someFunction)"),
Example("myList.sorted().first { $0 == key }"),
Example("myList.sorted().last { $0 == key }")
],
triggeringExamples: [
Example("↓myList.sorted().first\n"),
Example("↓myList.sorted(by: { $0.description < $1.description }).first\n"),
Example("↓myList.sorted(by: >).first\n"),
Example("↓myList.map { $0 + 1 }.sorted().first\n"),
Example("↓myList.sorted(by: someFunction).first\n"),
Example("↓myList.map { $0 + 1 }.sorted { $0.description < $1.description }.first\n"),
Example("↓myList.sorted().last\n"),
Example("↓myList.sorted().last?.something()\n"),
Example("↓myList.sorted(by: { $0.description < $1.description }).last\n"),
Example("↓myList.map { $0 + 1 }.sorted().last\n"),
Example("↓myList.sorted(by: someFunction).last\n"),
Example("↓myList.map { $0 + 1 }.sorted { $0.description < $1.description }.last\n"),
Example("↓myList.map { $0 + 1 }.sorted { $0.first < $1.first }.last\n")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
}
private extension SortedFirstLastRule {
final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: MemberAccessExprSyntax) {
guard
node.name.text == "first" || node.name.text == "last",
node.parent?.is(FunctionCallExprSyntax.self) != true,
let firstBase = node.base?.asFunctionCall,
let firstBaseCalledExpression = firstBase.calledExpression.as(MemberAccessExprSyntax.self),
firstBaseCalledExpression.name.text == "sorted",
case let argumentLabels = firstBase.argumentList.map({ $0.label?.text }),
argumentLabels.isEmpty || argumentLabels == ["by"]
else {
return
}
violations.append(node.positionAfterSkippingLeadingTrivia)
}
}
}