From 8102c59a531e0110b75c6a4d39bee5d70fcf8a37 Mon Sep 17 00:00:00 2001 From: Marcelo Fabri Date: Sun, 18 Dec 2016 23:10:56 -0200 Subject: [PATCH 1/7] Initial implementation of closure_end_indentation --- .../Models/MasterRuleList.swift | 1 + .../Rules/ClosureEndIndentationRule.swift | 71 +++++++++++++++++++ SwiftLint.xcodeproj/project.pbxproj | 4 ++ .../SwiftLintFrameworkTests/RulesTests.swift | 5 ++ 4 files changed, 81 insertions(+) create mode 100644 Source/SwiftLintFramework/Rules/ClosureEndIndentationRule.swift diff --git a/Source/SwiftLintFramework/Models/MasterRuleList.swift b/Source/SwiftLintFramework/Models/MasterRuleList.swift index 19393fdb5b..cd654055a1 100644 --- a/Source/SwiftLintFramework/Models/MasterRuleList.swift +++ b/Source/SwiftLintFramework/Models/MasterRuleList.swift @@ -43,6 +43,7 @@ public struct RuleList { public let masterRuleList = RuleList(rules: AttributesRule.self, ClosingBraceRule.self, + ClosureEndIndentationRule.self, ClosureParameterPositionRule.self, ClosureSpacingRule.self, ColonRule.self, diff --git a/Source/SwiftLintFramework/Rules/ClosureEndIndentationRule.swift b/Source/SwiftLintFramework/Rules/ClosureEndIndentationRule.swift new file mode 100644 index 0000000000..a5fc632937 --- /dev/null +++ b/Source/SwiftLintFramework/Rules/ClosureEndIndentationRule.swift @@ -0,0 +1,71 @@ +// +// ClosureEndIndentationRule.swift +// SwiftLint +// +// Created by Marcelo Fabri on 12/18/16. +// Copyright © 2016 Realm. All rights reserved. +// + +import Foundation +import SourceKittenFramework + +public struct ClosureEndIndentationRule: ASTRule, ConfigurationProviderRule { + public var configuration = SeverityConfiguration(.warning) + + public init() {} + + public static let description = RuleDescription( + identifier: "closure_end_indentation", + name: "Closure End Indentation", + description: "Closure end should have the same indentation as the line that started it.", + nonTriggeringExamples: [ + "SignalProducer(values: [1, 2, 3])\n" + + " .startWithNext { number in\n" + + " print(number)\n" + + " }" + ], + triggeringExamples: [ + "SignalProducer(values: [1, 2, 3])\n" + + " .startWithNext { number in\n" + + " print(number)\n" + + "}" + ] + ) + + private static let notWhitespace = regex("[^\\s]") + + public func validateFile(_ file: File, + kind: SwiftExpressionKind, + dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] { + guard kind == .call else { + return [] + } + + let contents = file.contents + guard let offset = (dictionary["key.offset"] as? Int64).flatMap({ Int($0) }), + let length = (dictionary["key.length"] as? Int64).flatMap({ Int($0) }), + let bodyOffset = (dictionary["key.bodyoffset"] as? Int64).flatMap({ Int($0) }), + let bodyLength = (dictionary["key.bodylength"] as? Int64).flatMap({ Int($0) }), + bodyLength > 0, + case let endOffset = offset + length - 1, + contents.bridge().substringWithByteRange(start: endOffset, length: 1) == "}", + let (startLine, _) = contents.lineAndCharacter(forByteOffset: bodyOffset), + let (endLine, endPosition) = contents.lineAndCharacter(forByteOffset: endOffset), + startLine != endLine else { + return [] + } + + let range = file.lines[startLine - 1].range + let regex = ClosureEndIndentationRule.notWhitespace + guard let match = regex.firstMatch(in: contents, options: [], range: range)?.range, + match.location - range.location != endPosition - 1 else { + return [] + } + + return [ + StyleViolation(ruleDescription: type(of: self).description, + severity: configuration.severity, + location: Location(file: file, byteOffset: endOffset)) + ] + } +} diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj index 60240290a3..7cc4ad1005 100644 --- a/SwiftLint.xcodeproj/project.pbxproj +++ b/SwiftLint.xcodeproj/project.pbxproj @@ -88,6 +88,7 @@ D41E7E0B1DF9DABB0065259A /* RedundantStringEnumValueRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D41E7E0A1DF9DABB0065259A /* RedundantStringEnumValueRule.swift */; }; D4348EEA1C46122C007707FB /* FunctionBodyLengthRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4348EE91C46122C007707FB /* FunctionBodyLengthRuleTests.swift */; }; D43B04641E0620AB004016AF /* UnusedEnumeratedRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43B04631E0620AB004016AF /* UnusedEnumeratedRule.swift */; }; + D43B046B1E075905004016AF /* ClosureEndIndentationRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43B046A1E075905004016AF /* ClosureEndIndentationRule.swift */; }; D43DB1081DC573DA00281215 /* ImplicitGetterRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43DB1071DC573DA00281215 /* ImplicitGetterRule.swift */; }; D44254201DB87CA200492EA4 /* ValidIBInspectableRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D442541E1DB87C3D00492EA4 /* ValidIBInspectableRule.swift */; }; D44254271DB9C15C00492EA4 /* SyntacticSugarRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44254251DB9C12300492EA4 /* SyntacticSugarRule.swift */; }; @@ -322,6 +323,7 @@ D41E7E0A1DF9DABB0065259A /* RedundantStringEnumValueRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantStringEnumValueRule.swift; sourceTree = ""; }; D4348EE91C46122C007707FB /* FunctionBodyLengthRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionBodyLengthRuleTests.swift; sourceTree = ""; }; D43B04631E0620AB004016AF /* UnusedEnumeratedRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnusedEnumeratedRule.swift; sourceTree = ""; }; + D43B046A1E075905004016AF /* ClosureEndIndentationRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClosureEndIndentationRule.swift; sourceTree = ""; }; D43DB1071DC573DA00281215 /* ImplicitGetterRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImplicitGetterRule.swift; sourceTree = ""; }; D442541E1DB87C3D00492EA4 /* ValidIBInspectableRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValidIBInspectableRule.swift; sourceTree = ""; }; D44254251DB9C12300492EA4 /* SyntacticSugarRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyntacticSugarRule.swift; sourceTree = ""; }; @@ -705,6 +707,7 @@ D47A510F1DB2DD4800A4CC21 /* AttributesRule.swift */, D48AE2CB1DFB58C5001C6A4A /* AttributesRulesExamples.swift */, 1F11B3CE1C252F23002E8FA8 /* ClosingBraceRule.swift */, + D43B046A1E075905004016AF /* ClosureEndIndentationRule.swift */, D47079A81DFDBED000027086 /* ClosureParameterPositionRule.swift */, 1E82D5581D7775C7009553D7 /* ClosureSpacingRule.swift */, E88DEA831B0990F500A66CB0 /* ColonRule.swift */, @@ -1109,6 +1112,7 @@ D47A510E1DB29EEB00A4CC21 /* SwitchCaseOnNewlineRule.swift in Sources */, D48AE2CC1DFB58C5001C6A4A /* AttributesRulesExamples.swift in Sources */, E88DEA6F1B09843F00A66CB0 /* Location.swift in Sources */, + D43B046B1E075905004016AF /* ClosureEndIndentationRule.swift in Sources */, 93E0C3CE1D67BD7F007FA25D /* ConditionalReturnsOnNewline.swift in Sources */, D43DB1081DC573DA00281215 /* ImplicitGetterRule.swift in Sources */, 7C0C2E7A1D2866CB0076435A /* ExplicitInitRule.swift in Sources */, diff --git a/Tests/SwiftLintFrameworkTests/RulesTests.swift b/Tests/SwiftLintFrameworkTests/RulesTests.swift index 317ecbeb6c..d67f84ec59 100644 --- a/Tests/SwiftLintFrameworkTests/RulesTests.swift +++ b/Tests/SwiftLintFrameworkTests/RulesTests.swift @@ -94,6 +94,10 @@ class RulesTests: XCTestCase { verifyRule(CommaRule.description) } + func testClosureEndIndentation() { + verifyRule(ClosureEndIndentationRule.description) + } + func testClosureParameterPosition() { verifyRule(ClosureParameterPositionRule.description) } @@ -414,6 +418,7 @@ extension RulesTests { ("testClosingBrace", testClosingBrace), ("testColon", testColon), ("testComma", testComma), + ("testClosureEndIndentation", testClosureEndIndentation), ("testClosureParameterPosition", testClosureParameterPosition), ("testClosureSpacingRule", testClosureSpacingRule), ("testConditionalReturnsOnNewline", testConditionalReturnsOnNewline), From 3f6ca7611aa476d649de16fbbaceec4cc6588cf8 Mon Sep 17 00:00:00 2001 From: Marcelo Fabri Date: Sun, 18 Dec 2016 23:11:30 -0200 Subject: [PATCH 2/7] Fix violations --- Source/SwiftLintFramework/Extensions/File+SwiftLint.swift | 2 +- .../SwiftLintFramework/Extensions/NSFileManager+SwiftLint.swift | 2 +- Source/SwiftLintFramework/Rules/CommaRule.swift | 2 +- Source/SwiftLintFramework/Rules/ForceUnwrappingRule.swift | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/SwiftLintFramework/Extensions/File+SwiftLint.swift b/Source/SwiftLintFramework/Extensions/File+SwiftLint.swift index 481f6c64f9..05420a4188 100644 --- a/Source/SwiftLintFramework/Extensions/File+SwiftLint.swift +++ b/Source/SwiftLintFramework/Extensions/File+SwiftLint.swift @@ -45,7 +45,7 @@ extension File { return Command(string: contents, range: range) }.flatMap { command in return command.expand() - } + } } fileprivate func endOfNextCommand(_ nextCommand: Command?) -> Location { diff --git a/Source/SwiftLintFramework/Extensions/NSFileManager+SwiftLint.swift b/Source/SwiftLintFramework/Extensions/NSFileManager+SwiftLint.swift index d60468df3f..b1afc2f9ab 100644 --- a/Source/SwiftLintFramework/Extensions/NSFileManager+SwiftLint.swift +++ b/Source/SwiftLintFramework/Extensions/NSFileManager+SwiftLint.swift @@ -28,7 +28,7 @@ extension FileManager { return try subpathsOfDirectory(atPath: absolutePath) .map(absolutePath.bridge().appendingPathComponent).filter { $0.bridge().isSwiftFile() - } + } } catch { fatalError("Couldn't find files in \(absolutePath): \(error)") } diff --git a/Source/SwiftLintFramework/Rules/CommaRule.swift b/Source/SwiftLintFramework/Rules/CommaRule.swift index b745ad2901..b18bf0dc22 100644 --- a/Source/SwiftLintFramework/Rules/CommaRule.swift +++ b/Source/SwiftLintFramework/Rules/CommaRule.swift @@ -148,6 +148,6 @@ public struct CommaRule: CorrectableRule, ConfigurationProviderRule { // return first captured range return firstRange - } + } } } diff --git a/Source/SwiftLintFramework/Rules/ForceUnwrappingRule.swift b/Source/SwiftLintFramework/Rules/ForceUnwrappingRule.swift index 36c4aa4115..5e85988787 100644 --- a/Source/SwiftLintFramework/Rules/ForceUnwrappingRule.swift +++ b/Source/SwiftLintFramework/Rules/ForceUnwrappingRule.swift @@ -134,7 +134,7 @@ public struct ForceUnwrappingRule: OptInRule, ConfigurationProviderRule { } else { return nil } - } + } } // Returns if range should generate violation From 4622af179294e795a5af3bccdd5745b4eedd5c53 Mon Sep 17 00:00:00 2001 From: Marcelo Fabri Date: Mon, 19 Dec 2016 21:20:36 -0200 Subject: [PATCH 3/7] Handling false triggers in closure_end_indentation --- .swiftlint.yml | 1 + .../Rules/ClosureEndIndentationRule.swift | 56 ++++++++++++++++--- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/.swiftlint.yml b/.swiftlint.yml index d041bcd46b..d6112f1ddc 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -14,6 +14,7 @@ opt_in_rules: - nimble_operator - attributes - operator_usage_whitespace + - closure_end_indentation file_header: required_pattern: | diff --git a/Source/SwiftLintFramework/Rules/ClosureEndIndentationRule.swift b/Source/SwiftLintFramework/Rules/ClosureEndIndentationRule.swift index a5fc632937..e06d742bbe 100644 --- a/Source/SwiftLintFramework/Rules/ClosureEndIndentationRule.swift +++ b/Source/SwiftLintFramework/Rules/ClosureEndIndentationRule.swift @@ -9,7 +9,7 @@ import Foundation import SourceKittenFramework -public struct ClosureEndIndentationRule: ASTRule, ConfigurationProviderRule { +public struct ClosureEndIndentationRule: ASTRule, OptInRule, ConfigurationProviderRule { public var configuration = SeverityConfiguration(.warning) public init() {} @@ -22,13 +22,24 @@ public struct ClosureEndIndentationRule: ASTRule, ConfigurationProviderRule { "SignalProducer(values: [1, 2, 3])\n" + " .startWithNext { number in\n" + " print(number)\n" + - " }" + " }\n", + "[1, 2].map { $0 + 1 }\n", + "return matchPattern(pattern, withSyntaxKinds: [.comment]).flatMap { range in\n" + + " return Command(string: contents, range: range)\n" + + "}.flatMap { command in\n" + + " return command.expand()\n" + + "}\n" ], triggeringExamples: [ "SignalProducer(values: [1, 2, 3])\n" + " .startWithNext { number in\n" + " print(number)\n" + - "}" + "↓}\n", + "return matchPattern(pattern, withSyntaxKinds: [.comment]).flatMap { range in\n" + + " return Command(string: contents, range: range)\n" + + " ↓}.flatMap { command in\n" + + " return command.expand()\n" + + "↓}\n" ] ) @@ -44,28 +55,57 @@ public struct ClosureEndIndentationRule: ASTRule, ConfigurationProviderRule { let contents = file.contents guard let offset = (dictionary["key.offset"] as? Int64).flatMap({ Int($0) }), let length = (dictionary["key.length"] as? Int64).flatMap({ Int($0) }), - let bodyOffset = (dictionary["key.bodyoffset"] as? Int64).flatMap({ Int($0) }), let bodyLength = (dictionary["key.bodylength"] as? Int64).flatMap({ Int($0) }), + let nameOffset = (dictionary["key.nameoffset"] as? Int64).flatMap({ Int($0) }), + let nameLength = (dictionary["key.namelength"] as? Int64).flatMap({ Int($0) }), bodyLength > 0, case let endOffset = offset + length - 1, contents.bridge().substringWithByteRange(start: endOffset, length: 1) == "}", - let (startLine, _) = contents.lineAndCharacter(forByteOffset: bodyOffset), + let startOffset = startOffsetFor(dictionary: dictionary, file: file), + let (startLine, _) = contents.lineAndCharacter(forByteOffset: startOffset), let (endLine, endPosition) = contents.lineAndCharacter(forByteOffset: endOffset), - startLine != endLine else { + case let nameEndPosition = nameOffset + nameLength, + let (bodyOffsetLine, _) = contents.lineAndCharacter(forByteOffset: nameEndPosition), + startLine != endLine, bodyOffsetLine != endLine else { return [] } let range = file.lines[startLine - 1].range let regex = ClosureEndIndentationRule.notWhitespace + let actual = endPosition - 1 guard let match = regex.firstMatch(in: contents, options: [], range: range)?.range, - match.location - range.location != endPosition - 1 else { + case let expected = match.location - range.location, + expected != actual else { return [] } + let reason = "Closure end should have the same indentation as the line that started it. " + + "Expected \(expected), got \(actual)." return [ StyleViolation(ruleDescription: type(of: self).description, severity: configuration.severity, - location: Location(file: file, byteOffset: endOffset)) + location: Location(file: file, byteOffset: endOffset), + reason: reason) ] } + + private func startOffsetFor(dictionary: [String: SourceKitRepresentable], + file: File) -> Int? { + guard let nameOffset = (dictionary["key.nameoffset"] as? Int64).flatMap({ Int($0) }), + let nameLength = (dictionary["key.namelength"] as? Int64).flatMap({ Int($0) }) else { + return nil + } + + let newLineRegex = regex("\n(\\s*\\}?\\.)") + let contents = file.contents.bridge() + guard let range = contents.byteRangeToNSRange(start: nameOffset, length: nameLength), + let match = newLineRegex.matches(in: file.contents, options: [], + range: range).last?.rangeAt(1), + let methodByteRange = contents.NSRangeToByteRange(start: match.location, + length: match.length) else { + return nameOffset + } + + return methodByteRange.location + } } From 23a0b03fe878581c63b4e084a482c8918ce5796a Mon Sep 17 00:00:00 2001 From: Marcelo Fabri Date: Mon, 19 Dec 2016 21:20:58 -0200 Subject: [PATCH 4/7] Fixing violations --- .../Extensions/File+SwiftLint.swift | 12 ++++++------ Source/SwiftLintFramework/Rules/NestingRule.swift | 2 +- .../Rules/SyntacticSugarRule.swift | 2 +- Source/SwiftLintFramework/Rules/VoidReturnRule.swift | 3 ++- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Source/SwiftLintFramework/Extensions/File+SwiftLint.swift b/Source/SwiftLintFramework/Extensions/File+SwiftLint.swift index 05420a4188..3f1f416b72 100644 --- a/Source/SwiftLintFramework/Extensions/File+SwiftLint.swift +++ b/Source/SwiftLintFramework/Extensions/File+SwiftLint.swift @@ -40,12 +40,12 @@ extension File { return [] } let contents = self.contents.bridge() - return matchPattern("swiftlint:(enable|disable)(:previous|:this|:next)?\\ [^\\n]+", - withSyntaxKinds: [.comment]).flatMap { range in - return Command(string: contents, range: range) - }.flatMap { command in - return command.expand() - } + let pattern = "swiftlint:(enable|disable)(:previous|:this|:next)?\\ [^\\n]+" + return matchPattern(pattern, withSyntaxKinds: [.comment]).flatMap { range in + return Command(string: contents, range: range) + }.flatMap { command in + return command.expand() + } } fileprivate func endOfNextCommand(_ nextCommand: Command?) -> Location { diff --git a/Source/SwiftLintFramework/Rules/NestingRule.swift b/Source/SwiftLintFramework/Rules/NestingRule.swift index cda624a02d..d1fe5780ec 100644 --- a/Source/SwiftLintFramework/Rules/NestingRule.swift +++ b/Source/SwiftLintFramework/Rules/NestingRule.swift @@ -26,7 +26,7 @@ public struct NestingRule: ASTRule, ConfigurationProviderRule { } + ["enum Enum0 { enum Enum1 { case Case } }"], triggeringExamples: ["class", "struct", "enum"].map { kind in "\(kind) A { \(kind) B { ↓\(kind) C {} } }\n" - } + [ + } + [ "func func0() {\nfunc func1() {\nfunc func2() {\nfunc func3() {\nfunc func4() { " + "func func5() {\n↓func func6() {\n}\n}\n}\n}\n}\n}\n}\n" ] diff --git a/Source/SwiftLintFramework/Rules/SyntacticSugarRule.swift b/Source/SwiftLintFramework/Rules/SyntacticSugarRule.swift index 7f357a9b13..8686753eae 100644 --- a/Source/SwiftLintFramework/Rules/SyntacticSugarRule.swift +++ b/Source/SwiftLintFramework/Rules/SyntacticSugarRule.swift @@ -45,7 +45,7 @@ public struct SyntacticSugarRule: Rule, ConfigurationProviderRule { let pattern = "\\b(" + types.joined(separator: "|") + ")\\s*<.*?>" return file.matchPattern(pattern, - excludingSyntaxKinds: SyntaxKind.commentAndStringKinds()).map { + excludingSyntaxKinds: SyntaxKind.commentAndStringKinds()).map { StyleViolation(ruleDescription: type(of: self).description, severity: configuration.severity, location: Location(file: file, characterOffset: $0.location)) diff --git a/Source/SwiftLintFramework/Rules/VoidReturnRule.swift b/Source/SwiftLintFramework/Rules/VoidReturnRule.swift index cebe3a70f6..065fa1d9b7 100644 --- a/Source/SwiftLintFramework/Rules/VoidReturnRule.swift +++ b/Source/SwiftLintFramework/Rules/VoidReturnRule.swift @@ -56,7 +56,8 @@ public struct VoidReturnRule: ConfigurationProviderRule, CorrectableRule { let excludingPattern = "(\(pattern))\\s*(throws\\s+)?->" return file.matchPattern(pattern, excludingSyntaxKinds: kinds, - excludingPattern: excludingPattern) { $0.rangeAt(1) }.flatMap { + excludingPattern: excludingPattern, + exclusionMapping: { $0.rangeAt(1) }).flatMap { let parensRegex = NSRegularExpression.forcePattern(parensPattern) return parensRegex.firstMatch(in: file.contents, options: [], range: $0)?.range } From b8a62f09dc4e4740eef129841568b9d68d8f51bb Mon Sep 17 00:00:00 2001 From: Marcelo Fabri Date: Mon, 19 Dec 2016 21:24:41 -0200 Subject: [PATCH 5/7] Add changelog entry --- CHANGELOG.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0fa858032..742065142d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,16 @@ ##### Enhancements -* None. +* Add `closure_end_indentation` opt-in rule that validates closure closing + braces according to this logic: + * If the method call is chained breaking lines on each method + (`.` is on a new line), the closing brace should be in the same + indentation as the `.`. + * Else, the closing brace should be in the same indentation as + the beginning of the statement (in the first line). + + [Marcelo Fabri](https://github.com/marcelofabri) + [#326](https://github.com/realm/SwiftLint/issues/326) ##### Bug Fixes From ed5fdd8b628e302ad6d2c3fdbae5850e630500bb Mon Sep 17 00:00:00 2001 From: Marcelo Fabri Date: Mon, 19 Dec 2016 22:39:11 -0200 Subject: [PATCH 6/7] Using bridge() --- .../Rules/ClosureEndIndentationRule.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/SwiftLintFramework/Rules/ClosureEndIndentationRule.swift b/Source/SwiftLintFramework/Rules/ClosureEndIndentationRule.swift index e06d742bbe..8bc5d2b6e9 100644 --- a/Source/SwiftLintFramework/Rules/ClosureEndIndentationRule.swift +++ b/Source/SwiftLintFramework/Rules/ClosureEndIndentationRule.swift @@ -52,7 +52,7 @@ public struct ClosureEndIndentationRule: ASTRule, OptInRule, ConfigurationProvid return [] } - let contents = file.contents + let contents = file.contents.bridge() guard let offset = (dictionary["key.offset"] as? Int64).flatMap({ Int($0) }), let length = (dictionary["key.length"] as? Int64).flatMap({ Int($0) }), let bodyLength = (dictionary["key.bodylength"] as? Int64).flatMap({ Int($0) }), @@ -60,7 +60,7 @@ public struct ClosureEndIndentationRule: ASTRule, OptInRule, ConfigurationProvid let nameLength = (dictionary["key.namelength"] as? Int64).flatMap({ Int($0) }), bodyLength > 0, case let endOffset = offset + length - 1, - contents.bridge().substringWithByteRange(start: endOffset, length: 1) == "}", + contents.substringWithByteRange(start: endOffset, length: 1) == "}", let startOffset = startOffsetFor(dictionary: dictionary, file: file), let (startLine, _) = contents.lineAndCharacter(forByteOffset: startOffset), let (endLine, endPosition) = contents.lineAndCharacter(forByteOffset: endOffset), @@ -73,7 +73,7 @@ public struct ClosureEndIndentationRule: ASTRule, OptInRule, ConfigurationProvid let range = file.lines[startLine - 1].range let regex = ClosureEndIndentationRule.notWhitespace let actual = endPosition - 1 - guard let match = regex.firstMatch(in: contents, options: [], range: range)?.range, + guard let match = regex.firstMatch(in: file.contents, options: [], range: range)?.range, case let expected = match.location - range.location, expected != actual else { return [] From 0e42639e4043d625a0e16d384b972adaa4726f44 Mon Sep 17 00:00:00 2001 From: JP Simard Date: Mon, 19 Dec 2016 18:46:06 -0800 Subject: [PATCH 7/7] minor tweaks to changelog entry --- CHANGELOG.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 742065142d..774db41ba3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,13 +6,13 @@ ##### Enhancements -* Add `closure_end_indentation` opt-in rule that validates closure closing - braces according to this logic: - * If the method call is chained breaking lines on each method - (`.` is on a new line), the closing brace should be in the same - indentation as the `.`. - * Else, the closing brace should be in the same indentation as - the beginning of the statement (in the first line). +* Add `closure_end_indentation` opt-in rule that validates closure closing + braces according to these rules: + * If the method call has chained breaking lines on each method + (`.` is on a new line), the closing brace should be vertically aligned + with the `.`. + * Otherwise, the closing brace should be vertically aligned with + the beginning of the statement in the first line. [Marcelo Fabri](https://github.com/marcelofabri) [#326](https://github.com/realm/SwiftLint/issues/326)