Files
SwiftLint/Source/SwiftLintFramework/Rules/ClosureParameterPositionRule.swift
T
JP Simard b83e0991b9 Remove all file headers
The MIT license doesn't require that all files be prepended with this
licensing or copyright information. Realm confirmed that they're ok with this
change. This will enable some companies to contribute to SwiftLint and the
date & authorship information will remain accessible via git source control.
2018-05-04 13:42:02 -07:00

89 lines
3.9 KiB
Swift

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.compactMap { 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))
}
}
}