<commit_before>//
//  TrailingClosureRule.swift
//  SwiftLint
//
//  Created by <NAME> on 01/15/17.
//  Copyright © 2017 Realm. All rights reserved.
//

import Foundation
import SourceKittenFramework

public struct TrailingClosureRule: OptInRule, ConfigurationProviderRule {
    public var configuration = SeverityConfiguration(.warning)

    public init() {}

    public static let description = RuleDescription(
        identifier: "trailing_closure",
        name: "Trailing Closure",
        description: "Trailing closure syntax should be used whenever possible",
        nonTriggeringExamples: [
            "foo.map { $0 + 1 }\n",
            "foo.bar()\n",
            "foo.reduce(0) { $0 + 1 }\n",
            "if let foo = bar.map({ $0 + 1 }) { }\n"
        ],
        triggeringExamples: [
            "↓foo.map({ $0 + 1 })\n",
            "↓foo.reduce(0, combine: { $0 + 1 })\n"
        ]
    )

    public func validate(file: File) -> [StyleViolation] {
        return violationOffsets(for: file.structure.dictionary, file: file).map {
            StyleViolation(ruleDescription: type(of: self).description,
                           severity: configuration.severity,
                           location: Location(file: file, byteOffset: $0))
        }
    }

    private func violationOffsets(for dictionary: [String: SourceKitRepresentable], file: File) -> [Int] {
        var results = [Int]()

        if (dictionary["key.kind"] as? String).flatMap(SwiftExpressionKind.init) == .call,
            shouldBeTrailingClosure(dictionary: dictionary, file: file),
            let offset = (dictionary["key.offset"] as? Int64).flatMap({ Int($0) }) {

            results = [offset]
        }

        if let kind = (dictionary["key.kind"] as? String).flatMap(StatementKind.init), kind != .brace {
            // trailing closures are not allowed in `if`, `guard`, etc
            results += dictionary.substructure.flatMap { subDict -> [Int] in
                guard (subDict["key.kind"] as? String).flatMap(StatementKind.init) == .brace else {
                    return []
                }

                return violationOffsets(for: subDict, file: file)
            }
        } else {
            results += dictionary.substructure.flatMap { subDict in
                violationOffsets(for: subDict, file: file)
            }
        }

        return results
    }

    private func shouldBeTrailingClosure(dictionary: [String: SourceKitRepresentable], file: File) -> Bool {
        let arguments = dictionary.enclosedArguments

        // check if last parameter should be trailing closure
        if arguments.count > 1,
            let lastArgument = dictionary.enclosedArguments.last,
            (lastArgument["key.name"] as? String) != nil,
            let offset = (lastArgument["key.bodyoffset"] as? Int64).flatMap({ Int($0) }),
            let length = (lastArgument["key.bodylength"] as? Int64).flatMap({ Int($0) }),
            let range = file.contents.bridge().byteRangeToNSRange(start: offset, length: length),
            let match = regex("\\s*\\{").firstMatch(in: file.contents, options: [], range: range)?.range,
            match.location == range.location {
            return true
        }

        // check if there's only one unnamed parameter that is a closure
        if arguments.isEmpty,
            let offset = (dictionary["key.offset"] as? Int64).flatMap({ Int($0) }),
            let totalLength = (dictionary["key.length"] 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) }),
            case let start = nameOffset + nameLength,
            case let length = totalLength + offset - start,
            let range = file.contents.bridge().byteRangeToNSRange(start: start, length: length),
            let match = regex("\\s*\\(\\s*\\{").firstMatch(in: file.contents, options: [], range: range)?.range,
            match.location == range.location {
            return true
        }

        return false
    }
}<commit_msg>Don't trigger when there're more than 1 closure parameter<commit_after>//
//  TrailingClosureRule.swift
//  SwiftLint
//
//  Created by <NAME> on 01/15/17.
//  Copyright © 2017 Realm. All rights reserved.
//

import Foundation
import SourceKittenFramework

public struct TrailingClosureRule: OptInRule, ConfigurationProviderRule {
    public var configuration = SeverityConfiguration(.warning)

    public init() {}

