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)
}