From 63bd8ce7a586c50a54ab04e5168f2bfa636f622e Mon Sep 17 00:00:00 2001 From: antonnazarov Date: Mon, 27 Apr 2020 05:16:17 +0300 Subject: [PATCH 1/4] Add PreferZeroOverExplicitInitRule --- CHANGELOG.md | 5 ++ .../Models/MasterRuleList.swift | 1 + .../PreferZeroOverExplicitInitRule.swift | 78 +++++++++++++++++++ SwiftLint.xcodeproj/project.pbxproj | 4 + Tests/LinuxMain.swift | 7 ++ .../AutomaticRuleTests.generated.swift | 6 ++ 6 files changed, 101 insertions(+) create mode 100644 Source/SwiftLintFramework/Rules/Style/PreferZeroOverExplicitInitRule.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 6daad7d50..267af8e40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,11 @@ * Extend `empty_enum_arguments` rule to support `if case` and `guard case`. [Zsolt Kovács](https://github.com/lordzsolt) [#3103](https://github.com/realm/SwiftLint/issues/3103) +* Add `prefer_zero_over_explicit_init` opt-in rule to enforce using + `.zero` instead of calling constructor with zero arguments + (e.g. `CGPoint(x: 0, y: 0)`) when using CoreGraphics types. + [Anton Nazarov](https://github.com/MortyMerr) + [#3190](https://github.com/realm/SwiftLint/issues/3190) #### Bug Fixes diff --git a/Source/SwiftLintFramework/Models/MasterRuleList.swift b/Source/SwiftLintFramework/Models/MasterRuleList.swift index 09134193f..7e2ea598d 100644 --- a/Source/SwiftLintFramework/Models/MasterRuleList.swift +++ b/Source/SwiftLintFramework/Models/MasterRuleList.swift @@ -124,6 +124,7 @@ public let masterRuleList = RuleList(rules: [ OverrideInExtensionRule.self, PatternMatchingKeywordsRule.self, PreferSelfTypeOverTypeOfSelfRule.self, + PreferZeroOverExplicitInitRule.self, PrefixedTopLevelConstantRule.self, PrivateActionRule.self, PrivateOutletRule.self, diff --git a/Source/SwiftLintFramework/Rules/Style/PreferZeroOverExplicitInitRule.swift b/Source/SwiftLintFramework/Rules/Style/PreferZeroOverExplicitInitRule.swift new file mode 100644 index 000000000..b9337b7af --- /dev/null +++ b/Source/SwiftLintFramework/Rules/Style/PreferZeroOverExplicitInitRule.swift @@ -0,0 +1,78 @@ +import Foundation +import SourceKittenFramework + +public struct PreferZeroOverExplicitInitRule: OptInRule, ConfigurationProviderRule, SubstitutionCorrectableRule, + AutomaticTestableRule { + public var configuration = SeverityConfiguration(.warning) + private var pattern: String { + let zero = "\\s*:\\s*0(.0*)?\\s*" + let type = [CGPoint.self, CGSize.self, CGVector.self, CGRect.self] + .map(String.init(describing:)) + .joined(separator: "|") + let firstArg = "(\(["x", "dx", "width"].joined(separator: "|")))" + let secondArg = "(\(["y", "dy", "height"].joined(separator: "|")))" + let thirdAndFourthArg = "(\\,\\s*width\(zero)\\,\\s*height\(zero))?" + return "(\(type))\\(\\s*\(firstArg)\(zero)\\,\\s*\(secondArg)\(zero)\(thirdAndFourthArg)\\)" + } + + public static let description = RuleDescription( + identifier: "prefer_zero_over_explicit_init", + name: "Prefer Zero Over Explicit Init", + description: "Prefer `.zero` over excplicit init with zero parameters (e.g. `CGPoint(x: 0, y: 0)`)", + kind: .style, + minSwiftVersion: .fiveDotOne, + nonTriggeringExamples: [ + Example("CGRect(x: 0, y: 0, width: 0, height: 1)"), + Example("CGPoint(x: 0, y: -1"), + Example("CGSize(width: 2, height: 4)"), + Example("CGVector(dx: -5, dy: 0)") + ], + triggeringExamples: [ + Example("↓CGPoint(x: 0, y: 0)"), + Example("↓CGPoint(x: 0.000000, y: 0)"), + Example("↓CGPoint(x: 0.000000, y: 0.000)"), + Example("↓CGRect(x: 0, y: 0, width: 0, height: 0)"), + Example("↓CGSize(width: 0, height: 0)"), + Example("↓CGVector(dx: 0, dy: 0)") + ], + corrections: [ + Example("↓CGPoint(x: 0, y: 0)"): Example("CGPoint.zero"), + Example("↓CGRect(x: 0, y: 0, width: 0, height: 0)"): Example("CGRect.zero"), + Example("↓CGSize(width: 0, height: 0.000)"): Example("CGSize.zero"), + Example("↓CGVector(dx: 0, dy: 0)"): Example("CGVector.zero") + ] + ) + + public init() {} + + public func validate(file: SwiftLintFile) -> [StyleViolation] { + violationRanges(in: file).map { + StyleViolation( + ruleDescription: Self.description, + severity: configuration.severity, + location: Location(file: file, characterOffset: $0.location) + ) + } + } + + public func violationRanges(in file: SwiftLintFile) -> [NSRange] { + guard SwiftVersion.current >= Self.description.minSwiftVersion else { + return [] + } + + return file.matchesAndSyntaxKinds(matching: pattern) + .filter { + $0.1 == [.identifier, .identifier, .number, .identifier, .number] || + $0.1 == [ + .identifier, .identifier, .number, .identifier, .number, .identifier, .number, .identifier, .number + ] + } + .map { $0.0.range(at: 0) } + } + + public func substitution(for violationRange: NSRange, in file: SwiftLintFile) -> (NSRange, String)? { + let declaration = file.stringView.substring(with: violationRange) + guard let typeEndIndex = declaration.firstIndex(of: "(") else { return nil } + return (violationRange, "\(declaration.prefix(upTo: typeEndIndex)).zero") + } +} diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj index e611ff400..5c3d5e37e 100644 --- a/SwiftLint.xcodeproj/project.pbxproj +++ b/SwiftLint.xcodeproj/project.pbxproj @@ -164,6 +164,7 @@ 77DFF0E923442DE30041EEB4 /* RawValueForCamelCasedCodableEnumRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7723A4DE23442D7100F38590 /* RawValueForCamelCasedCodableEnumRule.swift */; }; 787CDE39208E7D41005F3D2F /* SwitchCaseAlignmentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 787CDE38208E7D41005F3D2F /* SwitchCaseAlignmentConfiguration.swift */; }; 787CDE3B208F9C34005F3D2F /* SwitchCaseAlignmentRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 787CDE3A208F9C34005F3D2F /* SwitchCaseAlignmentRuleTests.swift */; }; + 78B9051D24564AF600C81B0A /* PreferZeroOverExplicitInitRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B9051A245646CD00C81B0A /* PreferZeroOverExplicitInitRule.swift */; }; 78F032461D7C877E00BE709A /* OverriddenSuperCallRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78F032441D7C877800BE709A /* OverriddenSuperCallRule.swift */; }; 78F032481D7D614300BE709A /* OverridenSuperCallConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78F032471D7D614300BE709A /* OverridenSuperCallConfiguration.swift */; }; 7C0C2E7A1D2866CB0076435A /* ExplicitInitRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0C2E791D2866CB0076435A /* ExplicitInitRule.swift */; }; @@ -683,6 +684,7 @@ 7723A4DE23442D7100F38590 /* RawValueForCamelCasedCodableEnumRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RawValueForCamelCasedCodableEnumRule.swift; sourceTree = ""; }; 787CDE38208E7D41005F3D2F /* SwitchCaseAlignmentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchCaseAlignmentConfiguration.swift; sourceTree = ""; }; 787CDE3A208F9C34005F3D2F /* SwitchCaseAlignmentRuleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchCaseAlignmentRuleTests.swift; sourceTree = ""; }; + 78B9051A245646CD00C81B0A /* PreferZeroOverExplicitInitRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferZeroOverExplicitInitRule.swift; sourceTree = ""; }; 78F032441D7C877800BE709A /* OverriddenSuperCallRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverriddenSuperCallRule.swift; sourceTree = ""; }; 78F032471D7D614300BE709A /* OverridenSuperCallConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverridenSuperCallConfiguration.swift; sourceTree = ""; }; 7C0C2E791D2866CB0076435A /* ExplicitInitRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExplicitInitRule.swift; sourceTree = ""; }; @@ -1306,6 +1308,7 @@ D4FBADCF1E00DA0400669C73 /* OperatorUsageWhitespaceRule.swift */, D43CDEDB23BDB8D30074F3EE /* OptionalEnumCaseMatchingRule.swift */, D46C7C3D23BF2F6A007C517F /* PreferSelfTypeOverTypeOfSelfRule.swift */, + 78B9051A245646CD00C81B0A /* PreferZeroOverExplicitInitRule.swift */, 62DADC471FFF0423002B6319 /* PrefixedTopLevelConstantRule.swift */, D47F31141EC918B600E3E1CA /* ProtocolPropertyAccessorsOrderRule.swift */, D4C889701E385B7B00BAE88D /* RedundantDiscardableLetRule.swift */, @@ -2356,6 +2359,7 @@ 6C15818D237026AC00F582A2 /* GitHubActionsLoggingReporter.swift in Sources */, 62FE5D32200CABDD00F68793 /* DiscouragedOptionalCollectionExamples.swift in Sources */, D41C09BD23C1B99D00F105C4 /* OrphanedDocCommentRule.swift in Sources */, + 78B9051D24564AF600C81B0A /* PreferZeroOverExplicitInitRule.swift in Sources */, D49896F12026B36C00814A83 /* RedundantSetAccessControlRule.swift in Sources */, E4A6CF752363CBFB00DD5B18 /* RandomAccessCollection+Swiftlint.swift in Sources */, 29FFC37A1F15764D007E4825 /* FileLengthRuleConfiguration.swift in Sources */, diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 545dfdcb9..cffe2ca50 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -1094,6 +1094,12 @@ extension PreferSelfTypeOverTypeOfSelfRuleTests { ] } +extension PreferZeroOverExplicitInitRuleTests { + static var allTests: [(String, (PreferZeroOverExplicitInitRuleTests) -> () throws -> Void)] = [ + ("testWithDefaultConfiguration", testWithDefaultConfiguration) + ] +} + extension PrefixedTopLevelConstantRuleTests { static var allTests: [(String, (PrefixedTopLevelConstantRuleTests) -> () throws -> Void)] = [ ("testDefaultConfiguration", testDefaultConfiguration), @@ -1826,6 +1832,7 @@ XCTMain([ testCase(OverrideInExtensionRuleTests.allTests), testCase(PatternMatchingKeywordsRuleTests.allTests), testCase(PreferSelfTypeOverTypeOfSelfRuleTests.allTests), + testCase(PreferZeroOverExplicitInitRuleTests.allTests), testCase(PrefixedTopLevelConstantRuleTests.allTests), testCase(PrivateActionRuleTests.allTests), testCase(PrivateOutletRuleTests.allTests), diff --git a/Tests/SwiftLintFrameworkTests/AutomaticRuleTests.generated.swift b/Tests/SwiftLintFrameworkTests/AutomaticRuleTests.generated.swift index 198c53ae7..88f853d78 100644 --- a/Tests/SwiftLintFrameworkTests/AutomaticRuleTests.generated.swift +++ b/Tests/SwiftLintFrameworkTests/AutomaticRuleTests.generated.swift @@ -522,6 +522,12 @@ class PreferSelfTypeOverTypeOfSelfRuleTests: XCTestCase { } } +class PreferZeroOverExplicitInitRuleTests: XCTestCase { + func testWithDefaultConfiguration() { + verifyRule(PreferZeroOverExplicitInitRule.description) + } +} + class PrivateActionRuleTests: XCTestCase { func testWithDefaultConfiguration() { verifyRule(PrivateActionRule.description) From 09a0a216d1c236a249aca37ce55e5eefe470878e Mon Sep 17 00:00:00 2001 From: antonnazarov Date: Mon, 27 Apr 2020 05:25:18 +0300 Subject: [PATCH 2/4] Change types on string representation --- .../Rules/Style/PreferZeroOverExplicitInitRule.swift | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Source/SwiftLintFramework/Rules/Style/PreferZeroOverExplicitInitRule.swift b/Source/SwiftLintFramework/Rules/Style/PreferZeroOverExplicitInitRule.swift index b9337b7af..8ed3729b0 100644 --- a/Source/SwiftLintFramework/Rules/Style/PreferZeroOverExplicitInitRule.swift +++ b/Source/SwiftLintFramework/Rules/Style/PreferZeroOverExplicitInitRule.swift @@ -6,13 +6,11 @@ public struct PreferZeroOverExplicitInitRule: OptInRule, ConfigurationProviderRu public var configuration = SeverityConfiguration(.warning) private var pattern: String { let zero = "\\s*:\\s*0(.0*)?\\s*" - let type = [CGPoint.self, CGSize.self, CGVector.self, CGRect.self] - .map(String.init(describing:)) - .joined(separator: "|") + let type = "(\(["CGPoint", "CGSize", "CGVector", "CGRect"].joined(separator: "|")))" let firstArg = "(\(["x", "dx", "width"].joined(separator: "|")))" let secondArg = "(\(["y", "dy", "height"].joined(separator: "|")))" let thirdAndFourthArg = "(\\,\\s*width\(zero)\\,\\s*height\(zero))?" - return "(\(type))\\(\\s*\(firstArg)\(zero)\\,\\s*\(secondArg)\(zero)\(thirdAndFourthArg)\\)" + return "\(type)\\(\\s*\(firstArg)\(zero)\\,\\s*\(secondArg)\(zero)\(thirdAndFourthArg)\\)" } public static let description = RuleDescription( From 30c7add095a0bca6e07cec1200a1cf67129144ae Mon Sep 17 00:00:00 2001 From: Marcelo Fabri Date: Wed, 5 Aug 2020 15:11:37 -0700 Subject: [PATCH 3/4] Change to .idiomatic --- .../PreferZeroOverExplicitInitRule.swift | 9 ++------- SwiftLint.xcodeproj/project.pbxproj | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) rename Source/SwiftLintFramework/Rules/{Style => Idiomatic}/PreferZeroOverExplicitInitRule.swift (91%) diff --git a/Source/SwiftLintFramework/Rules/Style/PreferZeroOverExplicitInitRule.swift b/Source/SwiftLintFramework/Rules/Idiomatic/PreferZeroOverExplicitInitRule.swift similarity index 91% rename from Source/SwiftLintFramework/Rules/Style/PreferZeroOverExplicitInitRule.swift rename to Source/SwiftLintFramework/Rules/Idiomatic/PreferZeroOverExplicitInitRule.swift index 8ed3729b0..e930b18fd 100644 --- a/Source/SwiftLintFramework/Rules/Style/PreferZeroOverExplicitInitRule.swift +++ b/Source/SwiftLintFramework/Rules/Idiomatic/PreferZeroOverExplicitInitRule.swift @@ -16,9 +16,8 @@ public struct PreferZeroOverExplicitInitRule: OptInRule, ConfigurationProviderRu public static let description = RuleDescription( identifier: "prefer_zero_over_explicit_init", name: "Prefer Zero Over Explicit Init", - description: "Prefer `.zero` over excplicit init with zero parameters (e.g. `CGPoint(x: 0, y: 0)`)", - kind: .style, - minSwiftVersion: .fiveDotOne, + description: "Prefer `.zero` over explicit init with zero parameters (e.g. `CGPoint(x: 0, y: 0)`)", + kind: .idiomatic, nonTriggeringExamples: [ Example("CGRect(x: 0, y: 0, width: 0, height: 1)"), Example("CGPoint(x: 0, y: -1"), @@ -54,10 +53,6 @@ public struct PreferZeroOverExplicitInitRule: OptInRule, ConfigurationProviderRu } public func violationRanges(in file: SwiftLintFile) -> [NSRange] { - guard SwiftVersion.current >= Self.description.minSwiftVersion else { - return [] - } - return file.matchesAndSyntaxKinds(matching: pattern) .filter { $0.1 == [.identifier, .identifier, .number, .identifier, .number] || diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj index 5c3d5e37e..68a817cea 100644 --- a/SwiftLint.xcodeproj/project.pbxproj +++ b/SwiftLint.xcodeproj/project.pbxproj @@ -1308,7 +1308,6 @@ D4FBADCF1E00DA0400669C73 /* OperatorUsageWhitespaceRule.swift */, D43CDEDB23BDB8D30074F3EE /* OptionalEnumCaseMatchingRule.swift */, D46C7C3D23BF2F6A007C517F /* PreferSelfTypeOverTypeOfSelfRule.swift */, - 78B9051A245646CD00C81B0A /* PreferZeroOverExplicitInitRule.swift */, 62DADC471FFF0423002B6319 /* PrefixedTopLevelConstantRule.swift */, D47F31141EC918B600E3E1CA /* ProtocolPropertyAccessorsOrderRule.swift */, D4C889701E385B7B00BAE88D /* RedundantDiscardableLetRule.swift */, @@ -1384,6 +1383,7 @@ 827169B41F48D712003FB9AF /* NoGroupingExtensionRule.swift */, D4B022971E102EE8007E5297 /* ObjectLiteralRule.swift */, D403A4A21F4DB5510020CA02 /* PatternMatchingKeywordsRule.swift */, + 78B9051A245646CD00C81B0A /* PreferZeroOverExplicitInitRule.swift */, 1E3C2D701EE36C6F00C8386D /* PrivateOverFilePrivateRule.swift */, 24B4DF0B1D6DFA370097803B /* RedundantNilCoalescingRule.swift */, 18B90B6A21ADD99800B60749 /* RedundantObjcAttributeRule.swift */, From 244f41c2ddc22b280fdc2037bae339a55030cdbc Mon Sep 17 00:00:00 2001 From: Marcelo Fabri Date: Wed, 5 Aug 2020 15:12:22 -0700 Subject: [PATCH 4/4] Improve CHANGELOG --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 267af8e40..01b6a70ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,9 +32,10 @@ * Extend `empty_enum_arguments` rule to support `if case` and `guard case`. [Zsolt Kovács](https://github.com/lordzsolt) [#3103](https://github.com/realm/SwiftLint/issues/3103) + * Add `prefer_zero_over_explicit_init` opt-in rule to enforce using - `.zero` instead of calling constructor with zero arguments - (e.g. `CGPoint(x: 0, y: 0)`) when using CoreGraphics types. + `.zero` instead of calling constructor with zero arguments + (e.g. `CGPoint(x: 0, y: 0)`) when using CoreGraphics types. [Anton Nazarov](https://github.com/MortyMerr) [#3190](https://github.com/realm/SwiftLint/issues/3190)