    public static let description = RuleDescription(
        identifier: "trailing_closure",
        name: "Trailing Closure",
        description: "Trailing closure syntax should be used whenever possible",
        nonTriggeringExamples: [
            "foo.map { $0 + 1 }\n",
            "foo.bar()\n",
            "foo.reduce(0) { $0 + 1 }\n",
            "if let foo = bar.map({ $0 + 1 }) { }\n",
            "foo.something(0, { $0 + 1 })\n",
            "foo.something(param1: { $0 }, param2: { $0 + 1 })\n",
            "offsets.sorted(by: { $0.offset < $1.offset })\n"
        ],
        triggeringExamples: [
            "↓foo.map({ $0 + 1 })\n",
            "↓foo.reduce(0, combine: { $0 + 1 })\n"
        ]
    )

    public func validate(file: File) -> [StyleViolation] {
        return violationOffsets(for: file.structure.dictionary, file: file).map {
            StyleViolation(ruleDescription: type(of: self).description,
                           severity: configuration.severity,
                           location: Location(file: file, byteOffset: $0))
        }
    }

    private func violationOffsets(for dictionary: [String: SourceKitRepresentable], file: File) -> [Int] {
        var results = [Int]()

        if (dictionary["key.kind"] as? String).flatMap(SwiftExpressionKind.init) == .call,
            shouldBeTrailingClosure(dictionary: dictionary, file: file),
            let offset = (dictionary["key.offset"] as? Int64).flatMap({ Int($0) }) {

            results = [offset]
        }

        if let kind = (dictionary["key.kind"] as? String).flatMap(StatementKind.init), kind != .brace {
            // trailing closures are not allowed in `if`, `guard`, etc
            results += dictionary.substructure.flatMap { subDict -> [Int] in
                guard (subDict["key.kind"] as? String).flatMap(StatementKind.init) == .brace else {
                    return []
                }

                return violationOffsets(for: subDict, file: file)
            }
        } else {
            results += dictionary.substructure.flatMap { subDict in
                violationOffsets(for: subDict, file: file)
            }
        }

        return results
    }

    private func shouldBeTrailingClosure(dictionary: [String: SourceKitRepresentable], file: File) -> Bool {
        let arguments = dictionary.enclosedArguments

        // check if last parameter should be trailing closure
        if arguments.count > 1,
            case let closureArguments = filterClosureArguments(arguments, file: file),
            closureArguments.count == 1,
            closureArguments.last?.bridge() == arguments.last?.bridge() {
            return true
        }

        // check if there's only one unnamed parameter that is a closure
        if arguments.isEmpty,
            let offset = (dictionary["key.offset"] as? Int64).flatMap({ Int($0) }),
            let totalLength = (dictionary["key.length"] 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) }),
            case let start = nameOffset + nameLength,
            case let length = totalLength + offset - start,
            let range = file.contents.bridge().byteRangeToNSRange(start: start, length: length),
            let match = regex("\\s*\\(\\s*\\{").firstMatch(in: file.contents, options: [], range: range)?.range,
            match.location == range.location {
            return true
        }

        return false
    }

    private func filterClosureArguments(_ arguments: [[String: SourceKitRepresentable]],
                                        file: File) -> [[String: SourceKitRepresentable]] {
        return arguments.filter { argument in
            guard (argument["key.name"] as? String) != nil,
                let offset = (argument["key.bodyoffset"] as? Int64).flatMap({ Int($0) }),
                let length = (argument["key.bodylength"] as? Int64).flatMap({ Int($0) }),
                let range = file.contents.bridge().byteRangeToNSRange(start: offset, length: length),
                let match = regex("\\s*\\{").firstMatch(in: file.contents, options: [], range: range)?.range,
                match.location == range.location else {
                    return false
            }

            return true
        }
    }
}
{'fraction_non_alphanumeric': 0.13139988845510317, 'fraction_numerical': 0.011377579475738985, 'mean_word_length': 2.145964912280702, 'pattern_counts': {'":': 0, '<': 6, '<?xml version=': 0, '>': 16, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3432397', 'n_tokens_mistral': 2523, 'n_tokens_neox': 2393, 'n_words': 639}