<commit_before>//
//  ContainsOverFirstNotNilRule.swift
//  SwiftLint
//
//  Created by <NAME> on 17/09/17.
//  Copyright © 2016 Realm. All rights reserved.
//

import Foundation
import SourceKittenFramework

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

    public init() {}

    public static let description = RuleDescription(
        identifier: "contains_over_first_not_nil",
        name: "Contains over first not nil",
        description: "Prefer `contains` over `first(where:) != nil`",
        kind: .performance,
        nonTriggeringExamples: [
            "let first = myList.first(where: { $0 % 2 == 0 })\n",
            "let first = myList.first { $0 % 2 == 0 }\n"
        ],

        triggeringExamples: [
            "↓myList.first { $0 % 2 == 0 } != nil\n",
            "↓myList.first(where: { $0 % 2 == 0 }) != nil\n",
            "↓myList.map { $0 + 1 }.first(where: { $0 % 2 == 0 }) != nil\n",
            "↓myList.first(where: someFunction) != nil\n",
            "↓myList.map { $0 + 1 }.first { $0 % 2 == 0 } != nil\n"
        ]
    )

    public func validate(file: File) -> [StyleViolation] {
        let pattern = "[\\}\\)]\\s*!=\\s*nil"
        let firstRanges = file.match(pattern: pattern, with: [.keyword])
        let contents = file.contents.bridge()
        let structure = file.structure

        let violatingLocations: [Int] = firstRanges.flatMap {
            guard let bodyByteRange = contents.NSRangeToByteRange(start: $0.location,
                                                                  length: $0.length),
                case let firstLocation = $0.location + $0.length - 1,
                let firstByteRange = contents.NSRangeToByteRange(start: firstLocation,
                                                                 length: 1) else {
                                                                    return nil
            }

            return methodCall(forByteOffset: bodyByteRange.location - 1,
                              excludingOffset: firstByteRange.location,
                              dictionary: structure.dictionary,
                              predicate: { dictionary in
                                guard let name = dictionary.name else {
                                    return false
                                }

                                return name.hasSuffix(".first")
            })
        }

        return violatingLocations.map {
            StyleViolation(ruleDescription: type(of: self).description,
                           severity: configuration.severity,
                           location: Location(file: file, byteOffset: $0))
        }
    }

    private func methodCall(forByteOffset byteOffset: Int,
                            excludingOffset: Int,
                            dictionary: [String: SourceKitRepresentable],
                            predicate: ([String: SourceKitRepresentable]) -> Bool) -> Int? {

        if let kindString = (dictionary.kind),
            SwiftExpressionKind(rawValue: kindString) == .call,
            let bodyOffset = dictionary.bodyOffset,
            let bodyLength = dictionary.bodyLength,
            let offset = dictionary.offset {
            let byteRange = NSRange(location: bodyOffset, length: bodyLength)

            if NSLocationInRange(byteOffset, byteRange) &&
                !NSLocationInRange(excludingOffset, byteRange) && predicate(dictionary) {
                return offset
            }
        }

        for dictionary in dictionary.substructure {
            if let offset = methodCall(forByteOffset: byteOffset,
                                       excludingOffset: excludingOffset,
                                       dictionary: dictionary,
                                       predicate: predicate) {
                return offset
            }
        }

        return nil
    }
}<commit_msg>Small changes to ContainsOverFirstNotNilRule<commit_after>//
//  ContainsOverFirstNotNilRule.swift
//  SwiftLint
//
//  Created by <NAME> on 17/09/17.
//  Copyright © 2017 Realm. All rights reserved.
//

import Foundation
import SourceKittenFramework

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

    public init() {}

    public static let description = RuleDescription(
        identifier: "contains_over_first_not_nil",
        name: "Contains over first not nil",
        description: "Prefer `contains` over `first(where:) != nil`",
        kind: .performance,
        nonTriggeringExamples: [
            "let first = myList.first(where: { $0 % 2 == 0 })\n",
            "let first = myList.first { $0 % 2 == 0 }\n"
        ],
        triggeringExamples: [
            "↓myList.first { $0 % 2 == 0 } != nil\n",
            "↓myList.first(where: { $0 % 2 == 0 }) != nil\n",
            "↓myList.map { $0 + 1 }.first(where: { $0 % 2 == 0 }) != nil\n",
            "↓myList.first(where: someFunction) != nil\n",
            "↓myList.map { $0 + 1 }.first { $0 % 2 == 0 } != nil\n"
        ]
    )

    public func validate(file: File) -> [StyleViolation] {
        let pattern = "[\\}\\)]\\s*!=\\s*nil"
        let firstRanges = file.match(pattern: pattern, with: [.keyword])
        let contents = file.contents.bridge()
        let structure = file.structure

        let violatingLocations: [Int] = firstRanges.flatMap { range in
            guard let bodyByteRange = contents.NSRangeToByteRange(start: range.location,
                                                                  length: range.length),
                case let firstLocation = range.location + range.length - 1,
                let firstByteRange = contents.NSRangeToByteRange(start: firstLocation, length: 1)
                else {
                    return nil
            }

            return methodCall(forByteOffset: bodyByteRange.location - 1,
                              excludingOffset: firstByteRange.location, dictionary: structure.dictionary,
                              predicate: { dictionary in
                guard let name = dictionary.name else {
                    return false
                }

                return name.hasSuffix(".first")
            })
        }

        return violatingLocations.map {
            StyleViolation(ruleDescription: type(of: self).description,
                           severity: configuration.severity,
                           location: Location(file: file, byteOffset: $0))
        }
    }

    private func methodCall(forByteOffset byteOffset: Int, excludingOffset: Int,
                            dictionary: [String: SourceKitRepresentable],
                            predicate: ([String: SourceKitRepresentable]) -> Bool) -> Int? {

        if let kindString = dictionary.kind,
            SwiftExpressionKind(rawValue: kindString) == .call,
            let bodyOffset = dictionary.bodyOffset,
            let bodyLength = dictionary.bodyLength,
            let offset = dictionary.offset {
            let byteRange = NSRange(location: bodyOffset, length: bodyLength)

            if byteRange.contains(byteOffset) && !byteRange.contains(excludingOffset)
                && predicate(dictionary) {
                return offset
            }
        }

        for dictionary in dictionary.substructure {
            if let offset = methodCall(forByteOffset: byteOffset,
                                       excludingOffset: excludingOffset,
                                       dictionary: dictionary,
                                       predicate: predicate) {
                return offset
            }
        }

        return nil
    }
}
{'fraction_non_alphanumeric': 0.0988982833717653, 'fraction_numerical': 0.009736100435562388, 'mean_word_length': 1.5496407576747224, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 15, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4332683', 'n_tokens_mistral': 2017, 'n_tokens_neox': 1884, 'n_words': 479}