Skip to content

Commit

Permalink
Adds closure_body_length opt-in rule
Browse files Browse the repository at this point in the history
because closure bodies should not span too many lines. Implements realm#52.
  • Loading branch information
ornithocoder committed Aug 30, 2017
1 parent 4539f2b commit 0514957
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
`redundant_void_return` rule.
[Ryan Booker](https://github.com/ryanbooker)
[#1761](https://github.com/realm/SwiftLint/issues/1761)
* Add `closure_body_length` rule to enforce the maximum number of lines
a closure should have.
[Ornithologist Coder](https://github.com/ornithocoder)
[#52](https://github.com/realm/SwiftLint/issues/52)

* Add `single_test_class` opt-in rule to validate that test files
only contain a single `QuickSpec` or `XCTestCase` subclass.
Expand Down
132 changes: 132 additions & 0 deletions Rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* [Block Based KVO](#block-based-kvo)
* [Class Delegate Protocol](#class-delegate-protocol)
* [Closing Brace Spacing](#closing-brace-spacing)
* [Closure Body Length](#closure-body-length)
* [Closure End Indentation](#closure-end-indentation)
* [Closure Parameter Position](#closure-parameter-position)
* [Closure Spacing](#closure-spacing)
Expand Down Expand Up @@ -610,6 +611,137 @@ Closing brace with closing parenthesis should not have any whitespaces in the mi



## Closure Body Length

Identifier | Enabled by default | Supports autocorrection | Kind
--- | --- | --- | ---
`closure_body_length` | Disabled | No | metrics

Closure bodies should not span too many lines.

### Examples

<details>
<summary>Non Triggering Examples</summary>

```swift
foo.bar { $0 }
```

```swift
foo.bar { value in
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
return value
}
```

```swift
foo.bar { value in

print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")

return value
}
```

</details>
<details>
<summary>Triggering Examples</summary>

```swift
foo.bar { value in
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
return value
}
```

```swift
foo.bar { value in

print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")
print("toto")

return value
}
```

</details>



## Closure End Indentation

Identifier | Enabled by default | Supports autocorrection | Kind
Expand Down
1 change: 1 addition & 0 deletions Source/SwiftLintFramework/Models/MasterRuleList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public let masterRuleList = RuleList(rules: [
BlockBasedKVORule.self,
ClassDelegateProtocolRule.self,
ClosingBraceRule.self,
ClosureBodyLengthRule.self,
ClosureEndIndentationRule.self,
ClosureParameterPositionRule.self,
ClosureSpacingRule.self,
Expand Down
61 changes: 61 additions & 0 deletions Source/SwiftLintFramework/Rules/ClosureBodyLengthRule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// ClosureBodyLengthRule.swift
// SwiftLint
//
// Created by Ornithologist Coder on 8/3/17.
// Copyright © 2017 Realm. All rights reserved.
//

import Foundation
import SourceKittenFramework

public struct ClosureBodyLengthRule: ASTRule, OptInRule, ConfigurationProviderRule {
public var configuration = SeverityLevelsConfiguration(warning: 20, error: 100)

public init() {}

public static let description = RuleDescription(
identifier: "closure_body_length",
name: "Closure Body Length",
description: "Closure bodies should not span too many lines.",
kind: .metrics,
nonTriggeringExamples: [
"foo.bar { $0 }",
"foo.bar { value in\n" + repeatElement("\tprint(\"toto\")\n", count: 19).joined() + "\treturn value\n}",
"foo.bar { value in\n\n" + repeatElement("\tprint(\"toto\")\n", count: 19).joined() + "\n\treturn value\n}"
],
triggeringExamples: [
"foo.bar {↓ value in\n" + repeatElement("\tprint(\"toto\")\n", count: 20).joined() + "\treturn value\n}",
"foo.bar {↓ value in\n\n" + repeatElement("\tprint(\"toto\")\n", count: 20).joined() + "\n\treturn value\n}"
]
)

public func validate(file: File,
kind: SwiftExpressionKind,
dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
guard
kind == .call,
let bodyOffset = dictionary.bodyOffset,
let bodyLength = dictionary.bodyLength,
case let contents = file.contents.bridge(),
contents.substringWithByteRange(start: bodyOffset - 1, length: 1) == "{",
let startLine = contents.lineAndCharacter(forByteOffset: bodyOffset)?.line,
let endLine = contents.lineAndCharacter(forByteOffset: bodyOffset + bodyLength)?.line
else {
return []
}

return configuration.params.flatMap { parameter in
let (exceeds, lineCount) = file.exceedsLineCountExcludingCommentsAndWhitespace(startLine,
endLine,
parameter.value)
guard exceeds else { return nil }

return StyleViolation(ruleDescription: type(of: self).description,
severity: parameter.severity,
location: Location(file: file, byteOffset: bodyOffset),
reason: "Closure body should span \(configuration.warning) lines or less " +
"excluding comments and whitespace: currently spans \(lineCount) " + "lines")
}
}
}
4 changes: 4 additions & 0 deletions SwiftLint.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
629C60D91F43906700B4AF92 /* SingleTestClassRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629C60D81F43906700B4AF92 /* SingleTestClassRule.swift */; };
62A498561F306A7700D766E4 /* DiscouragedDirectInitConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A498551F306A7700D766E4 /* DiscouragedDirectInitConfiguration.swift */; };
62A6E7931F3317E3003A0479 /* JoinedDefaultRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A6E7911F3317E3003A0479 /* JoinedDefaultRule.swift */; };
62A6E7951F3344CF003A0479 /* ClosureBodyLengthRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A6E7941F3344CF003A0479 /* ClosureBodyLengthRule.swift */; };
67932E2D1E54AF4B00CB0629 /* CyclomaticComplexityConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67932E2C1E54AF4B00CB0629 /* CyclomaticComplexityConfigurationTests.swift */; };
67EB4DFA1E4CC111004E9ACD /* CyclomaticComplexityConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB4DF81E4CC101004E9ACD /* CyclomaticComplexityConfiguration.swift */; };
67EB4DFC1E4CD7F5004E9ACD /* CyclomaticComplexityRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB4DFB1E4CD7F5004E9ACD /* CyclomaticComplexityRuleTests.swift */; };
Expand Down Expand Up @@ -391,6 +392,7 @@
629C60D81F43906700B4AF92 /* SingleTestClassRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleTestClassRule.swift; sourceTree = "<group>"; };
62A498551F306A7700D766E4 /* DiscouragedDirectInitConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscouragedDirectInitConfiguration.swift; sourceTree = "<group>"; };
62A6E7911F3317E3003A0479 /* JoinedDefaultRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinedDefaultRule.swift; sourceTree = "<group>"; };
62A6E7941F3344CF003A0479 /* ClosureBodyLengthRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClosureBodyLengthRule.swift; sourceTree = "<group>"; };
62AF35D71F30B183009B11EE /* DiscouragedDirectInitRuleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscouragedDirectInitRuleTests.swift; sourceTree = "<group>"; };
65454F451B14D73800319A6C /* ControlStatementRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControlStatementRule.swift; sourceTree = "<group>"; };
67932E2C1E54AF4B00CB0629 /* CyclomaticComplexityConfigurationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CyclomaticComplexityConfigurationTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -947,6 +949,7 @@
D4FD4C841F2A260A00DD8AA8 /* BlockBasedKVORule.swift */,
D4B0228D1E0CC608007E5297 /* ClassDelegateProtocolRule.swift */,
1F11B3CE1C252F23002E8FA8 /* ClosingBraceRule.swift */,
62A6E7941F3344CF003A0479 /* ClosureBodyLengthRule.swift */,
D43B046A1E075905004016AF /* ClosureEndIndentationRule.swift */,
D47079A81DFDBED000027086 /* ClosureParameterPositionRule.swift */,
1E82D5581D7775C7009553D7 /* ClosureSpacingRule.swift */,
Expand Down Expand Up @@ -1441,6 +1444,7 @@
D4130D991E16CC1300242361 /* TypeNameRuleExamples.swift in Sources */,
24E17F721B14BB3F008195BE /* File+Cache.swift in Sources */,
47ACC8981E7DC74E0088EEB2 /* ImplicitlyUnwrappedOptionalConfiguration.swift in Sources */,
62A6E7951F3344CF003A0479 /* ClosureBodyLengthRule.swift in Sources */,
009E09281DFEE4C200B588A7 /* ProhibitedSuperRule.swift in Sources */,
E80E018F1B92C1350078EB70 /* Region.swift in Sources */,
E88198581BEA956C00333A11 /* FunctionBodyLengthRule.swift in Sources */,
Expand Down
1 change: 1 addition & 0 deletions Tests/LinuxMain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ extension RulesTests {
("testBlockBasedKVO", testBlockBasedKVO),
("testClassDelegateProtocol", testClassDelegateProtocol),
("testClosingBrace", testClosingBrace),
("testClosureBodyLength", testClosureBodyLength),
("testClosureEndIndentation", testClosureEndIndentation),
("testClosureParameterPosition", testClosureParameterPosition),
("testClosureSpacing", testClosureSpacing),
Expand Down
4 changes: 4 additions & 0 deletions Tests/SwiftLintFrameworkTests/RulesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ class RulesTests: XCTestCase {
verifyRule(ClosingBraceRule.description)
}

func testClosureBodyLength() {
verifyRule(ClosureBodyLengthRule.description)
}

func testClosureEndIndentation() {
verifyRule(ClosureEndIndentationRule.description)
}
Expand Down

0 comments on commit 0514957

Please sign in to comment.