<commit_before>//
//  TestHelpers.swift
//  SwiftLint
//
//  Created by <NAME> on 2015-05-16.
//  Copyright (c) 2015 Realm. All rights reserved.
//

import Foundation
@testable import SwiftLintFramework
import SourceKittenFramework
import XCTest

private let violationMarker = "↓"

let allRuleIdentifiers = Array(masterRuleList.list.keys)

func violations(_ string: String, config: Configuration = Configuration()) -> [StyleViolation] {
    File.clearCaches()
    let stringStrippingMarkers = string.replacingOccurrences(of: violationMarker, with: "")
    let file = File(contents: stringStrippingMarkers)
    return Linter(file: file, configuration: config).styleViolations
}

private func cleanedContentsAndMarkerOffsets(from contents: String) -> (String, [Int]) {
    var contents = contents as NSString
    var markerOffsets = [Int]()
    var markerRange = contents.range(of: violationMarker)
    while markerRange.location != NSNotFound {
        markerOffsets.append(markerRange.location)
        contents = contents.replacingCharacters(in: markerRange, with: "") as NSString
        markerRange = contents.range(of: violationMarker)
    }
    return (contents as String, markerOffsets.sorted())
}

extension Configuration {
    fileprivate func assertCorrection(_ before: String, expected: String) {
        guard let path = NSURL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
            .appendingPathComponent(NSUUID().uuidString + ".swift")?.path else {
                XCTFail("couldn't generate temporary path for assertCorrection()")
                return
        }
        let (cleanedBefore, markerOffsets) = cleanedContentsAndMarkerOffsets(from: before)
        do {
            try cleanedBefore.write(toFile: path, atomically: true, encoding: .utf8)
        } catch {
            XCTFail("couldn't write to file for assertCorrection() with error: \(error)")
            return
        }
        guard let file = File(path: path) else {
            XCTFail("couldn't read file at path '\(path)' for assertCorrection()")
            return
        }
        // expectedLocations are needed to create before call `correct()`
        let expectedLocations = markerOffsets.map { Location(file: file, characterOffset: $0) }
        let corrections = Linter(file: file, configuration: self).correct().sorted {
            $0.location < $1.location
        }
        if expectedLocations.isEmpty {
            XCTAssertEqual(corrections.count, before != expected ? 1 : 0)
        } else {
            XCTAssertEqual(corrections.count, expectedLocations.count)
            for (correction, expectedLocation) in zip(corrections, expectedLocations) {
                XCTAssertEqual(correction.location, expectedLocation)
            }
        }
        XCTAssertEqual(file.contents, expected)
        do {
            let corrected = try String(contentsOfFile: path, encoding: .utf8)
            XCTAssertEqual(corrected, expected)
        } catch {
            XCTFail("couldn't read file at path '\(path)': \(error)")
        }
    }
}

extension String {
    fileprivate func toStringLiteral() -> String {
        return "\"" + replacingOccurrences(of: "\n", with: "\\n") + "\""
    }
}

fileprivate func makeConfig(_ ruleConfiguration: Any?, _ identifier: String) -> Configuration? {
    if let ruleConfiguration = ruleConfiguration, let ruleType = masterRuleList.list[identifier] {
        // The caller has provided a custom configuration for the rule under test
        return (try? ruleType.init(configuration: ruleConfiguration)).flatMap { configuredRule in
            return Configuration(whitelistRules: [identifier], configuredRules: [configuredRule])
        }
    }
    return Configuration(whitelistRules: [identifier])
}

