diff --git a/CHANGELOG.md b/CHANGELOG.md
index ff7d461726..f86310e703 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,10 @@
#### Enhancements
+* Add `reduce_boolean` rule to prefer simpler constructs over `reduce(Boolean)`.
+ [Xavier Lowmiller](https://github.com/xavierLowmiller)
+ [#2675](https://github.com/realm/SwiftLint/issues/2675)
+
* Add `nsobject_prefer_isequal` rule to warn against implementing `==` on an
`NSObject` subclass as calling `isEqual` (i.e. when using the class from
Objective-C) will will not use the defined `==` method.
diff --git a/Rules.md b/Rules.md
index c630e787cc..cbed13614a 100644
--- a/Rules.md
+++ b/Rules.md
@@ -114,6 +114,7 @@
* [Quick Discouraged Call](#quick-discouraged-call)
* [Quick Discouraged Focused Test](#quick-discouraged-focused-test)
* [Quick Discouraged Pending Test](#quick-discouraged-pending-test)
+* [Reduce Boolean](#reduce-boolean)
* [Redundant Discardable Let](#redundant-discardable-let)
* [Redundant Nil Coalescing](#redundant-nil-coalescing)
* [Redundant @objc Attribute](#redundant-@objc-attribute)
@@ -15783,6 +15784,67 @@ class TotoTests: QuickSpec {
+## Reduce Boolean
+
+Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version
+--- | --- | --- | --- | --- | ---
+`reduce_boolean` | Enabled | No | performance | No | 4.2.0
+
+Prefer using `.allSatisfy()` or `.contains()` over `reduce(true)` or `reduce(false)`
+
+### Examples
+
+
+Non Triggering Examples
+
+```swift
+nums.reduce(0) { $0.0 + $0.1 }
+```
+
+```swift
+nums.reduce(0.0) { $0.0 + $0.1 }
+```
+
+
+
+Triggering Examples
+
+```swift
+let allNines = nums.↓reduce(true) { $0.0 && $0.1 == 9 }
+```
+
+```swift
+let anyNines = nums.↓reduce(false) { $0.0 || $0.1 == 9 }
+```
+
+```swift
+let allValid = validators.↓reduce(true) { $0 && $1(input) }
+```
+
+```swift
+let anyValid = validators.↓reduce(false) { $0 || $1(input) }
+```
+
+```swift
+let allNines = nums.↓reduce(true, { $0.0 && $0.1 == 9 })
+```
+
+```swift
+let anyNines = nums.↓reduce(false, { $0.0 || $0.1 == 9 })
+```
+
+```swift
+let allValid = validators.↓reduce(true, { $0 && $1(input) })
+```
+
+```swift
+let anyValid = validators.↓reduce(false, { $0 || $1(input) })
+```
+
+
+
+
+
## Redundant Discardable Let
Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version
diff --git a/Source/SwiftLintFramework/Models/MasterRuleList.swift b/Source/SwiftLintFramework/Models/MasterRuleList.swift
index 4c0741e9db..7d1c46de12 100644
--- a/Source/SwiftLintFramework/Models/MasterRuleList.swift
+++ b/Source/SwiftLintFramework/Models/MasterRuleList.swift
@@ -115,6 +115,7 @@ public let masterRuleList = RuleList(rules: [
QuickDiscouragedCallRule.self,
QuickDiscouragedFocusedTestRule.self,
QuickDiscouragedPendingTestRule.self,
+ ReduceBooleanRule.self,
RedundantDiscardableLetRule.self,
RedundantNilCoalescingRule.self,
RedundantObjcAttributeRule.self,
diff --git a/Source/SwiftLintFramework/Rules/Lint/NSObjectPreferIsEqualRule.swift b/Source/SwiftLintFramework/Rules/Lint/NSObjectPreferIsEqualRule.swift
index 99896f2449..08fe81e935 100644
--- a/Source/SwiftLintFramework/Rules/Lint/NSObjectPreferIsEqualRule.swift
+++ b/Source/SwiftLintFramework/Rules/Lint/NSObjectPreferIsEqualRule.swift
@@ -60,8 +60,8 @@ public struct NSObjectPreferIsEqualRule: Rule, ConfigurationProviderRule, Automa
private func areAllArguments(toMethod method: [String: SourceKitRepresentable],
ofType typeName: String) -> Bool {
- return method.enclosedVarParameters.reduce(true) { soFar, param -> Bool in
- soFar && (param.typeName == typeName)
+ return method.enclosedVarParameters.allSatisfy { param in
+ param.typeName == typeName
}
}
}
diff --git a/Source/SwiftLintFramework/Rules/Performance/ReduceBooleanRule.swift b/Source/SwiftLintFramework/Rules/Performance/ReduceBooleanRule.swift
new file mode 100644
index 0000000000..57d76dd552
--- /dev/null
+++ b/Source/SwiftLintFramework/Rules/Performance/ReduceBooleanRule.swift
@@ -0,0 +1,50 @@
+import SourceKittenFramework
+
+public struct ReduceBooleanRule: Rule, ConfigurationProviderRule, AutomaticTestableRule {
+ public var configuration = SeverityConfiguration(.warning)
+
+ public init() {}
+
+ public static let description = RuleDescription(
+ identifier: "reduce_boolean",
+ name: "Reduce Boolean",
+ description: "Prefer using `.allSatisfy()` or `.contains()` over `reduce(true)` or `reduce(false)`",
+ kind: .performance,
+ minSwiftVersion: .fourDotTwo,
+ nonTriggeringExamples: [
+ "nums.reduce(0) { $0.0 + $0.1 }",
+ "nums.reduce(0.0) { $0.0 + $0.1 }"
+ ],
+ triggeringExamples: [
+ "let allNines = nums.↓reduce(true) { $0.0 && $0.1 == 9 }",
+ "let anyNines = nums.↓reduce(false) { $0.0 || $0.1 == 9 }",
+ "let allValid = validators.↓reduce(true) { $0 && $1(input) }",
+ "let anyValid = validators.↓reduce(false) { $0 || $1(input) }",
+ "let allNines = nums.↓reduce(true, { $0.0 && $0.1 == 9 })",
+ "let anyNines = nums.↓reduce(false, { $0.0 || $0.1 == 9 })",
+ "let allValid = validators.↓reduce(true, { $0 && $1(input) })",
+ "let anyValid = validators.↓reduce(false, { $0 || $1(input) })"
+ ]
+ )
+
+ public func validate(file: File) -> [StyleViolation] {
+ let pattern = "\\breduce\\((true|false)"
+ return file
+ .match(pattern: pattern, with: [.identifier, .keyword])
+ .map { range in
+ let reason: String
+ if file.contents[Range(range, in: file.contents)!].contains("true") {
+ reason = "Use `allSatisfy` instead"
+ } else {
+ reason = "Use `contains` instead"
+ }
+
+ return StyleViolation(
+ ruleDescription: type(of: self).description,
+ severity: configuration.severity,
+ location: Location(file: file, characterOffset: range.location),
+ reason: reason
+ )
+ }
+ }
+}
diff --git a/Source/SwiftLintFramework/Rules/Style/UnneededParenthesesInClosureArgumentRule.swift b/Source/SwiftLintFramework/Rules/Style/UnneededParenthesesInClosureArgumentRule.swift
index c7be19b853..a691137a2c 100644
--- a/Source/SwiftLintFramework/Rules/Style/UnneededParenthesesInClosureArgumentRule.swift
+++ b/Source/SwiftLintFramework/Rules/Style/UnneededParenthesesInClosureArgumentRule.swift
@@ -83,11 +83,7 @@ public struct UnneededParenthesesInClosureArgumentRule: ConfigurationProviderRul
}
let parametersTokens = file.syntaxMap.tokens(inByteRange: parametersByteRange)
- let parametersAreValid = parametersTokens.reduce(true) { isValid, token in
- guard isValid else {
- return false
- }
-
+ let parametersAreValid = parametersTokens.allSatisfy { token in
let kind = SyntaxKind(rawValue: token.type)
if kind == .identifier {
return true
diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj
index cf4783ce4f..9098d5cb4a 100644
--- a/SwiftLint.xcodeproj/project.pbxproj
+++ b/SwiftLint.xcodeproj/project.pbxproj
@@ -85,6 +85,7 @@
4A9A3A3A1DC1D75F00DF5183 /* HTMLReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A9A3A391DC1D75F00DF5183 /* HTMLReporter.swift */; };
4DB7815E1CAD72BA00BC4723 /* LegacyCGGeometryFunctionsRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DB7815C1CAD690100BC4723 /* LegacyCGGeometryFunctionsRule.swift */; };
4DCB8E7F1CBE494E0070FCF0 /* RegexHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DCB8E7D1CBE43640070FCF0 /* RegexHelpers.swift */; };
+ 4E342B4C2215C793008E4EF8 /* ReduceBooleanRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E342B4A2215C6DF008E4EF8 /* ReduceBooleanRule.swift */; };
57ED827B1CF656E3002B3513 /* JUnitReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57ED82791CF65183002B3513 /* JUnitReporter.swift */; };
584B0D3A2112BA78002F7E25 /* SonarQubeReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584B0D392112BA78002F7E25 /* SonarQubeReporter.swift */; };
584B0D3C2112E8FB002F7E25 /* CannedSonarQubeReporterOutput.json in Resources */ = {isa = PBXBuildFile; fileRef = 584B0D3B2112E8FB002F7E25 /* CannedSonarQubeReporterOutput.json */; };
@@ -539,6 +540,7 @@
4A9A3A391DC1D75F00DF5183 /* HTMLReporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLReporter.swift; sourceTree = ""; };
4DB7815C1CAD690100BC4723 /* LegacyCGGeometryFunctionsRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyCGGeometryFunctionsRule.swift; sourceTree = ""; };
4DCB8E7D1CBE43640070FCF0 /* RegexHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegexHelpers.swift; sourceTree = ""; };
+ 4E342B4A2215C6DF008E4EF8 /* ReduceBooleanRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReduceBooleanRule.swift; sourceTree = ""; };
5499CA961A2394B700783309 /* Components.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Components.plist; sourceTree = ""; };
5499CA971A2394B700783309 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
57ED82791CF65183002B3513 /* JUnitReporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JUnitReporter.swift; sourceTree = ""; };
@@ -1033,6 +1035,7 @@
D42D2B371E09CC0D00CD7A2E /* FirstWhereRule.swift */,
D414D6AD21D22FF500960935 /* LastWhereRule.swift */,
429644B41FB0A99E00D75128 /* SortedFirstLastRule.swift */,
+ 4E342B4A2215C6DF008E4EF8 /* ReduceBooleanRule.swift */,
);
path = Performance;
sourceTree = "";
@@ -1762,7 +1765,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "if which sourcery >/dev/null; then\n make Source/SwiftLintFramework/Models/MasterRuleList.swift\nelse\n echo \"Sourcery not found, install with 'brew install sourcery'\"\nfi";
+ shellScript = "if which sourcery >/dev/null; then\n make Source/SwiftLintFramework/Models/MasterRuleList.swift\nelse\n echo \"Sourcery not found, install with 'brew install sourcery'\"\nfi\n";
showEnvVarsInLog = 0;
};
6CF24C421FC99616008CB0B1 /* Update Tests/LinuxMain.swift */ = {
@@ -1779,7 +1782,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "if which sourcery >/dev/null; then\n make Tests/LinuxMain.swift\nelse\n echo \"Sourcery not found, install with 'brew install sourcery'\"\nfi";
+ shellScript = "if which sourcery >/dev/null; then\n make Tests/LinuxMain.swift\nelse\n echo \"Sourcery not found, install with 'brew install sourcery'\"\nfi\n";
showEnvVarsInLog = 0;
};
C2265FAB1A4B86AC00158358 /* Check Xcode Version */ = {
@@ -1871,6 +1874,7 @@
E88198571BEA953300333A11 /* ForceCastRule.swift in Sources */,
D44AD2761C0AA5350048F7B0 /* LegacyConstructorRule.swift in Sources */,
D41985E721F85014003BE2B7 /* NSLocalizedStringKeyRule.swift in Sources */,
+ 4E342B4C2215C793008E4EF8 /* ReduceBooleanRule.swift in Sources */,
D286EC021E02DF6F0003CF72 /* SortedImportsRule.swift in Sources */,
D40E041C1F46E3B30043BC4E /* SuperfluousDisableCommandRule.swift in Sources */,
82F614F42106015100D23904 /* MultilineArgumentsBracketsRule.swift in Sources */,
diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift
index 608905f692..bd6c16645a 100644
--- a/Tests/LinuxMain.swift
+++ b/Tests/LinuxMain.swift
@@ -984,6 +984,12 @@ extension QuickDiscouragedPendingTestRuleTests {
]
}
+extension ReduceBooleanRuleTests {
+ static var allTests: [(String, (ReduceBooleanRuleTests) -> () throws -> Void)] = [
+ ("testWithDefaultConfiguration", testWithDefaultConfiguration)
+ ]
+}
+
extension RedundantDiscardableLetRuleTests {
static var allTests: [(String, (RedundantDiscardableLetRuleTests) -> () throws -> Void)] = [
("testWithDefaultConfiguration", testWithDefaultConfiguration)
@@ -1593,6 +1599,7 @@ XCTMain([
testCase(QuickDiscouragedCallRuleTests.allTests),
testCase(QuickDiscouragedFocusedTestRuleTests.allTests),
testCase(QuickDiscouragedPendingTestRuleTests.allTests),
+ testCase(ReduceBooleanRuleTests.allTests),
testCase(RedundantDiscardableLetRuleTests.allTests),
testCase(RedundantNilCoalescingRuleTests.allTests),
testCase(RedundantObjcAttributeRuleTests.allTests),
diff --git a/Tests/SwiftLintFrameworkTests/AutomaticRuleTests.generated.swift b/Tests/SwiftLintFrameworkTests/AutomaticRuleTests.generated.swift
index 564688be19..d2ab101b03 100644
--- a/Tests/SwiftLintFrameworkTests/AutomaticRuleTests.generated.swift
+++ b/Tests/SwiftLintFrameworkTests/AutomaticRuleTests.generated.swift
@@ -510,6 +510,12 @@ class QuickDiscouragedPendingTestRuleTests: XCTestCase {
}
}
+class ReduceBooleanRuleTests: XCTestCase {
+ func testWithDefaultConfiguration() {
+ verifyRule(ReduceBooleanRule.description)
+ }
+}
+
class RedundantDiscardableLetRuleTests: XCTestCase {
func testWithDefaultConfiguration() {
verifyRule(RedundantDiscardableLetRule.description)