<commit_before>//
//  QuickDiscouragedCallRule.swift
//  SwiftLint
//
//  Created by <NAME> on 8/11/17.
//  Copyright © 2017 Realm. All rights reserved.
//

import Foundation
import SourceKittenFramework

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

    public init() {}

    public static let description = RuleDescription(
        identifier: "quick_discouraged_call",
        name: "Quick Discouraged Call",
        description: "Discouraged call inside 'describe' and/or 'context' block.",
        kind: .lint,
        nonTriggeringExamples: QuickDiscouragedCallRuleExamples.nonTriggeringExamples,
        triggeringExamples: QuickDiscouragedCallRuleExamples.triggeringExamples
    )

    public func validate(file: File,
                         kind: SwiftExpressionKind,
                         dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
        // is it a call to a restricted method?
        guard
            kind == .call,
            let name = dictionary.name,
            let kindName = QuickCallKind(rawValue: name),
            QuickCallKind.restrictiveKinds.contains(kindName)
            else { return [] }

        // and is this call within the bounds of a QuickSpec calls?
        guard
            let callBounds = structureBounds(dictionary: dictionary),
            !quickSpec(file: file)
                .flatMap(structureBounds)
                .filter(contains(offset: callBounds.offset))
                .isEmpty
            else { return [] }

        return violationOffsets(in: dictionary.enclosedArguments)
            .map {
                StyleViolation(ruleDescription: type(of: self).description,
                               severity: configuration.severity,
                               location: Location(file: file, byteOffset: $0),
                               reason: "Discouraged call inside a '\(name)' block.")
            }
    }

    // MARK: - Private

    typealias StructureBounds = (offset: Int, length: Int)
    typealias ViolationOffset = Int

    private func quickSpec(file: File) -> [[String: SourceKitRepresentable]] {
        return file.structure.dictionary.substructure.filter { $0.inheritedTypes.contains("QuickSpec") }
    }

    private func structureBounds(dictionary: [String: SourceKitRepresentable]) -> StructureBounds? {
        guard
            let offset = dictionary.offset,
            let length = dictionary.length
            else { return nil }

        return StructureBounds(offset: offset, length: length)
    }

    private func contains(offset: Int) -> (StructureBounds) -> Bool {
        return { structureBounds in
            structureBounds.offset..<(structureBounds.offset + structureBounds.length) ~= offset
        }
    }

    private func violationOffsets(in substructure: [[String: SourceKitRepresentable]]) -> [ViolationOffset] {
        return substructure.flatMap { dictionary -> [ViolationOffset] in
            return dictionary.substructure.flatMap(toViolationOffsets)
        }
    }

    private func toViolationOffsets(dictionary: [String: SourceKitRepresentable]) -> [ViolationOffset] {
        guard
            let kind = dictionary.kind,
            let offset = dictionary.offset
            else { return [] }

        if SwiftExpressionKind(rawValue: kind) == .call,
            let name = dictionary.name, QuickCallKind(rawValue: name) == nil {
            return [offset]
        }

        guard SwiftExpressionKind(rawValue: kind) != .call else { return [] }

        return dictionary.substructure.flatMap(toViolationOffset)
    }

    private func toViolationOffset(dictionary: [String: SourceKitRepresentable]) -> ViolationOffset? {
        guard
            let name = dictionary.name,
            let offset = dictionary.offset,
            let kind = dictionary.kind,
            SwiftExpressionKind(rawValue: kind) == .call,
            QuickCallKind(rawValue: name) == nil
            else { return nil }

        return offset
    }
}

// swiftlint:disable identifier_name
private enum QuickCallKind: String {
    case describe
    case context
    case sharedExamples
    case itBehavesLike
    case beforeEach
    case beforeSuite
    case afterEach
    case afterSuite
    case it
    case pending

    static let restrictiveKinds: [QuickCallKind] = [.describe, .context, .sharedExamples]
}
// swiftlint:enabled identifier_name<commit_msg>Alternative approach without ASTRule<commit_after>//
//  QuickDiscouragedCallRule.swift
//  SwiftLint
//
//  Created by <NAME> on 8/11/17.
//  Copyright © 2017 Realm. All rights reserved.
//