extension XCTestCase {
    func verifyRule(_ ruleDescription: RuleDescription,
                    ruleConfiguration: Any? = nil,
                    commentDoesntViolate: Bool = true,
                    stringDoesntViolate: Bool = true) {
        guard let config = makeConfig(ruleConfiguration, ruleDescription.identifier) else {
            XCTFail()
            return
        }

        let triggers = ruleDescription.triggeringExamples
        let nonTriggers = ruleDescription.nonTriggeringExamples

        // Non-triggering examples don't violate
        XCTAssertEqual(nonTriggers.flatMap({ violations($0, config: config) }), [])

        var violationsCount = 0
        var expectedViolationsCount = 0
        for trigger in triggers {<commit_msg>Produce detailed log of generated violations from nonTriggeringExamples<commit_after>//
//  TestHelpers.swift
//  SwiftLint
//
//  Created by <NAME> on 2015-05-16.
//  Copyright (c) 2015 Realm. All rights reserved.
//

import Foundation
@testable import SwiftLintFramework
import SourceKittenFramework
import XCTest

private let violationMarker = "↓"

let allRuleIdentifiers = Array(masterRuleList.list.keys)

func violations(_ string: String, config: Configuration = Configuration()) -> [StyleViolation] {
    File.clearCaches()
    let stringStrippingMarkers = string.replacingOccurrences(of: violationMarker, with: "")
    let file = File(contents: stringStrippingMarkers)
    return Linter(file: file, configuration: config).styleViolations
}

private func cleanedContentsAndMarkerOffsets(from contents: String) -> (String, [Int]) {
    var contents = contents as NSString
    var markerOffsets = [Int]()
    var markerRange = contents.range(of: violationMarker)
    while markerRange.location != NSNotFound {
        markerOffsets.append(markerRange.location)
        contents = contents.replacingCharacters(in: markerRange, with: "") as NSString
        markerRange = contents.range(of: violationMarker)
    }
    return (contents as String, markerOffsets.sorted())
}

func render(violations: [StyleViolation], in contents: String) -> String {
    var contents = (contents as NSString).lines().map { $0.content }
    for violation in violations.sorted(by: { $0.location > $1.location }) {
        guard let line = violation.location.line,
            let character = violation.location.character else { continue }

        let message = String(repeating: " ", count: character - 1) + "^ " + [
            "\(violation.severity.rawValue.lowercased()): ",
            "\(violation.ruleDescription.name) Violation: ",
            violation.reason,
            " (\(violation.ruleDescription.identifier))"].joined()
        if line >= contents.count {
            contents.append(message)
        } else {
            contents.insert(message, at: line)
        }
    }
    return (["```"] + contents + ["```"]).joined(separator: "\n")
}

extension Configuration {
    fileprivate func assertCorrection(_ before: String, expected: String) {
        guard let path = NSURL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
            .appendingPathComponent(NSUUID().uuidString + ".swift")?.path else {
                XCTFail("couldn't generate temporary path for assertCorrection()")
                return
        }
        let (cleanedBefore, markerOffsets) = cleanedContentsAndMarkerOffsets(from: before)
        do {
            try cleanedBefore.write(toFile: path, atomically: true, encoding: .utf8)
        } catch {
            XCTFail("couldn't write to file for assertCorrection() with error: \(error)")
            return
        }
        guard let file = File(path: path) else {
            XCTFail("couldn't read file at path '\(path)' for assertCorrection()")
            return
        }
        // expectedLocations are needed to create before call `correct()`
        let expectedLocations = markerOffsets.map { Location(file: file, characterOffset: $0) }
        let corrections = Linter(file: file, configuration: self).correct().sorted {
            $0.location < $1.location
        }
        if expectedLocations.isEmpty {
            XCTAssertEqual(corrections.count, before != expected ? 1 : 0)
        } else {
            XCTAssertEqual(corrections.count, expectedLocations.count)
            for (correction, expectedLocation) in zip(corrections, expectedLocations) {
                XCTAssertEqual(correction.location, expectedLocation)
            }
        }
        XCTAssertEqual(file.contents, expected)
        do {
            let corrected = try String(contentsOfFile: path, encoding: .utf8)
            XCTAssertEqual(corrected, expected)
        } catch {
            XCTFail("couldn't read file at path '\(path)': \(error)")
        }
    }
}

extension String {
    fileprivate func toStringLiteral() -> String {
        return "\"" + replacingOccurrences(of: "\n", with: "\\n") + "\""
    }
}

fileprivate func makeConfig(_ ruleConfiguration: Any?, _ identifier: String) -> Configuration? {
    if let ruleConfiguration = ruleConfiguration, let ruleType = masterRuleList.list[identifier] {
        // The caller has provided a custom configuration for the rule under test
        return (try? ruleType.init(configuration: ruleConfiguration)).flatMap { configuredRule in
            return Configuration(whitelistRules: [identifier], configuredRules: [configuredRule])
        }
    }
    return Configuration(whitelistRules: [identifier])
}

extension XCTestCase {
    func verifyRule(_ ruleDescription: RuleDescription,
                    ruleConfiguration: Any? = nil,
                    commentDoesntViolate: Bool = true,
                    stringDoesntViolate: Bool = true) {
        guard let config = makeConfig(ruleConfiguration, ruleDescription.identifier) else {
            XCTFail()
            return
        }

        let triggers = ruleDescription.triggeringExamples
        let nonTriggers = ruleDescription.nonTriggeringExamples

        // Non-triggering examples don't violate
        for nonTrigger in nonTriggers {
            let unexpectedViolations = violations(nonTrigger, config: config)
            if unexpectedViolations.isEmpty { continue }
            let nonTriggerWithViolations = render(violations: unexpectedViolations, in: nonTrigger)
            XCTFail("nonTriggeringExample violated: \n\(nonTriggerWithViolations)")
        }

        var violationsCount = 0
        var expectedViolationsCount = 0
        for trigger in triggers {
{'fraction_non_alphanumeric': 0.0984390243902439, 'fraction_numerical': 0.004585365853658536, 'mean_word_length': 2.8107806691449815, 'pattern_counts': {'":': 0, '<': 7, '<?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': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1553812', 'n_tokens_mistral': 2744, 'n_tokens_neox': 2638, 'n_words': 707}