diff --git a/CHANGELOG.md b/CHANGELOG.md index 254df40266..3542b64278 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,13 +14,13 @@ * Add `empty_string` opt-in rule to validate against comparing strings to `""` instead of using `.isEmpty`.   [Davide Sibilio](https://github.com/idevid) - + * Add `untyped_error_in_catch` opt-in rule to warn against declaring errors without an explicit type in catch statements instead of using the implicit `error` variable. [Daniel Metzing](https://github.com/dirtydanee) [#2045](https://github.com/realm/SwiftLint/issues/2045) - + * Add `all` keyword for use in disable / enable statement: `// swiftlint:disable all`. It allows disabling SwiftLint entirely, in-code, for a particular section. @@ -33,6 +33,11 @@ [Ash Furrow](https://github.com/ashfurrow) [#2051](https://github.com/realm/SwiftLint/issues/2051) +* Adds `discouraged_optional_collection` opt-in rule to encourage the use of + empty collections instead of optional collections. + [Ornithologist Coder](https://github.com/ornithocoder) + [#1885](https://github.com/realm/SwiftLint/issues/1885) + #### Bug Fixes * Fix TODOs lint message to state that TODOs should be resolved instead of diff --git a/Rules.md b/Rules.md index 66973221e3..ded4dc5f26 100644 --- a/Rules.md +++ b/Rules.md @@ -21,6 +21,7 @@ * [Discouraged Direct Initialization](#discouraged-direct-initialization) * [Discouraged Object Literal](#discouraged-object-literal) * [Discouraged Optional Boolean](#discouraged-optional-boolean) +* [Discouraged Optional Collection](#discouraged-optional-collection) * [Dynamic Inline](#dynamic-inline) * [Empty Count](#empty-count) * [Empty Enum Arguments](#empty-enum-arguments) @@ -2790,6 +2791,921 @@ enum Foo { +## Discouraged Optional Collection + +Identifier | Enabled by default | Supports autocorrection | Kind +--- | --- | --- | --- +`discouraged_optional_collection` | Disabled | No | idiomatic + +Prefer empty collection over optional collection. + +### Examples + +
+Non Triggering Examples + +```swift +var foo: [Int] +``` + +```swift +var foo: [String: Int] +``` + +```swift +var foo: Set +``` + +```swift +var foo: [String: [String: Int]] +``` + +```swift +let foo: [Int] = [] +``` + +```swift +let foo: [String: Int] = [:] +``` + +```swift +let foo: Set = [] +``` + +```swift +let foo: [String: [String: Int]] = [:] +``` + +```swift +var foo: [Int] { return [] } +``` + +```swift +func foo() -> [Int] {} +``` + +```swift +func foo() -> [String: String] {} +``` + +```swift +func foo() -> Set {} +``` + +```swift +func foo() -> ([Int]) -> String {} +``` + +```swift +func foo(input: [String] = []) {} +``` + +```swift +func foo(input: [String: String] = [:]) {} +``` + +```swift +func foo(input: Set = []) {} +``` + +```swift +class Foo { + func foo() -> [Int] {} +} +``` + +```swift +class Foo { + func foo() -> [String: String] {} +} +``` + +```swift +class Foo { + func foo() -> Set {} +} +``` + +```swift +class Foo { + func foo() -> ([Int]) -> String {} +} +``` + +```swift +struct Foo { + func foo() -> [Int] {} +} +``` + +```swift +struct Foo { + func foo() -> [String: String] {} +} +``` + +```swift +struct Foo { + func foo() -> Set {} +} +``` + +```swift +struct Foo { + func foo() -> ([Int]) -> String {} +} +``` + +```swift +enum Foo { + func foo() -> [Int] {} +} +``` + +```swift +enum Foo { + func foo() -> [String: String] {} +} +``` + +```swift +enum Foo { + func foo() -> Set {} +} +``` + +```swift +enum Foo { + func foo() -> ([Int]) -> String {} +} +``` + +```swift +class Foo { + func foo(input: [String] = []) {} +} +``` + +```swift +class Foo { + func foo(input: [String: String] = [:]) {} +} +``` + +```swift +class Foo { + func foo(input: Set = []) {} +} +``` + +```swift +struct Foo { + func foo(input: [String] = []) {} +} +``` + +```swift +struct Foo { + func foo(input: [String: String] = [:]) {} +} +``` + +```swift +struct Foo { + func foo(input: Set = []) {} +} +``` + +```swift +enum Foo { + func foo(input: [String] = []) {} +} +``` + +```swift +enum Foo { + func foo(input: [String: String] = [:]) {} +} +``` + +```swift +enum Foo { + func foo(input: Set = []) {} +} +``` + +
+
+Triggering Examples + +```swift +↓var foo: [Int]? +``` + +```swift +↓var foo: [String: Int]? +``` + +```swift +↓var foo: Set? +``` + +```swift +↓let foo: [Int]? = nil +``` + +```swift +↓let foo: [String: Int]? = nil +``` + +```swift +↓let foo: Set? = nil +``` + +```swift +↓var foo: [Int]? { return nil } +``` + +```swift +↓let foo: [Int]? { return nil }() +``` + +```swift +func ↓foo() -> [T]? {} +``` + +```swift +func ↓foo() -> [String: String]? {} +``` + +```swift +func ↓foo() -> [String: [String: String]]? {} +``` + +```swift +func ↓foo() -> [String: [String: String]?] {} +``` + +```swift +func ↓foo() -> Set? {} +``` + +```swift +static func ↓foo() -> [T]? {} +``` + +```swift +static func ↓foo() -> [String: String]? {} +``` + +```swift +static func ↓foo() -> [String: [String: String]]? {} +``` + +```swift +static func ↓foo() -> [String: [String: String]?] {} +``` + +```swift +static func ↓foo() -> Set? {} +``` + +```swift +func ↓foo() -> ([Int]?) -> String {} +``` + +```swift +func ↓foo() -> ([Int]) -> [String]? {} +``` + +```swift +func foo(↓input: [String: String]?) {} +``` + +```swift +func foo(↓input: [String: [String: String]]?) {} +``` + +```swift +func foo(↓input: [String: [String: String]?]) {} +``` + +```swift +func foo(↓↓input: [String: [String: String]?]?) {} +``` + +```swift +func foo(_ dict1: [K: V], ↓_ dict2: [K: V]?) -> [K: V] +``` + +```swift +func foo(dict1: [K: V], ↓dict2: [K: V]?) -> [K: V] +``` + +```swift +static func foo(↓input: [String: String]?) {} +``` + +```swift +static func foo(↓input: [String: [String: String]]?) {} +``` + +```swift +static func foo(↓input: [String: [String: String]?]) {} +``` + +```swift +static func foo(↓↓input: [String: [String: String]?]?) {} +``` + +```swift +static func foo(_ dict1: [K: V], ↓_ dict2: [K: V]?) -> [K: V] +``` + +```swift +static func foo(dict1: [K: V], ↓dict2: [K: V]?) -> [K: V] +``` + +```swift +class Foo { + ↓var foo: [Int]? +} +``` + +```swift +class Foo { + ↓var foo: [String: Int]? +} +``` + +```swift +class Foo { + ↓var foo: Set? +} +``` + +```swift +class Foo { + ↓let foo: [Int]? = nil +} +``` + +```swift +class Foo { + ↓let foo: [String: Int]? = nil +} +``` + +```swift +class Foo { + ↓let foo: Set? = nil +} +``` + +```swift +struct Foo { + ↓var foo: [Int]? +} +``` + +```swift +struct Foo { + ↓var foo: [String: Int]? +} +``` + +```swift +struct Foo { + ↓var foo: Set? +} +``` + +```swift +struct Foo { + ↓let foo: [Int]? = nil +} +``` + +```swift +struct Foo { + ↓let foo: [String: Int]? = nil +} +``` + +```swift +struct Foo { + ↓let foo: Set? = nil +} +``` + +```swift +class Foo { + ↓var foo: [Int]? { return nil } +} +``` + +```swift +class Foo { + ↓let foo: [Int]? { return nil }() +} +``` + +```swift +class Foo { + ↓var foo: Set? { return nil } +} +``` + +```swift +class Foo { + ↓let foo: Set? { return nil }() +} +``` + +```swift +struct Foo { + ↓var foo: [Int]? { return nil } +} +``` + +```swift +struct Foo { + ↓let foo: [Int]? { return nil }() +} +``` + +```swift +struct Foo { + ↓var foo: Set? { return nil } +} +``` + +```swift +struct Foo { + ↓let foo: Set? { return nil }() +} +``` + +```swift +enum Foo { + ↓var foo: [Int]? { return nil } +} +``` + +```swift +enum Foo { + ↓let foo: [Int]? { return nil }() +} +``` + +```swift +enum Foo { + ↓var foo: Set? { return nil } +} +``` + +```swift +enum Foo { + ↓let foo: Set? { return nil }() +} +``` + +```swift +class Foo { + func ↓foo() -> [T]? {} +} +``` + +```swift +class Foo { + func ↓foo() -> [String: String]? {} +} +``` + +```swift +class Foo { + func ↓foo() -> [String: [String: String]]? {} +} +``` + +```swift +class Foo { + func ↓foo() -> [String: [String: String]?] {} +} +``` + +```swift +class Foo { + func ↓foo() -> Set? {} +} +``` + +```swift +class Foo { + static func ↓foo() -> [T]? {} +} +``` + +```swift +class Foo { + static func ↓foo() -> [String: String]? {} +} +``` + +```swift +class Foo { + static func ↓foo() -> [String: [String: String]]? {} +} +``` + +```swift +class Foo { + static func ↓foo() -> [String: [String: String]?] {} +} +``` + +```swift +class Foo { + static func ↓foo() -> Set? {} +} +``` + +```swift +class Foo { + func ↓foo() -> ([Int]?) -> String {} +} +``` + +```swift +class Foo { + func ↓foo() -> ([Int]) -> [String]? {} +} +``` + +```swift +struct Foo { + func ↓foo() -> [T]? {} +} +``` + +```swift +struct Foo { + func ↓foo() -> [String: String]? {} +} +``` + +```swift +struct Foo { + func ↓foo() -> [String: [String: String]]? {} +} +``` + +```swift +struct Foo { + func ↓foo() -> [String: [String: String]?] {} +} +``` + +```swift +struct Foo { + func ↓foo() -> Set? {} +} +``` + +```swift +struct Foo { + static func ↓foo() -> [T]? {} +} +``` + +```swift +struct Foo { + static func ↓foo() -> [String: String]? {} +} +``` + +```swift +struct Foo { + static func ↓foo() -> [String: [String: String]]? {} +} +``` + +```swift +struct Foo { + static func ↓foo() -> [String: [String: String]?] {} +} +``` + +```swift +struct Foo { + static func ↓foo() -> Set? {} +} +``` + +```swift +struct Foo { + func ↓foo() -> ([Int]?) -> String {} +} +``` + +```swift +struct Foo { + func ↓foo() -> ([Int]) -> [String]? {} +} +``` + +```swift +enum Foo { + func ↓foo() -> [T]? {} +} +``` + +```swift +enum Foo { + func ↓foo() -> [String: String]? {} +} +``` + +```swift +enum Foo { + func ↓foo() -> [String: [String: String]]? {} +} +``` + +```swift +enum Foo { + func ↓foo() -> [String: [String: String]?] {} +} +``` + +```swift +enum Foo { + func ↓foo() -> Set? {} +} +``` + +```swift +enum Foo { + static func ↓foo() -> [T]? {} +} +``` + +```swift +enum Foo { + static func ↓foo() -> [String: String]? {} +} +``` + +```swift +enum Foo { + static func ↓foo() -> [String: [String: String]]? {} +} +``` + +```swift +enum Foo { + static func ↓foo() -> [String: [String: String]?] {} +} +``` + +```swift +enum Foo { + static func ↓foo() -> Set? {} +} +``` + +```swift +enum Foo { + func ↓foo() -> ([Int]?) -> String {} +} +``` + +```swift +enum Foo { + func ↓foo() -> ([Int]) -> [String]? {} +} +``` + +```swift +class Foo { + func foo(↓input: [String: String]?) {} +} +``` + +```swift +class Foo { + func foo(↓input: [String: [String: String]]?) {} +} +``` + +```swift +class Foo { + func foo(↓input: [String: [String: String]?]) {} +} +``` + +```swift +class Foo { + func foo(↓↓input: [String: [String: String]?]?) {} +} +``` + +```swift +class Foo { + func foo(_ dict1: [K: V], ↓_ dict2: [K: V]?) -> [K: V] +} +``` + +```swift +class Foo { + func foo(dict1: [K: V], ↓dict2: [K: V]?) -> [K: V] +} +``` + +```swift +class Foo { + static func foo(↓input: [String: String]?) {} +} +``` + +```swift +class Foo { + static func foo(↓input: [String: [String: String]]?) {} +} +``` + +```swift +class Foo { + static func foo(↓input: [String: [String: String]?]) {} +} +``` + +```swift +class Foo { + static func foo(↓↓input: [String: [String: String]?]?) {} +} +``` + +```swift +class Foo { + static func foo(_ dict1: [K: V], ↓_ dict2: [K: V]?) -> [K: V] +} +``` + +```swift +class Foo { + static func foo(dict1: [K: V], ↓dict2: [K: V]?) -> [K: V] +} +``` + +```swift +struct Foo { + func foo(↓input: [String: String]?) {} +} +``` + +```swift +struct Foo { + func foo(↓input: [String: [String: String]]?) {} +} +``` + +```swift +struct Foo { + func foo(↓input: [String: [String: String]?]) {} +} +``` + +```swift +struct Foo { + func foo(↓↓input: [String: [String: String]?]?) {} +} +``` + +```swift +struct Foo { + func foo(_ dict1: [K: V], ↓_ dict2: [K: V]?) -> [K: V] +} +``` + +```swift +struct Foo { + func foo(dict1: [K: V], ↓dict2: [K: V]?) -> [K: V] +} +``` + +```swift +struct Foo { + static func foo(↓input: [String: String]?) {} +} +``` + +```swift +struct Foo { + static func foo(↓input: [String: [String: String]]?) {} +} +``` + +```swift +struct Foo { + static func foo(↓input: [String: [String: String]?]) {} +} +``` + +```swift +struct Foo { + static func foo(↓↓input: [String: [String: String]?]?) {} +} +``` + +```swift +struct Foo { + static func foo(_ dict1: [K: V], ↓_ dict2: [K: V]?) -> [K: V] +} +``` + +```swift +struct Foo { + static func foo(dict1: [K: V], ↓dict2: [K: V]?) -> [K: V] +} +``` + +```swift +enum Foo { + func foo(↓input: [String: String]?) {} +} +``` + +```swift +enum Foo { + func foo(↓input: [String: [String: String]]?) {} +} +``` + +```swift +enum Foo { + func foo(↓input: [String: [String: String]?]) {} +} +``` + +```swift +enum Foo { + func foo(↓↓input: [String: [String: String]?]?) {} +} +``` + +```swift +enum Foo { + func foo(_ dict1: [K: V], ↓_ dict2: [K: V]?) -> [K: V] +} +``` + +```swift +enum Foo { + func foo(dict1: [K: V], ↓dict2: [K: V]?) -> [K: V] +} +``` + +```swift +enum Foo { + static func foo(↓input: [String: String]?) {} +} +``` + +```swift +enum Foo { + static func foo(↓input: [String: [String: String]]?) {} +} +``` + +```swift +enum Foo { + static func foo(↓input: [String: [String: String]?]) {} +} +``` + +```swift +enum Foo { + static func foo(↓↓input: [String: [String: String]?]?) {} +} +``` + +```swift +enum Foo { + static func foo(_ dict1: [K: V], ↓_ dict2: [K: V]?) -> [K: V] +} +``` + +```swift +enum Foo { + static func foo(dict1: [K: V], ↓dict2: [K: V]?) -> [K: V] +} +``` + +
+ + + ## Dynamic Inline Identifier | Enabled by default | Supports autocorrection | Kind diff --git a/Source/SwiftLintFramework/Models/MasterRuleList.swift b/Source/SwiftLintFramework/Models/MasterRuleList.swift index bebc179040..acfa470b9e 100644 --- a/Source/SwiftLintFramework/Models/MasterRuleList.swift +++ b/Source/SwiftLintFramework/Models/MasterRuleList.swift @@ -29,6 +29,7 @@ public let masterRuleList = RuleList(rules: [ DiscouragedDirectInitRule.self, DiscouragedObjectLiteralRule.self, DiscouragedOptionalBooleanRule.self, + DiscouragedOptionalCollectionRule.self, DynamicInlineRule.self, EmptyCountRule.self, EmptyEnumArgumentsRule.self, diff --git a/Source/SwiftLintFramework/Rules/DiscouragedOptionalCollectionRule.swift b/Source/SwiftLintFramework/Rules/DiscouragedOptionalCollectionRule.swift new file mode 100644 index 0000000000..9b15b0dddd --- /dev/null +++ b/Source/SwiftLintFramework/Rules/DiscouragedOptionalCollectionRule.swift @@ -0,0 +1,174 @@ +// +// DiscouragedOptinalCollection.swift +// SwiftLint +// +// Created by Ornithologist Coder on 1/10/18. +// Copyright © 2018 Realm. All rights reserved. +// + +import Foundation +import SourceKittenFramework + +public struct DiscouragedOptionalCollectionRule: ASTRule, OptInRule, ConfigurationProviderRule { + public var configuration = SeverityConfiguration(.warning) + + public init() {} + + public static let description = RuleDescription( + identifier: "discouraged_optional_collection", + name: "Discouraged Optional Collection", + description: "Prefer empty collection over optional collection.", + kind: .idiomatic, + nonTriggeringExamples: DiscouragedOptionalCollectionExamples.nonTriggeringExamples, + triggeringExamples: DiscouragedOptionalCollectionExamples.triggeringExamples + ) + + public func validate(file: File, + kind: SwiftDeclarationKind, + dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] { + + let offsets = variableViolations(file: file, kind: kind, dictionary: dictionary) + + functionViolations(file: file, kind: kind, dictionary: dictionary) + + return offsets.map { + StyleViolation(ruleDescription: type(of: self).description, + severity: configuration.severity, + location: Location(file: file, byteOffset: $0)) + } + } + + // MARK: - Private + + private func variableViolations(file: File, + kind: SwiftDeclarationKind, + dictionary: [String: SourceKitRepresentable]) -> [Int] { + guard + SwiftDeclarationKind.variableKinds.contains(kind), + let offset = dictionary.offset, + let typeName = dictionary.typeName else { return [] } + + return typeName.optionalCollectionRanges().map { _ in offset } + } + + private func functionViolations(file: File, + kind: SwiftDeclarationKind, + dictionary: [String: SourceKitRepresentable]) -> [Int] { + guard + SwiftDeclarationKind.functionKinds.contains(kind), + let nameOffset = dictionary.nameOffset, + let nameLength = dictionary.nameLength, + let length = dictionary.length, + let offset = dictionary.offset, + case let start = nameOffset + nameLength, + case let end = dictionary.bodyOffset ?? offset + length, + case let contents = file.contents.bridge(), + let range = contents.byteRangeToNSRange(start: start, length: end - start), + let match = file.match(pattern: "->\\s*(.*?)\\{", excludingSyntaxKinds: excludingKinds, range: range).first + else { return [] } + + return contents.substring(with: match).optionalCollectionRanges().map { _ in nameOffset } + } + + private let excludingKinds = SyntaxKind.allKinds.subtracting([.typeidentifier]) +} + +private extension String { + /// Ranges of optional collections within the bounds of the string. + /// + /// Example: [String: [Int]?] + /// + /// [ S t r i n g : [ I n t ] ? ] + /// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + /// ^ ^ + /// = [9, 14] + /// = [9, 15), mathematical interval, w/ lower and upper bounds. + /// + /// Example: [String: [Int]?]? + /// + /// [ S t r i n g : [ I n t ] ? ] ? + /// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 + /// ^ ^ ^ ^ + /// = [0, 16], [9, 14] + /// = [0, 17), [9, 15), mathematical interval, w/ lower and upper bounds. + /// + /// Example: var x = Set? + /// + /// v a r x = S e t < I n t > ? + /// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 + /// ^ ^ + /// = [8, 16] + /// = [8, 17), mathematical interval, w/ lower and upper bounds. + /// + /// - Returns: An array of ranges. + func optionalCollectionRanges() -> [Range] { + let squareBrackets = balancedRanges(from: "[", to: "]").flatMap { range -> Range? in + guard + range.upperBound < endIndex, + let finalIndex = index(range.upperBound, offsetBy: 1, limitedBy: endIndex), + self[range.upperBound] == "?" else { return nil } + + return Range(range.lowerBound..").flatMap { range -> Range? in + guard + range.upperBound < endIndex, + let initialIndex = index(range.lowerBound, offsetBy: -3, limitedBy: startIndex), + let finalIndex = index(range.upperBound, offsetBy: 1, limitedBy: endIndex), + self[initialIndex.. [String.Index] { + return indices.flatMap { self[$0] == character ? $0 : nil } + } + + /// Ranges of balanced substrings. + /// + /// Example: ((1+2)*(3+4)) + /// + /// ( ( 1 + 2 ) * ( 3 + 4 ) ) + /// 0 1 2 3 4 5 6 7 8 9 10 11 12 + /// ^ ^ ^ ^ ^ ^ + /// = [0, 12], [1, 5], [7, 11] + /// = [0, 13), [1, 6), [7, 12), mathematical interval, w/ lower and upper bounds. + /// + /// - Parameters: + /// - prefix: The prefix to look for. + /// - suffix: The suffix to look for. + /// - Returns: Array of ranges of balanced substrings + private func balancedRanges(from prefix: Character, to suffix: Character) -> [Range] { + return indices(of: prefix).flatMap { prefixIndex in + var pairCount = 0 + var currentIndex = prefixIndex + var foundCharacter = false + + while currentIndex < endIndex { + let character = self[currentIndex] + currentIndex = index(after: currentIndex) + + if character == prefix { pairCount += 1 } + if character == suffix { pairCount -= 1 } + if pairCount != 0 { foundCharacter = true } + if pairCount == 0 && foundCharacter { break } + } + + return pairCount == 0 && foundCharacter ? Range(prefixIndex..", + "var foo: [String: [String: Int]]", + "let foo: [Int] = []", + "let foo: [String: Int] = [:]", + "let foo: Set = []", + "let foo: [String: [String: Int]] = [:]", + + // Computed get variable + "var foo: [Int] { return [] }", + + // Free function return + "func foo() -> [Int] {}", + "func foo() -> [String: String] {}", + "func foo() -> Set {}", + "func foo() -> ([Int]) -> String {}", + + // Free function parameter + "func foo(input: [String] = []) {}", + "func foo(input: [String: String] = [:]) {}", + "func foo(input: Set = []) {}", + + // Method return + wrapExample("class", "func foo() -> [Int] {}"), + wrapExample("class", "func foo() -> [String: String] {}"), + wrapExample("class", "func foo() -> Set {}"), + wrapExample("class", "func foo() -> ([Int]) -> String {}"), + + wrapExample("struct", "func foo() -> [Int] {}"), + wrapExample("struct", "func foo() -> [String: String] {}"), + wrapExample("struct", "func foo() -> Set {}"), + wrapExample("struct", "func foo() -> ([Int]) -> String {}"), + + wrapExample("enum", "func foo() -> [Int] {}"), + wrapExample("enum", "func foo() -> [String: String] {}"), + wrapExample("enum", "func foo() -> Set {}"), + wrapExample("enum", "func foo() -> ([Int]) -> String {}"), + + // Method parameter + wrapExample("class", "func foo(input: [String] = []) {}"), + wrapExample("class", "func foo(input: [String: String] = [:]) {}"), + wrapExample("class", "func foo(input: Set = []) {}"), + + wrapExample("struct", "func foo(input: [String] = []) {}"), + wrapExample("struct", "func foo(input: [String: String] = [:]) {}"), + wrapExample("struct", "func foo(input: Set = []) {}"), + + wrapExample("enum", "func foo(input: [String] = []) {}"), + wrapExample("enum", "func foo(input: [String: String] = [:]) {}"), + wrapExample("enum", "func foo(input: Set = []) {}") + ] + + static let triggeringExamples = [ + + // Global variable + "↓var foo: [Int]?", + "↓var foo: [String: Int]?", + "↓var foo: Set?", + "↓let foo: [Int]? = nil", + "↓let foo: [String: Int]? = nil", + "↓let foo: Set? = nil", + + // Computed Get Variable + "↓var foo: [Int]? { return nil }", + "↓let foo: [Int]? { return nil }()", + + // Free function return + "func ↓foo() -> [T]? {}", + "func ↓foo() -> [String: String]? {}", + "func ↓foo() -> [String: [String: String]]? {}", + "func ↓foo() -> [String: [String: String]?] {}", + "func ↓foo() -> Set? {}", + "static func ↓foo() -> [T]? {}", + "static func ↓foo() -> [String: String]? {}", + "static func ↓foo() -> [String: [String: String]]? {}", + "static func ↓foo() -> [String: [String: String]?] {}", + "static func ↓foo() -> Set? {}", + "func ↓foo() -> ([Int]?) -> String {}", + "func ↓foo() -> ([Int]) -> [String]? {}", + + // Free function parameter + "func foo(↓input: [String: String]?) {}", + "func foo(↓input: [String: [String: String]]?) {}", + "func foo(↓input: [String: [String: String]?]) {}", + "func foo(↓↓input: [String: [String: String]?]?) {}", + "func foo(_ dict1: [K: V], ↓_ dict2: [K: V]?) -> [K: V]", + "func foo(dict1: [K: V], ↓dict2: [K: V]?) -> [K: V]", + "static func foo(↓input: [String: String]?) {}", + "static func foo(↓input: [String: [String: String]]?) {}", + "static func foo(↓input: [String: [String: String]?]) {}", + "static func foo(↓↓input: [String: [String: String]?]?) {}", + "static func foo(_ dict1: [K: V], ↓_ dict2: [K: V]?) -> [K: V]", + "static func foo(dict1: [K: V], ↓dict2: [K: V]?) -> [K: V]", + + // Instance variable + wrapExample("class", "↓var foo: [Int]?"), + wrapExample("class", "↓var foo: [String: Int]?"), + wrapExample("class", "↓var foo: Set?"), + wrapExample("class", "↓let foo: [Int]? = nil"), + wrapExample("class", "↓let foo: [String: Int]? = nil"), + wrapExample("class", "↓let foo: Set? = nil"), + + wrapExample("struct", "↓var foo: [Int]?"), + wrapExample("struct", "↓var foo: [String: Int]?"), + wrapExample("struct", "↓var foo: Set?"), + wrapExample("struct", "↓let foo: [Int]? = nil"), + wrapExample("struct", "↓let foo: [String: Int]? = nil"), + wrapExample("struct", "↓let foo: Set? = nil"), + + // Instance computed variable + wrapExample("class", "↓var foo: [Int]? { return nil }"), + wrapExample("class", "↓let foo: [Int]? { return nil }()"), + wrapExample("class", "↓var foo: Set? { return nil }"), + wrapExample("class", "↓let foo: Set? { return nil }()"), + + wrapExample("struct", "↓var foo: [Int]? { return nil }"), + wrapExample("struct", "↓let foo: [Int]? { return nil }()"), + wrapExample("struct", "↓var foo: Set? { return nil }"), + wrapExample("struct", "↓let foo: Set? { return nil }()"), + + wrapExample("enum", "↓var foo: [Int]? { return nil }"), + wrapExample("enum", "↓let foo: [Int]? { return nil }()"), + wrapExample("enum", "↓var foo: Set? { return nil }"), + wrapExample("enum", "↓let foo: Set? { return nil }()"), + + // Method return + wrapExample("class", "func ↓foo() -> [T]? {}"), + wrapExample("class", "func ↓foo() -> [String: String]? {}"), + wrapExample("class", "func ↓foo() -> [String: [String: String]]? {}"), + wrapExample("class", "func ↓foo() -> [String: [String: String]?] {}"), + wrapExample("class", "func ↓foo() -> Set? {}"), + wrapExample("class", "static func ↓foo() -> [T]? {}"), + wrapExample("class", "static func ↓foo() -> [String: String]? {}"), + wrapExample("class", "static func ↓foo() -> [String: [String: String]]? {}"), + wrapExample("class", "static func ↓foo() -> [String: [String: String]?] {}"), + wrapExample("class", "static func ↓foo() -> Set? {}"), + wrapExample("class", "func ↓foo() -> ([Int]?) -> String {}"), + wrapExample("class", "func ↓foo() -> ([Int]) -> [String]? {}"), + + wrapExample("struct", "func ↓foo() -> [T]? {}"), + wrapExample("struct", "func ↓foo() -> [String: String]? {}"), + wrapExample("struct", "func ↓foo() -> [String: [String: String]]? {}"), + wrapExample("struct", "func ↓foo() -> [String: [String: String]?] {}"), + wrapExample("struct", "func ↓foo() -> Set? {}"), + wrapExample("struct", "static func ↓foo() -> [T]? {}"), + wrapExample("struct", "static func ↓foo() -> [String: String]? {}"), + wrapExample("struct", "static func ↓foo() -> [String: [String: String]]? {}"), + wrapExample("struct", "static func ↓foo() -> [String: [String: String]?] {}"), + wrapExample("struct", "static func ↓foo() -> Set? {}"), + wrapExample("struct", "func ↓foo() -> ([Int]?) -> String {}"), + wrapExample("struct", "func ↓foo() -> ([Int]) -> [String]? {}"), + + wrapExample("enum", "func ↓foo() -> [T]? {}"), + wrapExample("enum", "func ↓foo() -> [String: String]? {}"), + wrapExample("enum", "func ↓foo() -> [String: [String: String]]? {}"), + wrapExample("enum", "func ↓foo() -> [String: [String: String]?] {}"), + wrapExample("enum", "func ↓foo() -> Set? {}"), + wrapExample("enum", "static func ↓foo() -> [T]? {}"), + wrapExample("enum", "static func ↓foo() -> [String: String]? {}"), + wrapExample("enum", "static func ↓foo() -> [String: [String: String]]? {}"), + wrapExample("enum", "static func ↓foo() -> [String: [String: String]?] {}"), + wrapExample("enum", "static func ↓foo() -> Set? {}"), + wrapExample("enum", "func ↓foo() -> ([Int]?) -> String {}"), + wrapExample("enum", "func ↓foo() -> ([Int]) -> [String]? {}"), + + // Method parameter + wrapExample("class", "func foo(↓input: [String: String]?) {}"), + wrapExample("class", "func foo(↓input: [String: [String: String]]?) {}"), + wrapExample("class", "func foo(↓input: [String: [String: String]?]) {}"), + wrapExample("class", "func foo(↓↓input: [String: [String: String]?]?) {}"), + wrapExample("class", "func foo(_ dict1: [K: V], ↓_ dict2: [K: V]?) -> [K: V]"), + wrapExample("class", "func foo(dict1: [K: V], ↓dict2: [K: V]?) -> [K: V]"), + wrapExample("class", "static func foo(↓input: [String: String]?) {}"), + wrapExample("class", "static func foo(↓input: [String: [String: String]]?) {}"), + wrapExample("class", "static func foo(↓input: [String: [String: String]?]) {}"), + wrapExample("class", "static func foo(↓↓input: [String: [String: String]?]?) {}"), + wrapExample("class", "static func foo(_ dict1: [K: V], ↓_ dict2: [K: V]?) -> [K: V]"), + wrapExample("class", "static func foo(dict1: [K: V], ↓dict2: [K: V]?) -> [K: V]"), + + wrapExample("struct", "func foo(↓input: [String: String]?) {}"), + wrapExample("struct", "func foo(↓input: [String: [String: String]]?) {}"), + wrapExample("struct", "func foo(↓input: [String: [String: String]?]) {}"), + wrapExample("struct", "func foo(↓↓input: [String: [String: String]?]?) {}"), + wrapExample("struct", "func foo(_ dict1: [K: V], ↓_ dict2: [K: V]?) -> [K: V]"), + wrapExample("struct", "func foo(dict1: [K: V], ↓dict2: [K: V]?) -> [K: V]"), + wrapExample("struct", "static func foo(↓input: [String: String]?) {}"), + wrapExample("struct", "static func foo(↓input: [String: [String: String]]?) {}"), + wrapExample("struct", "static func foo(↓input: [String: [String: String]?]) {}"), + wrapExample("struct", "static func foo(↓↓input: [String: [String: String]?]?) {}"), + wrapExample("struct", "static func foo(_ dict1: [K: V], ↓_ dict2: [K: V]?) -> [K: V]"), + wrapExample("struct", "static func foo(dict1: [K: V], ↓dict2: [K: V]?) -> [K: V]"), + + wrapExample("enum", "func foo(↓input: [String: String]?) {}"), + wrapExample("enum", "func foo(↓input: [String: [String: String]]?) {}"), + wrapExample("enum", "func foo(↓input: [String: [String: String]?]) {}"), + wrapExample("enum", "func foo(↓↓input: [String: [String: String]?]?) {}"), + wrapExample("enum", "func foo(_ dict1: [K: V], ↓_ dict2: [K: V]?) -> [K: V]"), + wrapExample("enum", "func foo(dict1: [K: V], ↓dict2: [K: V]?) -> [K: V]"), + wrapExample("enum", "static func foo(↓input: [String: String]?) {}"), + wrapExample("enum", "static func foo(↓input: [String: [String: String]]?) {}"), + wrapExample("enum", "static func foo(↓input: [String: [String: String]?]) {}"), + wrapExample("enum", "static func foo(↓↓input: [String: [String: String]?]?) {}"), + wrapExample("enum", "static func foo(_ dict1: [K: V], ↓_ dict2: [K: V]?) -> [K: V]"), + wrapExample("enum", "static func foo(dict1: [K: V], ↓dict2: [K: V]?) -> [K: V]") + ] +} + +// MARK: - Private + +private func wrapExample(_ type: String, _ test: String) -> String { + return "\(type) Foo {\n\t\(test)\n}" +} diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj index 2465fc66d2..8ce19caebc 100644 --- a/SwiftLint.xcodeproj/project.pbxproj +++ b/SwiftLint.xcodeproj/project.pbxproj @@ -86,11 +86,13 @@ 626C16E21F948EBC00BB7475 /* QuickDiscouragedFocusedTestRuleExamples.swift in Sources */ = {isa = PBXBuildFile; fileRef = 626C16E01F948E1C00BB7475 /* QuickDiscouragedFocusedTestRuleExamples.swift */; }; 626D02971F31CBCC0054788D /* XCTFailMessageRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 626D02961F31CBCC0054788D /* XCTFailMessageRule.swift */; }; 627BC48D1F9405160004A6C2 /* QuickDiscouragedFocusedTestRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E54FED1F93AD57005B367B /* QuickDiscouragedFocusedTestRule.swift */; }; + 629ADD062006302D0009E362 /* DiscouragedOptionalCollectionRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629ADD052006302D0009E362 /* DiscouragedOptionalCollectionRule.swift */; }; 629C60D91F43906700B4AF92 /* SingleTestClassRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629C60D81F43906700B4AF92 /* SingleTestClassRule.swift */; }; 62A498561F306A7700D766E4 /* DiscouragedDirectInitConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A498551F306A7700D766E4 /* DiscouragedDirectInitConfiguration.swift */; }; 62A6E7931F3317E3003A0479 /* JoinedDefaultRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A6E7911F3317E3003A0479 /* JoinedDefaultRule.swift */; }; 62DADC481FFF0423002B6319 /* PrefixedTopLevelConstantRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62DADC471FFF0423002B6319 /* PrefixedTopLevelConstantRule.swift */; }; 62DEA1661FB21A9E00BCCCC6 /* PrivateActionRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62DEA1651FB21A9E00BCCCC6 /* PrivateActionRule.swift */; }; + 62FE5D32200CABDD00F68793 /* DiscouragedOptionalCollectionRuleExamples.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62FE5D30200CAB6E00F68793 /* DiscouragedOptionalCollectionRuleExamples.swift */; }; 67932E2D1E54AF4B00CB0629 /* CyclomaticComplexityConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67932E2C1E54AF4B00CB0629 /* CyclomaticComplexityConfigurationTests.swift */; }; 67EB4DFA1E4CC111004E9ACD /* CyclomaticComplexityConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB4DF81E4CC101004E9ACD /* CyclomaticComplexityConfiguration.swift */; }; 67EB4DFC1E4CD7F5004E9ACD /* CyclomaticComplexityRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB4DFB1E4CD7F5004E9ACD /* CyclomaticComplexityRuleTests.swift */; }; @@ -443,6 +445,7 @@ 6264015320155533005B9C4A /* DiscouragedOptionalBooleanRuleExamples.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscouragedOptionalBooleanRuleExamples.swift; sourceTree = ""; }; 626C16E01F948E1C00BB7475 /* QuickDiscouragedFocusedTestRuleExamples.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickDiscouragedFocusedTestRuleExamples.swift; sourceTree = ""; }; 626D02961F31CBCC0054788D /* XCTFailMessageRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTFailMessageRule.swift; sourceTree = ""; }; + 629ADD052006302D0009E362 /* DiscouragedOptionalCollectionRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscouragedOptionalCollectionRule.swift; sourceTree = ""; }; 629C60D81F43906700B4AF92 /* SingleTestClassRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleTestClassRule.swift; sourceTree = ""; }; 62A498551F306A7700D766E4 /* DiscouragedDirectInitConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscouragedDirectInitConfiguration.swift; sourceTree = ""; }; 62A6E7911F3317E3003A0479 /* JoinedDefaultRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinedDefaultRule.swift; sourceTree = ""; }; @@ -450,6 +453,7 @@ 62DADC471FFF0423002B6319 /* PrefixedTopLevelConstantRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefixedTopLevelConstantRule.swift; sourceTree = ""; }; 62DEA1651FB21A9E00BCCCC6 /* PrivateActionRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivateActionRule.swift; sourceTree = ""; }; 62E54FED1F93AD57005B367B /* QuickDiscouragedFocusedTestRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickDiscouragedFocusedTestRule.swift; sourceTree = ""; }; + 62FE5D30200CAB6E00F68793 /* DiscouragedOptionalCollectionRuleExamples.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscouragedOptionalCollectionRuleExamples.swift; sourceTree = ""; }; 65454F451B14D73800319A6C /* ControlStatementRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControlStatementRule.swift; sourceTree = ""; }; 67932E2C1E54AF4B00CB0629 /* CyclomaticComplexityConfigurationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CyclomaticComplexityConfigurationTests.swift; sourceTree = ""; }; 67EB4DF81E4CC101004E9ACD /* CyclomaticComplexityConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CyclomaticComplexityConfiguration.swift; sourceTree = ""; }; @@ -1058,6 +1062,8 @@ 6258783A1FFC458100AC34F2 /* DiscouragedObjectLiteralRule.swift */, 62640150201552E0005B9C4A /* DiscouragedOptionalBooleanRule.swift */, 6264015320155533005B9C4A /* DiscouragedOptionalBooleanRuleExamples.swift */, + 629ADD052006302D0009E362 /* DiscouragedOptionalCollectionRule.swift */, + 62FE5D30200CAB6E00F68793 /* DiscouragedOptionalCollectionRuleExamples.swift */, E315B83B1DFA4BC500621B44 /* DynamicInlineRule.swift */, E847F0A81BFBBABD00EA9363 /* EmptyCountRule.swift */, 740DF1AF203F5AFC0081F694 /* EmptyStringRule.swift */, @@ -1542,6 +1548,7 @@ E88198551BEA949A00333A11 /* ControlStatementRule.swift in Sources */, E57B23C11B1D8BF000DEA512 /* ReturnArrowWhitespaceRule.swift in Sources */, D4246D6D1F30D8620097E658 /* PrivateOverFilePrivateRuleConfiguration.swift in Sources */, + 629ADD062006302D0009E362 /* DiscouragedOptionalCollectionRule.swift in Sources */, E816194E1BFBFEAB00946723 /* ForceTryRule.swift in Sources */, E88198541BEA945100333A11 /* CommaRule.swift in Sources */, D4DA1DFE1E1A10DB0037413D /* NumberSeparatorConfiguration.swift in Sources */, @@ -1690,6 +1697,7 @@ 4A9A3A3A1DC1D75F00DF5183 /* HTMLReporter.swift in Sources */, D40F83881DE9179200524C62 /* TrailingCommaConfiguration.swift in Sources */, 827169B31F488181003FB9AF /* ExplicitEnumRawValueRule.swift in Sources */, + 62FE5D32200CABDD00F68793 /* DiscouragedOptionalCollectionRuleExamples.swift in Sources */, 29FFC37A1F15764D007E4825 /* FileLengthRuleConfiguration.swift in Sources */, 3B5B9FE11C444DA20009AD27 /* Array+SwiftLint.swift in Sources */, 8FD216CC205584AF008ED13F /* CharacterSet+SwiftLint.swift in Sources */, diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index fb7ede4755..1527c6caf4 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -432,6 +432,7 @@ extension RulesTests { ("testDiscardedNotificationCenterObserver", testDiscardedNotificationCenterObserver), ("testDiscouragedObjectLiteral", testDiscouragedObjectLiteral), ("testDiscouragedOptionalBoolean", testDiscouragedOptionalBoolean), + ("testDiscouragedOptionalCollection", testDiscouragedOptionalCollection), ("testDynamicInline", testDynamicInline), ("testEmptyCount", testEmptyCount), ("testEmptyEnumArguments", testEmptyEnumArguments), diff --git a/Tests/SwiftLintFrameworkTests/RulesTests.swift b/Tests/SwiftLintFrameworkTests/RulesTests.swift index 23aac63668..2242930d9e 100644 --- a/Tests/SwiftLintFrameworkTests/RulesTests.swift +++ b/Tests/SwiftLintFrameworkTests/RulesTests.swift @@ -78,6 +78,10 @@ class RulesTests: XCTestCase { verifyRule(DiscouragedOptionalBooleanRule.description) } + func testDiscouragedOptionalCollection() { + verifyRule(DiscouragedOptionalCollectionRule.description) + } + func testDynamicInline() { verifyRule(DynamicInlineRule.description) }