forked from realm/SwiftLint
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathClosureParameterPositionRule.swift
96 lines (84 loc) · 4.08 KB
/
ClosureParameterPositionRule.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
//
// ClosureParameterPositionRule.swift
// SwiftLint
//
// Created by Marcelo Fabri on 12/11/16.
// Copyright © 2016 Realm. All rights reserved.
//
import Foundation
import SourceKittenFramework
public struct ClosureParameterPositionRule: ASTRule, ConfigurationProviderRule {
public var configuration = SeverityConfiguration(.warning)
public init() {}
public static let description = RuleDescription(
identifier: "closure_parameter_position",
name: "Closure Parameter Position",
description: "Closure parameters should be on the same line as opening brace.",
kind: .style,
nonTriggeringExamples: [
"[1, 2].map { $0 + 1 }\n",
"[1, 2].map({ $0 + 1 })\n",
"[1, 2].map { number in\n number + 1 \n}\n",
"[1, 2].map { number -> Int in\n number + 1 \n}\n",
"[1, 2].map { (number: Int) -> Int in\n number + 1 \n}\n",
"[1, 2].map { [weak self] number in\n number + 1 \n}\n",
"[1, 2].something(closure: { number in\n number + 1 \n})\n",
"let isEmpty = [1, 2].isEmpty()\n",
"rlmConfiguration.migrationBlock.map { rlmMigration in\n" +
"return { migration, schemaVersion in\n" +
"rlmMigration(migration.rlmMigration, schemaVersion)\n" +
"}\n" +
"}",
"let mediaView: UIView = { [weak self] index in\n" +
" return UIView()\n" +
"}(index)\n"
],
triggeringExamples: [
"[1, 2].map {\n ↓number in\n number + 1 \n}\n",
"[1, 2].map {\n ↓number -> Int in\n number + 1 \n}\n",
"[1, 2].map {\n (↓number: Int) -> Int in\n number + 1 \n}\n",
"[1, 2].map {\n [weak self] ↓number in\n number + 1 \n}\n",
"[1, 2].map { [weak self]\n ↓number in\n number + 1 \n}\n",
"[1, 2].map({\n ↓number in\n number + 1 \n})\n",
"[1, 2].something(closure: {\n ↓number in\n number + 1 \n})\n",
"[1, 2].reduce(0) {\n ↓sum, ↓number in\n number + sum \n}\n"
]
)
private static let openBraceRegex = regex("\\{")
public func validate(file: File, kind: SwiftExpressionKind,
dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
guard kind == .call else {
return []
}
guard let nameOffset = dictionary.nameOffset,
let nameLength = dictionary.nameLength,
let bodyLength = dictionary.bodyLength,
bodyLength > 0 else {
return []
}
let parameters = dictionary.enclosedVarParameters
let rangeStart = nameOffset + nameLength
let regex = ClosureParameterPositionRule.openBraceRegex
// parameters from inner closures are reported on the top-level one, so we can't just
// use the first and last parameters to check, we need to check all of them
return parameters.flatMap { param -> StyleViolation? in
guard let paramOffset = param.offset, paramOffset > rangeStart else {
return nil
}
let rangeLength = paramOffset - rangeStart
let contents = file.contents.bridge()
guard let range = contents.byteRangeToNSRange(start: rangeStart, length: rangeLength),
let match = regex.matches(in: file.contents, options: [], range: range).last?.range,
match.location != NSNotFound,
let braceOffset = contents.NSRangeToByteRange(start: match.location, length: match.length)?.location,
let (braceLine, _) = contents.lineAndCharacter(forByteOffset: braceOffset),
let (paramLine, _) = contents.lineAndCharacter(forByteOffset: paramOffset),
braceLine != paramLine else {
return nil
}
return StyleViolation(ruleDescription: type(of: self).description,
severity: configuration.severity,
location: Location(file: file, byteOffset: paramOffset))
}
}
}