Files
SwiftLint/Source/SwiftLintBuiltInRules/Rules/Performance/EmptyCountRule.swift
Danny Mösch 40bd97038a Support arbitrary configurations in @SwiftSyntaxRule (#5275)
Almost all rules based on SwiftSyntax can be set up now by just adding
`@SwiftSyntaxRule` to the rule struct.
2023-10-16 19:34:43 +02:00

92 lines
3.2 KiB
Swift

import SwiftSyntax
@SwiftSyntaxRule(foldExpressions: true)
struct EmptyCountRule: OptInRule {
var configuration = EmptyCountConfiguration()
static let description = RuleDescription(
identifier: "empty_count",
name: "Empty Count",
description: "Prefer checking `isEmpty` over comparing `count` to zero",
kind: .performance,
nonTriggeringExamples: [
Example("var count = 0"),
Example("[Int]().isEmpty"),
Example("[Int]().count > 1"),
Example("[Int]().count == 1"),
Example("[Int]().count == 0xff"),
Example("[Int]().count == 0b01"),
Example("[Int]().count == 0o07"),
Example("discount == 0"),
Example("order.discount == 0")
],
triggeringExamples: [
Example("[Int]().↓count == 0"),
Example("0 == [Int]().↓count"),
Example("[Int]().↓count==0"),
Example("[Int]().↓count > 0"),
Example("[Int]().↓count != 0"),
Example("[Int]().↓count == 0x0"),
Example("[Int]().↓count == 0x00_00"),
Example("[Int]().↓count == 0b00"),
Example("[Int]().↓count == 0o00"),
Example("↓count == 0")
]
)
}
private extension EmptyCountRule {
final class Visitor: ViolationsSyntaxVisitor<ConfigurationType> {
private let operators: Set = ["==", "!=", ">", ">=", "<", "<="]
override func visitPost(_ node: InfixOperatorExprSyntax) {
guard let operatorNode = node.operator.as(BinaryOperatorExprSyntax.self),
let binaryOperator = operatorNode.operator.binaryOperator,
operators.contains(binaryOperator) else {
return
}
if let intExpr = node.rightOperand.as(IntegerLiteralExprSyntax.self), intExpr.isZero,
let position = node.leftOperand.countCallPosition(onlyAfterDot: configuration.onlyAfterDot) {
violations.append(position)
return
}
if let intExpr = node.leftOperand.as(IntegerLiteralExprSyntax.self), intExpr.isZero,
let position = node.rightOperand.countCallPosition(onlyAfterDot: configuration.onlyAfterDot) {
violations.append(position)
return
}
}
}
}
private extension ExprSyntax {
func countCallPosition(onlyAfterDot: Bool) -> AbsolutePosition? {
if let expr = self.as(MemberAccessExprSyntax.self) {
if expr.declName.argumentNames == nil && expr.declName.baseName.tokenKind == .identifier("count") {
return expr.declName.baseName.positionAfterSkippingLeadingTrivia
}
return nil
}
if !onlyAfterDot, let expr = self.as(DeclReferenceExprSyntax.self) {
return expr.baseName.tokenKind == .identifier("count") ? expr.positionAfterSkippingLeadingTrivia : nil
}
return nil
}
}
private extension TokenSyntax {
var binaryOperator: String? {
switch tokenKind {
case .binaryOperator(let str):
return str
default:
return nil
}
}
}