diff --git a/CHANGELOG.md b/CHANGELOG.md index e2f6cf312d..d4b7728b5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -87,6 +87,10 @@ `inclusive_language` rule, with a default value of `mastercard`. [Dalton Claybrook](https://github.com/daltonclaybrook) [#3415](https://github.com/realm/SwiftLint/issues/3415) + +* Add Bool violation reporting in `redundant_type_annotation`. + [Artem Garmash](https://github.com/agarmash) + [#3423](https://github.com/realm/SwiftLint/issues/3423) #### Bug Fixes diff --git a/Source/SwiftLintFramework/Extensions/Configuration+FileGraph.swift b/Source/SwiftLintFramework/Extensions/Configuration+FileGraph.swift index f2dbdd1de9..78ef81362e 100644 --- a/Source/SwiftLintFramework/Extensions/Configuration+FileGraph.swift +++ b/Source/SwiftLintFramework/Extensions/Configuration+FileGraph.swift @@ -13,7 +13,7 @@ internal extension Configuration { private var vertices: Set private var edges: Set - private var isBuilt: Bool = false + private var isBuilt = false // MARK: - Initializers internal init(commandLineChildConfigs: [String], rootDirectory: String, ignoreParentAndChildConfigs: Bool) { diff --git a/Source/SwiftLintFramework/Extensions/Configuration+Remote.swift b/Source/SwiftLintFramework/Extensions/Configuration+Remote.swift index a6ecf90113..4dd20c6d8f 100644 --- a/Source/SwiftLintFramework/Extensions/Configuration+Remote.swift +++ b/Source/SwiftLintFramework/Extensions/Configuration+Remote.swift @@ -70,7 +70,7 @@ internal extension Configuration.FileGraph.FilePath { // Load from url var taskResult: (Data?, URLResponse?, Error?) - var taskDone: Bool = false + var taskDone = false // `.ephemeral` disables caching (which we don't want to be managed by the system) let task = URLSession(configuration: .ephemeral).dataTask(with: url) { data, response, error in diff --git a/Source/SwiftLintFramework/Models/Configuration.swift b/Source/SwiftLintFramework/Models/Configuration.swift index 39e67e4b4a..ca92fb7d69 100644 --- a/Source/SwiftLintFramework/Models/Configuration.swift +++ b/Source/SwiftLintFramework/Models/Configuration.swift @@ -38,7 +38,7 @@ public struct Configuration { /// This value is `true` iff the `--config` parameter was used to specify (a) configuration file(s) /// In particular, this means that the value is also `true` if the `--config` parameter /// was used to explicitly specify the default `.swiftlint.yml` as the configuration file - public private(set) var basedOnCustomConfigurationFiles: Bool = false + public private(set) var basedOnCustomConfigurationFiles = false // MARK: Public Computed /// All rules enabled in this configuration diff --git a/Source/SwiftLintFramework/Rules/Idiomatic/RedundantTypeAnnotationRule.swift b/Source/SwiftLintFramework/Rules/Idiomatic/RedundantTypeAnnotationRule.swift index cb0936a59e..d16a6342c2 100644 --- a/Source/SwiftLintFramework/Rules/Idiomatic/RedundantTypeAnnotationRule.swift +++ b/Source/SwiftLintFramework/Rules/Idiomatic/RedundantTypeAnnotationRule.swift @@ -5,8 +5,6 @@ public struct RedundantTypeAnnotationRule: OptInRule, SubstitutionCorrectableRul ConfigurationProviderRule, AutomaticTestableRule { public var configuration = SeverityConfiguration(.warning) - public init() {} - public static let description = RuleDescription( identifier: "redundant_type_annotation", name: "Redundant Type Annotation", @@ -15,7 +13,23 @@ public struct RedundantTypeAnnotationRule: OptInRule, SubstitutionCorrectableRul nonTriggeringExamples: [ Example("var url = URL()"), Example("var url: CustomStringConvertible = URL()"), - Example("@IBInspectable var color: UIColor = UIColor.white") + Example("@IBInspectable var color: UIColor = UIColor.white"), + Example(""" + enum Direction { + case up + case down + } + + var direction: Direction = .up + """), + Example(""" + enum Direction { + case up + case down + } + + var direction = Direction.up + """) ], triggeringExamples: [ Example("var url↓:URL=URL()"), @@ -30,6 +44,15 @@ public struct RedundantTypeAnnotationRule: OptInRule, SubstitutionCorrectableRul let myVar↓: Int = Int(5) } } + """), + Example("var isEnabled↓: Bool = true"), + Example(""" + enum Direction { + case up + case down + } + + var direction↓: Direction = Direction.up """) ], corrections: [ @@ -68,42 +91,81 @@ public struct RedundantTypeAnnotationRule: OptInRule, SubstitutionCorrectableRul return (violationRange, "") } + private let typeAnnotationPattern: String + private let expressionPattern: String + + public init() { + typeAnnotationPattern = + ":\\s*" + // semicolon and any number of whitespaces + "\\w+" // type name + + expressionPattern = + "(var|let)" + // var or let + "\\s+" + // at least single whitespace + "\\w+" + // variable name + "\\s*" + // possible whitespaces + typeAnnotationPattern + + "\\s*=\\s*" + // assignment operator with possible surrounding whitespaces + "\\w+" + // assignee name (type or keyword) + "[\\(\\.]?" // possible opening parenthesis or dot + } + public func violationRanges(in file: SwiftLintFile) -> [NSRange] { - let typeAnnotationPattern = ":\\s?\\w+" - let pattern = "(var|let)\\s?\\w+\(typeAnnotationPattern)\\s?=\\s?\\w+(\\(|.)" - let foundRanges = file.match(pattern: pattern, with: [.keyword, .identifier, .typeidentifier, .identifier]) - return foundRanges - .filter { !isFalsePositive(in: file, range: $0) && !isIBInspectable(range: $0, file: file) } + let violatingRanges = file + .match(pattern: expressionPattern) + .filter { + $0.1 == [.keyword, .identifier, .typeidentifier, .identifier] || + $0.1 == [.keyword, .identifier, .typeidentifier, .keyword] + } + .filter { !isFalsePositive(file: file, range: $0.0) } + .filter { !isIBInspectable(file: file, range: $0.0) } .compactMap { file.match(pattern: typeAnnotationPattern, - excludingSyntaxKinds: SyntaxKind.commentAndStringKinds, range: $0).first + excludingSyntaxKinds: SyntaxKind.commentAndStringKinds, range: $0.0).first } + + return violatingRanges } - private func isFalsePositive(in file: SwiftLintFile, range: NSRange) -> Bool { - let substring = file.stringView.substring(with: range) + private func isFalsePositive(file: SwiftLintFile, range: NSRange) -> Bool { + guard let typeNames = getPartsOfExpression(in: file, range: range) else { return false } + + let lhs = typeNames.variableTypeName + let rhs = typeNames.assigneeName + + if lhs == rhs || (lhs == "Bool" && (rhs == "true" || rhs == "false")) { + return false + } else { + return true + } + } + private func getPartsOfExpression( + in file: SwiftLintFile, range: NSRange + ) -> (variableTypeName: String, assigneeName: String)? { + let substring = file.stringView.substring(with: range) let components = substring.components(separatedBy: "=") - let charactersToTrimFromRhs = CharacterSet(charactersIn: ".(").union(.whitespaces) guard components.count == 2, - let lhsTypeName = components[0].components(separatedBy: ":").last?.trimmingCharacters(in: .whitespaces) + let variableTypeName = components[0].components(separatedBy: ":").last?.trimmingCharacters(in: .whitespaces) else { - return true + return nil } - let rhsTypeName = components[1].trimmingCharacters(in: charactersToTrimFromRhs) - return lhsTypeName != rhsTypeName + let charactersToTrimFromRhs = CharacterSet(charactersIn: ".(").union(.whitespaces) + let assigneeName = components[1].trimmingCharacters(in: charactersToTrimFromRhs) + + return (variableTypeName, assigneeName) } - private func isIBInspectable(range: NSRange, file: SwiftLintFile) -> Bool { - guard let byteRange = file.stringView.NSRangeToByteRange(start: range.location, length: range.length), + private func isIBInspectable(file: SwiftLintFile, range: NSRange) -> Bool { + guard + let byteRange = file.stringView.NSRangeToByteRange(start: range.location, length: range.length), let dict = file.structureDictionary.structures(forByteOffset: byteRange.location).last, let kind = dict.declarationKind, - SwiftDeclarationKind.variableKinds.contains(kind) else { - return false - } + SwiftDeclarationKind.variableKinds.contains(kind) + else { return false } return dict.enclosedSwiftAttributes.contains(.ibinspectable) } diff --git a/Source/SwiftLintFramework/Rules/RuleConfigurations/EmptyCountConfiguration.swift b/Source/SwiftLintFramework/Rules/RuleConfigurations/EmptyCountConfiguration.swift index 8e686aa913..09bbf9b89a 100644 --- a/Source/SwiftLintFramework/Rules/RuleConfigurations/EmptyCountConfiguration.swift +++ b/Source/SwiftLintFramework/Rules/RuleConfigurations/EmptyCountConfiguration.swift @@ -5,7 +5,7 @@ private enum ConfigurationKey: String { public struct EmptyCountConfiguration: RuleConfiguration, Equatable { private(set) var severityConfiguration = SeverityConfiguration(.error) - private(set) var onlyAfterDot: Bool = false + private(set) var onlyAfterDot = false public var consoleDescription: String { return [severityConfiguration.consoleDescription, diff --git a/Source/SwiftLintFramework/Rules/RuleConfigurations/OpeningBraceConfiguration.swift b/Source/SwiftLintFramework/Rules/RuleConfigurations/OpeningBraceConfiguration.swift index b27dde9442..1d3201d12a 100644 --- a/Source/SwiftLintFramework/Rules/RuleConfigurations/OpeningBraceConfiguration.swift +++ b/Source/SwiftLintFramework/Rules/RuleConfigurations/OpeningBraceConfiguration.swift @@ -5,7 +5,7 @@ private enum ConfigurationKey: String { public struct OpeningBraceConfiguration: RuleConfiguration, Equatable { private(set) var severityConfiguration = SeverityConfiguration(.warning) - private(set) var allowMultilineFunc: Bool = false + private(set) var allowMultilineFunc = false public var consoleDescription: String { return [severityConfiguration.consoleDescription,