import Foundation
import SourceKittenFramework

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

    public init() {}

    public static let description = RuleDescription(
        identifier: "quick_discouraged_call",
        name: "Quick Discouraged Call",
        description: "Discouraged call inside 'describe' and/or 'context' block.",
        kind: .lint,
        nonTriggeringExamples: QuickDiscouragedCallRuleExamples.nonTriggeringExamples,
        triggeringExamples: QuickDiscouragedCallRuleExamples.triggeringExamples
    )

    public func validate(file: File) -> [StyleViolation] {
        let testClasses = file.structure.dictionary.substructure.filter {
            return $0.inheritedTypes.contains("QuickSpec") &&
                $0.kind.flatMap(SwiftDeclarationKind.init) == .class
        }

        let specDeclarations = testClasses.flatMap { classDict in
            return classDict.substructure.filter {
                return $0.name == "spec()" && $0.enclosedVarParameters.isEmpty &&
                    $0.enclosedSwiftAttributes.contains("source.decl.attribute.override")
            }
        }

        return specDeclarations.flatMap {
            validate(file: file, dictionary: $0)
        }
    }

    private func validate(file: File, dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
        return dictionary.substructure.flatMap { subDict -> [StyleViolation] in
            var violations = validate(file: file, dictionary: subDict)

            if let kindString = subDict.kind,
                let kind = SwiftExpressionKind(rawValue: kindString) {
                violations += validate(file: file, kind: kind, dictionary: subDict)
            }

            return violations
        }
    }

    public func validate(file: File,
                         kind: SwiftExpressionKind,
                         dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
        // is it a call to a restricted method?
        guard
            kind == .call,
            let name = dictionary.name,
            let kindName = QuickCallKind(rawValue: name),
            QuickCallKind.restrictiveKinds.contains(kindName)
            else { return [] }

        return violationOffsets(in: dictionary.enclosedArguments).map {
            StyleViolation(ruleDescription: type(of: self).description,
                           severity: configuration.severity,
                           location: Location(file: file, byteOffset: $0),
                           reason: "Discouraged call inside a '\(name)' block.")
        }
    }

    private func violationOffsets(in substructure: [[String: SourceKitRepresentable]]) -> [Int] {
        return substructure.flatMap { dictionary -> [Int] in
            return dictionary.substructure.flatMap(toViolationOffsets)
        }
    }

    private func toViolationOffsets(dictionary: [String: SourceKitRepresentable]) -> [Int] {
        guard
            let kind = dictionary.kind,
            let offset = dictionary.offset
            else { return [] }

        if SwiftExpressionKind(rawValue: kind) == .call,
            let name = dictionary.name, QuickCallKind(rawValue: name) == nil {
            return [offset]
        }

        guard SwiftExpressionKind(rawValue: kind) != .call else { return [] }

        return dictionary.substructure.flatMap(toViolationOffset)
    }

    private func toViolationOffset(dictionary: [String: SourceKitRepresentable]) -> Int? {
        guard
            let name = dictionary.name,
            let offset = dictionary.offset,
            let kind = dictionary.kind,
            SwiftExpressionKind(rawValue: kind) == .call,
            QuickCallKind(rawValue: name) == nil
            else { return nil }

        return offset
    }
}

private enum QuickCallKind: String {
    case describe
    case context
    case sharedExamples
    case itBehavesLike
    case beforeEach
    case beforeSuite
    case afterEach
    case afterSuite
    case it // swiftlint:disable:this identifier_name
    case pending

    static let restrictiveKinds: Set<QuickCallKind> = [.describe, .context, .sharedExamples]
}
{'fraction_non_alphanumeric': 0.08708775852431526, 'fraction_numerical': 0.0030184460592509784, 'mean_word_length': 2.325650557620818, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6719190', 'n_tokens_mistral': 2343, 'n_tokens_neox': 2216, 'n_words': 579}