From 9e6644923e09bac976d3297d65486242d6f83f02 Mon Sep 17 00:00:00 2001 From: Aaron McTavish Date: Tue, 12 Jan 2016 08:17:28 +0000 Subject: [PATCH 1/6] Implements LegacyConstantRule From #319 CGGeometry: Prefer struct-scope constants CGRect.infinite, CGRect.null, etc. over global constants CGRectInfinite, CGRectNull, etc. --- CHANGELOG.md | 4 + .../Models/Configuration.swift | 1 + .../Rules/LegacyConstantRule.swift | 80 +++++++++++++++++++ .../StringRuleTests.swift | 4 + SwiftLint.xcodeproj/project.pbxproj | 4 + 5 files changed, 93 insertions(+) create mode 100644 Source/SwiftLintFramework/Rules/LegacyConstantRule.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index ace2f414ae..62ec6e34b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,10 @@ [Aaron McTavish](https://github.com/aamctustwo) [#202](https://github.com/realm/SwiftLint/issues/202) +* Add LegacyConstantRule. + [Aaron McTavish](https://github.com/aamctustwo) + [#319](https://github.com/realm/SwiftLint/issues/319) + ##### Bug Fixes * AutoCorrect for TrailingNewlineRule only removes at most one line. diff --git a/Source/SwiftLintFramework/Models/Configuration.swift b/Source/SwiftLintFramework/Models/Configuration.swift index 3a0ca99eb3..62956f9615 100644 --- a/Source/SwiftLintFramework/Models/Configuration.swift +++ b/Source/SwiftLintFramework/Models/Configuration.swift @@ -159,6 +159,7 @@ public struct Configuration: Equatable { ForceCastRule(), ForceTryRule(), LeadingWhitespaceRule(), + LegacyConstantRule(), LegacyConstructorRule(), NestingRule(), OpeningBraceRule(), diff --git a/Source/SwiftLintFramework/Rules/LegacyConstantRule.swift b/Source/SwiftLintFramework/Rules/LegacyConstantRule.swift new file mode 100644 index 0000000000..e8afbd0cbe --- /dev/null +++ b/Source/SwiftLintFramework/Rules/LegacyConstantRule.swift @@ -0,0 +1,80 @@ +// +// LegacyConstantRule.swift +// SwiftLint +// +// Created by Aaron McTavish on 12/01/2016. +// Copyright © 2016 Realm. All rights reserved. +// + +import SourceKittenFramework +import CoreGraphics + +public struct LegacyConstantRule: CorrectableRule { + public static let description = RuleDescription( + identifier: "legacy_constant", + name: "Legacy Constant", + description: "Struct-scoped constants are preferred over legacy global constants.", + nonTriggeringExamples: [ + "CGRect.infinite", + "CGPoint.zero", + "CGRect.zero", + "CGSize.zero", + "CGRect.null" + ], + triggeringExamples: [ + "↓CGRectInfinite", + "↓CGPointZero", + "↓CGRectZero", + "↓CGSizeZero", + "↓CGRectNull" + ], + corrections: [ + "CGRectInfinite\n": "CGRect.infinite\n", + "CGPointZero\n": "CGPoint.zero\n", + "CGRectZero\n": "CGRect.zero\n", + "CGSizeZero\n": "CGSize.zero\n", + "CGRectNull\n": "CGRect.null\n" + ] + ) + + public func validateFile(file: File) -> [StyleViolation] { + let constants = ["CGRectInfinite", "CGPointZero", "CGRectZero", "CGSizeZero", + "CGRectNull"] + + let pattern = "\\b(" + constants.joinWithSeparator("|") + ")\\b" + + return file.matchPattern(pattern, withSyntaxKinds: [.Identifier]).map { + StyleViolation(ruleDescription: self.dynamicType.description, + location: Location(file: file, characterOffset: $0.location)) + } + } + + public func correctFile(file: File) -> [Correction] { + let patterns = [ + "CGRectInfinite": "CGRect.infinite", + "CGPointZero": "CGPoint.zero", + "CGRectZero": "CGRect.zero", + "CGSizeZero": "CGSize.zero", + "CGRectNull": "CGRect.null" + ] + + let description = self.dynamicType.description + var corrections = [Correction]() + var contents = file.contents + + for (pattern, template) in patterns { + let matches = file.matchPattern(pattern, excludingSyntaxKinds: [.Comment]) + + let regularExpression = regex(pattern) + for range in matches.reverse() { + contents = regularExpression.stringByReplacingMatchesInString(contents, + options: [], range: range, withTemplate: template) + let location = Location(file: file, characterOffset: range.location) + corrections.append(Correction(ruleDescription: description, location: location)) + } + } + + file.write(contents) + return corrections + } +} diff --git a/Source/SwiftLintFrameworkTests/StringRuleTests.swift b/Source/SwiftLintFrameworkTests/StringRuleTests.swift index d71afc262f..2e9500efcf 100644 --- a/Source/SwiftLintFrameworkTests/StringRuleTests.swift +++ b/Source/SwiftLintFrameworkTests/StringRuleTests.swift @@ -113,4 +113,8 @@ class StringRuleTests: XCTestCase { func testConditionalBindingCascade() { verifyRule(ConditionalBindingCascadeRule.description) } + + func testLegacyConstant() { + verifyRule(LegacyConstantRule.description) + } } diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj index 15a193a886..f0c811981e 100644 --- a/SwiftLint.xcodeproj/project.pbxproj +++ b/SwiftLint.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 006ECFC41C44E99E00EF6364 /* LegacyConstantRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 006ECFC31C44E99E00EF6364 /* LegacyConstantRule.swift */; }; 00970C751C3FC3A0007C4A83 /* ConditionalBindingCascadeRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00970C741C3FC3A0007C4A83 /* ConditionalBindingCascadeRule.swift */; }; 02FD8AEF1BFC18D60014BFFB /* ExtendedNSStringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02FD8AEE1BFC18D60014BFFB /* ExtendedNSStringTests.swift */; }; 1F11B3CF1C252F23002E8FA8 /* ClosingBraceRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F11B3CE1C252F23002E8FA8 /* ClosingBraceRule.swift */; }; @@ -143,6 +144,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 006ECFC31C44E99E00EF6364 /* LegacyConstantRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyConstantRule.swift; sourceTree = ""; }; 00970C741C3FC3A0007C4A83 /* ConditionalBindingCascadeRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConditionalBindingCascadeRule.swift; sourceTree = ""; }; 02FD8AEE1BFC18D60014BFFB /* ExtendedNSStringTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtendedNSStringTests.swift; sourceTree = ""; }; 1F11B3CE1C252F23002E8FA8 /* ClosingBraceRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClosingBraceRule.swift; sourceTree = ""; }; @@ -489,6 +491,7 @@ E816194D1BFBFEAB00946723 /* ForceTryRule.swift */, E88DEA8F1B099A3100A66CB0 /* FunctionBodyLengthRule.swift */, E88DEA7D1B098F2A00A66CB0 /* LeadingWhitespaceRule.swift */, + 006ECFC31C44E99E00EF6364 /* LegacyConstantRule.swift */, D44AD2741C0AA3730048F7B0 /* LegacyConstructorRule.swift */, E88DEA7B1B098D7D00A66CB0 /* LineLengthRule.swift */, E88DEA951B099CF200A66CB0 /* NestingRule.swift */, @@ -759,6 +762,7 @@ E88198441BEA93D200333A11 /* ColonRule.swift in Sources */, E809EDA11B8A71DF00399043 /* Configuration.swift in Sources */, E8EA41171C2D1DBE004F9930 /* CheckstyleReporter.swift in Sources */, + 006ECFC41C44E99E00EF6364 /* LegacyConstantRule.swift in Sources */, E88DEA731B0984C400A66CB0 /* String+SwiftLint.swift in Sources */, E88198591BEA95F100333A11 /* LeadingWhitespaceRule.swift in Sources */, 24E17F721B14BB3F008195BE /* File+Cache.swift in Sources */, From e13907ead30ec5750f174fc6a8e7de3e3b74aa4a Mon Sep 17 00:00:00 2001 From: JP Simard Date: Tue, 12 Jan 2016 18:35:31 -0800 Subject: [PATCH 2/6] fix infinite regex search & 'where' false positive in ConditionalBindingCascadeRule --- .../Extensions/File+SwiftLint.swift | 3 ++- .../Rules/ConditionalBindingCascadeRule.swift | 13 ++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Source/SwiftLintFramework/Extensions/File+SwiftLint.swift b/Source/SwiftLintFramework/Extensions/File+SwiftLint.swift index 45f0b86cbf..9b04b6c668 100644 --- a/Source/SwiftLintFramework/Extensions/File+SwiftLint.swift +++ b/Source/SwiftLintFramework/Extensions/File+SwiftLint.swift @@ -15,7 +15,8 @@ internal func regex(pattern: String) -> NSRegularExpression { // confirmed to work, so it's ok to force-try here. // swiftlint:disable:next force_try - return try! NSRegularExpression(pattern: pattern, options: [.AnchorsMatchLines]) + return try! NSRegularExpression(pattern: pattern, + options: [.AnchorsMatchLines, .DotMatchesLineSeparators]) } extension File { diff --git a/Source/SwiftLintFramework/Rules/ConditionalBindingCascadeRule.swift b/Source/SwiftLintFramework/Rules/ConditionalBindingCascadeRule.swift index 97c117f443..cf9b1d350a 100644 --- a/Source/SwiftLintFramework/Rules/ConditionalBindingCascadeRule.swift +++ b/Source/SwiftLintFramework/Rules/ConditionalBindingCascadeRule.swift @@ -22,7 +22,8 @@ public struct ConditionalBindingCascadeRule: Rule { "if let a = b, \n c = d \n {", "if let a = b { if let c = d {", "if let a = b { let c = d({ foo in ... })", - "guard let a = b, c = d else {" + "guard let a = b, c = d else {", + "guard let a = b where a, let c = d else {" ], triggeringExamples: [ "if let a = b, let c = d {", @@ -36,12 +37,10 @@ public struct ConditionalBindingCascadeRule: Rule { ) public func validateFile(file: File) -> [StyleViolation] { - let conditionalBindingKeywords = ["if", "guard"] - let pattern = "^(" + - conditionalBindingKeywords.joinWithSeparator("|") + - ")(\\s*?)let((.|\\s)*?),(\\s*?)let((.|\\s)*?)\\{" - return file.matchPattern(pattern, - excludingSyntaxKinds: SyntaxKind.commentAndStringKinds()).map { + return file.matchPattern("^(if|guard)(.*?)let(.*?),(.*?)let(.*?)\\{", + excludingSyntaxKinds: SyntaxKind.commentAndStringKinds()).filter { + !(file.contents as NSString).substringWithRange($0).containsString("where") + }.map { StyleViolation(ruleDescription: self.dynamicType.description, location: Location(file: file, characterOffset: $0.location)) } From 8e08cf78f2abb3f8697ce6c538cf3997142599a3 Mon Sep 17 00:00:00 2001 From: Aaron McTavish Date: Tue, 12 Jan 2016 08:17:28 +0000 Subject: [PATCH 3/6] Implements LegacyConstantRule From #319 CGGeometry: Prefer struct-scope constants CGRect.infinite, CGRect.null, etc. over global constants CGRectInfinite, CGRectNull, etc. --- CHANGELOG.md | 4 + .../Rules/LegacyConstantRule.swift | 80 +++++++++++++++++++ .../StringRuleTests.swift | 4 + SwiftLint.xcodeproj/project.pbxproj | 4 + 4 files changed, 92 insertions(+) create mode 100644 Source/SwiftLintFramework/Rules/LegacyConstantRule.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 1418529484..5bc0c505d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,10 @@ [JP Simard](https://github.com/jpsim) [#256](https://github.com/realm/SwiftLint/issues/256) +* Add LegacyConstantRule. + [Aaron McTavish](https://github.com/aamctustwo) + [#319](https://github.com/realm/SwiftLint/issues/319) + ##### Bug Fixes * AutoCorrect for TrailingNewlineRule only removes at most one line. diff --git a/Source/SwiftLintFramework/Rules/LegacyConstantRule.swift b/Source/SwiftLintFramework/Rules/LegacyConstantRule.swift new file mode 100644 index 0000000000..e8afbd0cbe --- /dev/null +++ b/Source/SwiftLintFramework/Rules/LegacyConstantRule.swift @@ -0,0 +1,80 @@ +// +// LegacyConstantRule.swift +// SwiftLint +// +// Created by Aaron McTavish on 12/01/2016. +// Copyright © 2016 Realm. All rights reserved. +// + +import SourceKittenFramework +import CoreGraphics + +public struct LegacyConstantRule: CorrectableRule { + public static let description = RuleDescription( + identifier: "legacy_constant", + name: "Legacy Constant", + description: "Struct-scoped constants are preferred over legacy global constants.", + nonTriggeringExamples: [ + "CGRect.infinite", + "CGPoint.zero", + "CGRect.zero", + "CGSize.zero", + "CGRect.null" + ], + triggeringExamples: [ + "↓CGRectInfinite", + "↓CGPointZero", + "↓CGRectZero", + "↓CGSizeZero", + "↓CGRectNull" + ], + corrections: [ + "CGRectInfinite\n": "CGRect.infinite\n", + "CGPointZero\n": "CGPoint.zero\n", + "CGRectZero\n": "CGRect.zero\n", + "CGSizeZero\n": "CGSize.zero\n", + "CGRectNull\n": "CGRect.null\n" + ] + ) + + public func validateFile(file: File) -> [StyleViolation] { + let constants = ["CGRectInfinite", "CGPointZero", "CGRectZero", "CGSizeZero", + "CGRectNull"] + + let pattern = "\\b(" + constants.joinWithSeparator("|") + ")\\b" + + return file.matchPattern(pattern, withSyntaxKinds: [.Identifier]).map { + StyleViolation(ruleDescription: self.dynamicType.description, + location: Location(file: file, characterOffset: $0.location)) + } + } + + public func correctFile(file: File) -> [Correction] { + let patterns = [ + "CGRectInfinite": "CGRect.infinite", + "CGPointZero": "CGPoint.zero", + "CGRectZero": "CGRect.zero", + "CGSizeZero": "CGSize.zero", + "CGRectNull": "CGRect.null" + ] + + let description = self.dynamicType.description + var corrections = [Correction]() + var contents = file.contents + + for (pattern, template) in patterns { + let matches = file.matchPattern(pattern, excludingSyntaxKinds: [.Comment]) + + let regularExpression = regex(pattern) + for range in matches.reverse() { + contents = regularExpression.stringByReplacingMatchesInString(contents, + options: [], range: range, withTemplate: template) + let location = Location(file: file, characterOffset: range.location) + corrections.append(Correction(ruleDescription: description, location: location)) + } + } + + file.write(contents) + return corrections + } +} diff --git a/Source/SwiftLintFrameworkTests/StringRuleTests.swift b/Source/SwiftLintFrameworkTests/StringRuleTests.swift index d71afc262f..2e9500efcf 100644 --- a/Source/SwiftLintFrameworkTests/StringRuleTests.swift +++ b/Source/SwiftLintFrameworkTests/StringRuleTests.swift @@ -113,4 +113,8 @@ class StringRuleTests: XCTestCase { func testConditionalBindingCascade() { verifyRule(ConditionalBindingCascadeRule.description) } + + func testLegacyConstant() { + verifyRule(LegacyConstantRule.description) + } } diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj index 2429be4f22..a20c0f72f5 100644 --- a/SwiftLint.xcodeproj/project.pbxproj +++ b/SwiftLint.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 006ECFC41C44E99E00EF6364 /* LegacyConstantRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 006ECFC31C44E99E00EF6364 /* LegacyConstantRule.swift */; }; 00970C751C3FC3A0007C4A83 /* ConditionalBindingCascadeRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00970C741C3FC3A0007C4A83 /* ConditionalBindingCascadeRule.swift */; }; 02FD8AEF1BFC18D60014BFFB /* ExtendedNSStringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02FD8AEE1BFC18D60014BFFB /* ExtendedNSStringTests.swift */; }; 1F11B3CF1C252F23002E8FA8 /* ClosingBraceRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F11B3CE1C252F23002E8FA8 /* ClosingBraceRule.swift */; }; @@ -150,6 +151,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 006ECFC31C44E99E00EF6364 /* LegacyConstantRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyConstantRule.swift; sourceTree = ""; }; 00970C741C3FC3A0007C4A83 /* ConditionalBindingCascadeRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConditionalBindingCascadeRule.swift; sourceTree = ""; }; 02FD8AEE1BFC18D60014BFFB /* ExtendedNSStringTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtendedNSStringTests.swift; sourceTree = ""; }; 1F11B3CE1C252F23002E8FA8 /* ClosingBraceRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClosingBraceRule.swift; sourceTree = ""; }; @@ -506,6 +508,7 @@ E816194D1BFBFEAB00946723 /* ForceTryRule.swift */, E88DEA8F1B099A3100A66CB0 /* FunctionBodyLengthRule.swift */, E88DEA7D1B098F2A00A66CB0 /* LeadingWhitespaceRule.swift */, + 006ECFC31C44E99E00EF6364 /* LegacyConstantRule.swift */, D44AD2741C0AA3730048F7B0 /* LegacyConstructorRule.swift */, E88DEA7B1B098D7D00A66CB0 /* LineLengthRule.swift */, E88DEA951B099CF200A66CB0 /* NestingRule.swift */, @@ -781,6 +784,7 @@ E88198441BEA93D200333A11 /* ColonRule.swift in Sources */, E809EDA11B8A71DF00399043 /* Configuration.swift in Sources */, E8EA41171C2D1DBE004F9930 /* CheckstyleReporter.swift in Sources */, + 006ECFC41C44E99E00EF6364 /* LegacyConstantRule.swift in Sources */, E88DEA731B0984C400A66CB0 /* String+SwiftLint.swift in Sources */, E88198591BEA95F100333A11 /* LeadingWhitespaceRule.swift in Sources */, 24E17F721B14BB3F008195BE /* File+Cache.swift in Sources */, From cf0255a6e4376fce7bba75cb605caa08e3ed2e06 Mon Sep 17 00:00:00 2001 From: Aaron McTavish Date: Wed, 13 Jan 2016 07:13:31 +0000 Subject: [PATCH 4/6] Resolved issues based on feedback from @jpsim --- Source/SwiftLintFramework/Rules/LegacyConstantRule.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/SwiftLintFramework/Rules/LegacyConstantRule.swift b/Source/SwiftLintFramework/Rules/LegacyConstantRule.swift index e8afbd0cbe..60af1eae65 100644 --- a/Source/SwiftLintFramework/Rules/LegacyConstantRule.swift +++ b/Source/SwiftLintFramework/Rules/LegacyConstantRule.swift @@ -7,7 +7,6 @@ // import SourceKittenFramework -import CoreGraphics public struct LegacyConstantRule: CorrectableRule { public static let description = RuleDescription( @@ -63,7 +62,7 @@ public struct LegacyConstantRule: CorrectableRule { var contents = file.contents for (pattern, template) in patterns { - let matches = file.matchPattern(pattern, excludingSyntaxKinds: [.Comment]) + let matches = file.matchPattern(pattern, withSyntaxKinds: [.Identifier]) let regularExpression = regex(pattern) for range in matches.reverse() { From e79eba5bf914879c0a250054e71589adce33517a Mon Sep 17 00:00:00 2001 From: Aaron McTavish Date: Wed, 13 Jan 2016 07:44:41 +0000 Subject: [PATCH 5/6] Rebased branch --- Source/SwiftLintFramework/Models/MasterRuleList.swift | 1 + Source/SwiftLintFramework/Rules/LegacyConstantRule.swift | 3 +++ 2 files changed, 4 insertions(+) diff --git a/Source/SwiftLintFramework/Models/MasterRuleList.swift b/Source/SwiftLintFramework/Models/MasterRuleList.swift index 326e9fb796..e3af368837 100644 --- a/Source/SwiftLintFramework/Models/MasterRuleList.swift +++ b/Source/SwiftLintFramework/Models/MasterRuleList.swift @@ -29,6 +29,7 @@ public let masterRuleList = RuleList( rules: ClosingBraceRule.self, ForceTryRule.self, FunctionBodyLengthRule.self, LeadingWhitespaceRule.self, + LegacyConstantRule.self, LegacyConstructorRule.self, LineLengthRule.self, NestingRule.self, diff --git a/Source/SwiftLintFramework/Rules/LegacyConstantRule.swift b/Source/SwiftLintFramework/Rules/LegacyConstantRule.swift index 60af1eae65..5824a5c9bc 100644 --- a/Source/SwiftLintFramework/Rules/LegacyConstantRule.swift +++ b/Source/SwiftLintFramework/Rules/LegacyConstantRule.swift @@ -9,6 +9,9 @@ import SourceKittenFramework public struct LegacyConstantRule: CorrectableRule { + + public init() {} + public static let description = RuleDescription( identifier: "legacy_constant", name: "Legacy Constant", From 63ae70e6922cce138ff9cb231e05e58c45691220 Mon Sep 17 00:00:00 2001 From: Aaron McTavish Date: Wed, 13 Jan 2016 07:49:27 +0000 Subject: [PATCH 6/6] Refix rebase --- .../Rules/LegacyConstantRule.swift | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 Source/SwiftLintFramework/Rules/LegacyConstantRule.swift diff --git a/Source/SwiftLintFramework/Rules/LegacyConstantRule.swift b/Source/SwiftLintFramework/Rules/LegacyConstantRule.swift new file mode 100644 index 0000000000..5824a5c9bc --- /dev/null +++ b/Source/SwiftLintFramework/Rules/LegacyConstantRule.swift @@ -0,0 +1,82 @@ +// +// LegacyConstantRule.swift +// SwiftLint +// +// Created by Aaron McTavish on 12/01/2016. +// Copyright © 2016 Realm. All rights reserved. +// + +import SourceKittenFramework + +public struct LegacyConstantRule: CorrectableRule { + + public init() {} + + public static let description = RuleDescription( + identifier: "legacy_constant", + name: "Legacy Constant", + description: "Struct-scoped constants are preferred over legacy global constants.", + nonTriggeringExamples: [ + "CGRect.infinite", + "CGPoint.zero", + "CGRect.zero", + "CGSize.zero", + "CGRect.null" + ], + triggeringExamples: [ + "↓CGRectInfinite", + "↓CGPointZero", + "↓CGRectZero", + "↓CGSizeZero", + "↓CGRectNull" + ], + corrections: [ + "CGRectInfinite\n": "CGRect.infinite\n", + "CGPointZero\n": "CGPoint.zero\n", + "CGRectZero\n": "CGRect.zero\n", + "CGSizeZero\n": "CGSize.zero\n", + "CGRectNull\n": "CGRect.null\n" + ] + ) + + public func validateFile(file: File) -> [StyleViolation] { + let constants = ["CGRectInfinite", "CGPointZero", "CGRectZero", "CGSizeZero", + "CGRectNull"] + + let pattern = "\\b(" + constants.joinWithSeparator("|") + ")\\b" + + return file.matchPattern(pattern, withSyntaxKinds: [.Identifier]).map { + StyleViolation(ruleDescription: self.dynamicType.description, + location: Location(file: file, characterOffset: $0.location)) + } + } + + public func correctFile(file: File) -> [Correction] { + let patterns = [ + "CGRectInfinite": "CGRect.infinite", + "CGPointZero": "CGPoint.zero", + "CGRectZero": "CGRect.zero", + "CGSizeZero": "CGSize.zero", + "CGRectNull": "CGRect.null" + ] + + let description = self.dynamicType.description + var corrections = [Correction]() + var contents = file.contents + + for (pattern, template) in patterns { + let matches = file.matchPattern(pattern, withSyntaxKinds: [.Identifier]) + + let regularExpression = regex(pattern) + for range in matches.reverse() { + contents = regularExpression.stringByReplacingMatchesInString(contents, + options: [], range: range, withTemplate: template) + let location = Location(file: file, characterOffset: range.location) + corrections.append(Correction(ruleDescription: description, location: location)) + } + } + + file.write(contents) + return corrections + } +}