diff --git a/.swiftlint.yml b/.swiftlint.yml index 6f0bd6043e..78e0b69dd4 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -27,6 +27,7 @@ opt_in_rules: - file_header - file_name - first_where + - flatmap_over_map_reduce - identical_operands - joined_default_parameter - legacy_random diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e84800628..a651a4a635 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,11 @@ [nvanfleet](https://github.com/nvanfleet) [#2866](https://github.com/realm/SwiftLint/issues/2866) +* Add `flatmap_over_map_reduce` opt-in rule to prefer + using `flatMap` over `map { ... }.reduce([], +)`. + [Marcelo Fabri](https://github.com/marcelofabri) + [#2883](https://github.com/realm/SwiftLint/issues/2883) + #### Bug Fixes * None. diff --git a/Rules.md b/Rules.md index 302546b45e..33a48cbb47 100644 --- a/Rules.md +++ b/Rules.md @@ -54,6 +54,7 @@ * [File Name](#file-name) * [File Types Order](#file-types-order) * [First Where](#first-where) +* [FlatMap over map and reduce](#flatmap-over-map-and-reduce) * [For Where](#for-where) * [Force Cast](#force-cast) * [Force Try](#force-try) @@ -8429,6 +8430,39 @@ if let pause = timeTracker.pauses.filter("beginDate < %@", beginDate).first { pr +## FlatMap over map and reduce + +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`flatmap_over_map_reduce` | Disabled | No | performance | No | 3.0.0 + +Prefer `flatMap` over `map` followed by `reduce([], +)`. + +### Examples + +
+Non Triggering Examples + +```swift +let foo = bar.map { $0.count }.reduce(0, +) +``` + +```swift +let foo = bar.flatMap { $0.array } +``` + +
+
+Triggering Examples + +```swift +let foo = ↓bar.map { $0.array }.reduce([], +) +``` + +
+ + + ## For Where 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 b0ef7db703..b34c694029 100644 --- a/Source/SwiftLintFramework/Models/MasterRuleList.swift +++ b/Source/SwiftLintFramework/Models/MasterRuleList.swift @@ -55,6 +55,7 @@ public let masterRuleList = RuleList(rules: [ FileNameRule.self, FileTypesOrderRule.self, FirstWhereRule.self, + FlatMapOverMapReduceRule.self, ForWhereRule.self, ForceCastRule.self, ForceTryRule.self, diff --git a/Source/SwiftLintFramework/Rules/Performance/FlatMapOverMapReduceRule.swift b/Source/SwiftLintFramework/Rules/Performance/FlatMapOverMapReduceRule.swift new file mode 100644 index 0000000000..5f7f5636ca --- /dev/null +++ b/Source/SwiftLintFramework/Rules/Performance/FlatMapOverMapReduceRule.swift @@ -0,0 +1,27 @@ +import SourceKittenFramework + +public struct FlatMapOverMapReduceRule: CallPairRule, OptInRule, ConfigurationProviderRule, AutomaticTestableRule { + public var configuration = SeverityConfiguration(.warning) + + public init() {} + + public static let description = RuleDescription( + identifier: "flatmap_over_map_reduce", + name: "FlatMap over map and reduce", + description: "Prefer `flatMap` over `map` followed by `reduce([], +)`.", + kind: .performance, + nonTriggeringExamples: [ + "let foo = bar.map { $0.count }.reduce(0, +)", + "let foo = bar.flatMap { $0.array }" + ], + triggeringExamples: [ + "let foo = ↓bar.map { $0.array }.reduce([], +)" + ] + ) + + public func validate(file: File) -> [StyleViolation] { + let pattern = "[\\}\\)]\\s*\\.reduce\\s*\\(\\[\\s*\\],\\s*\\+\\s*\\)" + return validate(file: file, pattern: pattern, patternSyntaxKinds: [.identifier], + callNameSuffix: ".map", severity: configuration.severity) + } +} diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj index 239b92ec97..ed7ef78094 100644 --- a/SwiftLint.xcodeproj/project.pbxproj +++ b/SwiftLint.xcodeproj/project.pbxproj @@ -282,6 +282,7 @@ D45255C81F0932F8003C9B56 /* RuleDescription+Examples.swift in Sources */ = {isa = PBXBuildFile; fileRef = D45255C71F0932F8003C9B56 /* RuleDescription+Examples.swift */; }; D462021F1E15F52D0027AAD1 /* NumberSeparatorRuleExamples.swift in Sources */ = {isa = PBXBuildFile; fileRef = D462021E1E15F52D0027AAD1 /* NumberSeparatorRuleExamples.swift */; }; D46252541DF63FB200BE2CA1 /* NumberSeparatorRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D46252531DF63FB200BE2CA1 /* NumberSeparatorRule.swift */; }; + D466B620233D229F0068190B /* FlatMapOverMapReduceRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D466B61F233D229F0068190B /* FlatMapOverMapReduceRule.swift */; }; D46A317F1F1CEDCD00AF914A /* UnneededParenthesesInClosureArgumentRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D46A317E1F1CEDCD00AF914A /* UnneededParenthesesInClosureArgumentRule.swift */; }; D46E041D1DE3712C00728374 /* TrailingCommaRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D46E041C1DE3712C00728374 /* TrailingCommaRule.swift */; }; D47079A71DFCEB2D00027086 /* EmptyParenthesesWithTrailingClosureRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47079A61DFCEB2D00027086 /* EmptyParenthesesWithTrailingClosureRule.swift */; }; @@ -779,6 +780,7 @@ D45255C71F0932F8003C9B56 /* RuleDescription+Examples.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "RuleDescription+Examples.swift"; sourceTree = ""; }; D462021E1E15F52D0027AAD1 /* NumberSeparatorRuleExamples.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumberSeparatorRuleExamples.swift; sourceTree = ""; }; D46252531DF63FB200BE2CA1 /* NumberSeparatorRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumberSeparatorRule.swift; sourceTree = ""; }; + D466B61F233D229F0068190B /* FlatMapOverMapReduceRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlatMapOverMapReduceRule.swift; sourceTree = ""; }; D46A317E1F1CEDCD00AF914A /* UnneededParenthesesInClosureArgumentRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnneededParenthesesInClosureArgumentRule.swift; sourceTree = ""; }; D46E041C1DE3712C00728374 /* TrailingCommaRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrailingCommaRule.swift; sourceTree = ""; }; D47079A61DFCEB2D00027086 /* EmptyParenthesesWithTrailingClosureRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmptyParenthesesWithTrailingClosureRule.swift; sourceTree = ""; }; @@ -1088,6 +1090,7 @@ E847F0A81BFBBABD00EA9363 /* EmptyCountRule.swift */, 740DF1AF203F5AFC0081F694 /* EmptyStringRule.swift */, D42D2B371E09CC0D00CD7A2E /* FirstWhereRule.swift */, + D466B61F233D229F0068190B /* FlatMapOverMapReduceRule.swift */, D414D6AD21D22FF500960935 /* LastWhereRule.swift */, 756C0777222EA49400A111F4 /* ReduceIntoRule.swift */, 429644B41FB0A99E00D75128 /* SortedFirstLastRule.swift */, @@ -2197,6 +2200,7 @@ 4A9A3A3A1DC1D75F00DF5183 /* HTMLReporter.swift in Sources */, D40F83881DE9179200524C62 /* TrailingCommaConfiguration.swift in Sources */, 827169B31F488181003FB9AF /* ExplicitEnumRawValueRule.swift in Sources */, + D466B620233D229F0068190B /* FlatMapOverMapReduceRule.swift in Sources */, D41985E921FAB62F003BE2B7 /* DeploymentTargetRule.swift in Sources */, 62FE5D32200CABDD00F68793 /* DiscouragedOptionalCollectionExamples.swift in Sources */, D49896F12026B36C00814A83 /* RedundantSetAccessControlRule.swift in Sources */, diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index ba9c7a8dd1..5d48007bfd 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -528,6 +528,12 @@ extension FirstWhereRuleTests { ] } +extension FlatMapOverMapReduceRuleTests { + static var allTests: [(String, (FlatMapOverMapReduceRuleTests) -> () throws -> Void)] = [ + ("testWithDefaultConfiguration", testWithDefaultConfiguration) + ] +} + extension ForWhereRuleTests { static var allTests: [(String, (ForWhereRuleTests) -> () throws -> Void)] = [ ("testWithDefaultConfiguration", testWithDefaultConfiguration) @@ -1624,6 +1630,7 @@ XCTMain([ testCase(FileNameRuleTests.allTests), testCase(FileTypesOrderRuleTests.allTests), testCase(FirstWhereRuleTests.allTests), + testCase(FlatMapOverMapReduceRuleTests.allTests), testCase(ForWhereRuleTests.allTests), testCase(ForceCastRuleTests.allTests), testCase(ForceTryRuleTests.allTests), diff --git a/Tests/SwiftLintFrameworkTests/AutomaticRuleTests.generated.swift b/Tests/SwiftLintFrameworkTests/AutomaticRuleTests.generated.swift index 52d34728b5..19a4d02c32 100644 --- a/Tests/SwiftLintFrameworkTests/AutomaticRuleTests.generated.swift +++ b/Tests/SwiftLintFrameworkTests/AutomaticRuleTests.generated.swift @@ -228,6 +228,12 @@ class FirstWhereRuleTests: XCTestCase { } } +class FlatMapOverMapReduceRuleTests: XCTestCase { + func testWithDefaultConfiguration() { + verifyRule(FlatMapOverMapReduceRule.description) + } +} + class ForWhereRuleTests: XCTestCase { func testWithDefaultConfiguration() { verifyRule(ForWhereRule.description)