diff --git a/CHANGELOG.md b/CHANGELOG.md index ed5a4a1b3c..4ce64a9118 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,12 @@ [Martin Redington](https://github.com/mildm8nnered) [#4792](https://github.com/realm/SwiftLint/issues/4792) +* With the introduction of the `consider_default_literal_types_redundant` + option to the `redundant_type_annotation` rule, `Bool` literals will no + longer be considered redundant by default. Set this option to true to + preserve the previous behavior. + [Garric Nahapetian](https://github.com/garricn) + #### Experimental * Add two new options to the `lint` and `analyze` commands: `--write-baseline` @@ -188,6 +194,14 @@ [Martin Redington](https://github.com/mildm8nnered) [#5470](https://github.com/realm/SwiftLint/issues/5470) +* Include `Double`, `Int` and `String` to the exiting redundant type validation + check of `Bool` in the `redundant_type_annotation` rule. Add + `consider_default_literal_types_redundant` option supporting `Bool`, + `Double`, `Int` and `String`. Setting this option to `true` lets the rule + consider said types in declarations like `let i: Int = 1` or + `let s: String = ""` as redundant. + [Garric Nahapetian](https://github.com/garricn) + #### Bug Fixes * Silence `discarded_notification_center_observer` rule in closures. Furthermore, diff --git a/Source/SwiftLintBuiltInRules/Rules/Idiomatic/RedundantTypeAnnotationRule.swift b/Source/SwiftLintBuiltInRules/Rules/Idiomatic/RedundantTypeAnnotationRule.swift index a75b3be334..5810619632 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Idiomatic/RedundantTypeAnnotationRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Idiomatic/RedundantTypeAnnotationRule.swift @@ -52,7 +52,11 @@ struct RedundantTypeAnnotationRule: OptInRule, SwiftSyntaxCorrectableRule { @IgnoreMe let i: Int = Int(1) return i } - """, configuration: ["ignore_attributes": ["IgnoreMe"]]) + """, configuration: ["ignore_attributes": ["IgnoreMe"]]), + Example("var bol: Bool = true"), + Example("var dbl: Double = 0.0"), + Example("var int: Int = 0"), + Example("var str: String = \"str\"") ], triggeringExamples: [ Example("var url↓:URL=URL()"), @@ -84,7 +88,6 @@ struct RedundantTypeAnnotationRule: OptInRule, SwiftSyntaxCorrectableRule { } } """), - Example("var isEnabled↓: Bool = true"), Example("let a↓: [Int] = [Int]()"), Example("let a↓: A.B = A.B()"), Example(""" @@ -102,7 +105,11 @@ struct RedundantTypeAnnotationRule: OptInRule, SwiftSyntaxCorrectableRule { let i↓: Int = Int(1) return i } - """, configuration: ["ignore_attributes": ["IgnoreMe"]]) + """, configuration: ["ignore_attributes": ["IgnoreMe"]]), + Example("var bol↓: Bool = true", configuration: ["consider_default_literal_types_redundant": true]), + Example("var dbl↓: Double = 0.0", configuration: ["consider_default_literal_types_redundant": true]), + Example("var int↓: Int = 0", configuration: ["consider_default_literal_types_redundant": true]), + Example("var str↓: String = \"str\"", configuration: ["consider_default_literal_types_redundant": true]) ], corrections: [ Example("var url↓: URL = URL()"): Example("var url = URL()"), @@ -159,7 +166,15 @@ struct RedundantTypeAnnotationRule: OptInRule, SwiftSyntaxCorrectableRule { let i = Int(1) return i } - """) + """), + Example("var bol: Bool = true", configuration: ["consider_default_literal_types_redundant": true]): + Example("var bol = true"), + Example("var dbl: Double = 0.0", configuration: ["consider_default_literal_types_redundant": true]): + Example("var dbl = 0.0"), + Example("var int: Int = 0", configuration: ["consider_default_literal_types_redundant": true]): + Example("var int = 0"), + Example("var str: String = \"str\"", configuration: ["consider_default_literal_types_redundant": true]): + Example("var str = \"str\"") ] ) } @@ -168,52 +183,44 @@ private extension RedundantTypeAnnotationRule { final class Visitor: ViolationsSyntaxVisitor { override func visitPost(_ node: PatternBindingSyntax) { guard let varDecl = node.parent?.parent?.as(VariableDeclSyntax.self), - configuration.ignoreAttributes.allSatisfy({ !varDecl.attributes.contains(attributeNamed: $0) }) else { + configuration.ignoreAttributes.allSatisfy({ !varDecl.attributes.contains(attributeNamed: $0) }), + let typeAnnotation = node.typeAnnotation, + let initializer = node.initializer?.value else { return } - if let typeAnnotation = node.typeAnnotation, - let initializer = node.initializer?.value, - typeAnnotation.isRedundant(with: initializer) { - violations.append(typeAnnotation.positionAfterSkippingLeadingTrivia) - violationCorrections.append(ViolationCorrection( - start: typeAnnotation.position, - end: typeAnnotation.endPositionBeforeTrailingTrivia, - replacement: "" - )) + let validateLiterals = configuration.considerDefaultLiteralTypesRedundant + let isLiteralRedundant = validateLiterals && initializer.hasRedundantLiteralType(typeAnnotation.type) + guard isLiteralRedundant || initializer.hasRedundantType(typeAnnotation.type) else { + return } + violations.append(typeAnnotation.positionAfterSkippingLeadingTrivia) + violationCorrections.append(ViolationCorrection( + start: typeAnnotation.position, + end: typeAnnotation.endPositionBeforeTrailingTrivia, + replacement: "" + )) } override func visitPost(_ node: OptionalBindingConditionSyntax) { - if let typeAnnotation = node.typeAnnotation, - let initializer = node.initializer?.value, - typeAnnotation.isRedundant(with: initializer) { - violations.append(typeAnnotation.positionAfterSkippingLeadingTrivia) - violationCorrections.append(ViolationCorrection( - start: typeAnnotation.position, - end: typeAnnotation.endPositionBeforeTrailingTrivia, - replacement: "" - )) + guard let typeAnnotation = node.typeAnnotation, + let initializer = node.initializer?.value else { + return } + let validateLiterals = configuration.considerDefaultLiteralTypesRedundant + let isLiteralRedundant = validateLiterals && initializer.hasRedundantLiteralType(typeAnnotation.type) + guard isLiteralRedundant || initializer.hasRedundantType(typeAnnotation.type) else { + return + } + violations.append(typeAnnotation.positionAfterSkippingLeadingTrivia) + violationCorrections.append(ViolationCorrection( + start: typeAnnotation.position, + end: typeAnnotation.endPositionBeforeTrailingTrivia, + replacement: "" + )) } } } -private extension TypeAnnotationSyntax { - func isRedundant(with initializerExpr: ExprSyntax) -> Bool { - var initializer = initializerExpr - if let forceUnwrap = initializer.as(ForceUnwrapExprSyntax.self) { - initializer = forceUnwrap.expression - } - - // If the initializer is a boolean expression, we consider using the `Bool` type - // annotation as redundant. - if initializer.is(BooleanLiteralExprSyntax.self) { - return type.trimmedDescription == "Bool" - } - return initializer.accessedNames.contains(type.trimmedDescription) - } -} - private extension ExprSyntax { /// An expression can represent an access to an identifier in one or another way depending on the exact underlying /// expression type. E.g. the expression `A` accesses `A` while `f()` accesses `f` and `a.b.c` accesses `a` in the @@ -233,4 +240,33 @@ private extension ExprSyntax { [] } } + + func hasRedundantLiteralType(_ type: TypeSyntax) -> Bool { + type.trimmedDescription == kind.compilerInferredLiteralType + } + + func hasRedundantType(_ type: TypeSyntax) -> Bool { + var expr = self + if let forceUnwrap = expr.as(ForceUnwrapExprSyntax.self) { + expr = forceUnwrap.expression + } + return expr.accessedNames.contains(type.trimmedDescription) + } +} + +private extension SyntaxKind { + var compilerInferredLiteralType: String? { + switch self { + case .booleanLiteralExpr: + "Bool" + case .floatLiteralExpr: + "Double" + case .integerLiteralExpr: + "Int" + case .stringLiteralExpr: + "String" + default: + nil + } + } } diff --git a/Source/SwiftLintBuiltInRules/Rules/RuleConfigurations/RedundantTypeAnnotationConfiguration.swift b/Source/SwiftLintBuiltInRules/Rules/RuleConfigurations/RedundantTypeAnnotationConfiguration.swift index 01c22d6005..fa837304f5 100644 --- a/Source/SwiftLintBuiltInRules/Rules/RuleConfigurations/RedundantTypeAnnotationConfiguration.swift +++ b/Source/SwiftLintBuiltInRules/Rules/RuleConfigurations/RedundantTypeAnnotationConfiguration.swift @@ -8,4 +8,6 @@ struct RedundantTypeAnnotationConfiguration: SeverityBasedRuleConfiguration { var severityConfiguration = SeverityConfiguration(.warning) @ConfigurationElement(key: "ignore_attributes") var ignoreAttributes = Set(["IBInspectable"]) + @ConfigurationElement(key: "consider_default_literal_types_redundant") + private(set) var considerDefaultLiteralTypesRedundant = false } diff --git a/Tests/IntegrationTests/default_rule_configurations.yml b/Tests/IntegrationTests/default_rule_configurations.yml index a8796526ee..404b9ef45d 100644 --- a/Tests/IntegrationTests/default_rule_configurations.yml +++ b/Tests/IntegrationTests/default_rule_configurations.yml @@ -457,6 +457,7 @@ redundant_string_enum_value: redundant_type_annotation: severity: warning ignore_attributes: ["IBInspectable"] + consider_default_literal_types_redundant: false redundant_void_return: severity: warning include_closures: true