import SourceKittenFramework import TestHelpers import XCTest @testable import SwiftLintBuiltInRules @testable import SwiftLintCore final class BaselineTests: XCTestCase { private static let example = """ import Foundation import SwiftLintFramework class Example: NSObject { private var foo: Int private var bar: String init(foo: Int, bar: String) { self.foo = foo self.bar = bar } // init func someFunction() -> Int { foo * 10 } // someFunction func someOtherFunction() -> String { bar } // someOtherFunction func yetAnotherFunction() -> (Int, String) { (foo, bar) } // yetAnotherFunction } """ private static let ruleDescriptions = [ ArrayInitRule.description, BlockBasedKVORule.description, ClosingBraceRule.description, DirectReturnRule.description, ] private static func violations(for filePath: URL?) -> [StyleViolation] { ruleDescriptions.violations(for: filePath) } private static func baseline(for filePath: URL) -> Baseline { Baseline(violations: ruleDescriptions.violations(for: filePath)) } nonisolated(unsafe) private static var currentDirectoryPath: String? override static func setUp() { super.setUp() currentDirectoryPath = FileManager.default.currentDirectoryPath XCTAssertTrue(FileManager.default.changeCurrentDirectoryPath(URL.temporaryDirectory.filepath)) } override static func tearDown() { XCTAssertTrue(FileManager.default.changeCurrentDirectoryPath(currentDirectoryPath!)) super.tearDown() } func testWritingAndReading() throws { try withExampleFileCreated { sourceFilePath in let baselinePath = URL.temporaryDirectory.appending(path: UUID().uuidString) try Baseline(violations: Self.violations(for: sourceFilePath)).write(toPath: baselinePath) defer { try? FileManager.default.removeItem(atPath: baselinePath.filepath) } let newBaseline = try Baseline(fromPath: baselinePath) XCTAssertEqual(newBaseline, Self.baseline(for: sourceFilePath)) } } func testUnchangedViolations() throws { try withExampleFileCreated { sourceFilePath in XCTAssertEqual(Self.baseline(for: sourceFilePath).filter(Self.violations(for: sourceFilePath)), []) } } func testShiftedViolations() throws { try withExampleFileCreated { sourceFilePath in let baseline = Self.baseline(for: sourceFilePath) let violations = try Self.violations(for: sourceFilePath).lineShifted(by: 2, path: sourceFilePath) XCTAssertEqual(baseline.filter(violations), []) } } func testNewViolation() throws { try testViolationDetection( violationRuleDescriptions: Self.ruleDescriptions, newViolationRuleDescription: EmptyCollectionLiteralRule.description, insertionIndex: 2 ) } func testViolationDetection() throws { let violationRuleDescriptions = [ ArrayInitRule.description, BlockBasedKVORule.description, ArrayInitRule.description, ClosingBraceRule.description, ClosingBraceRule.description, ClosingBraceRule.description, BlockBasedKVORule.description, DirectReturnRule.description, ArrayInitRule.description, ClosingBraceRule.description, ] let ruleDescriptions = [ ArrayInitRule.description, BlockBasedKVORule.description, ClosingBraceRule.description, DirectReturnRule.description, ] for ruleDescription in ruleDescriptions { for insertionIndex in 0.. Void) throws { let sourceFilePath = URL.temporaryDirectory.appending(path: "\(UUID().uuidString).swift") guard let data = Self.example.data(using: .utf8) else { XCTFail("Could not convert example code to data using UTF-8 encoding") return } try data.write(to: sourceFilePath) defer { try? FileManager.default.removeItem(atPath: sourceFilePath.filepath) } try block(sourceFilePath) } } private extension [StyleViolation] { func lineShifted(by shift: Int, path: URL) throws -> [StyleViolation] { guard shift > 0 else { XCTFail("Shift must be positive") return self } var lines = SwiftLintFile(path: path)?.lines.map(\.content) ?? [] lines = [String](repeating: "", count: shift) + lines if let data = lines.joined(separator: "\n").data(using: .utf8) { try data.write(to: path) } return map { let shiftedLocation = Location( file: path, line: $0.location.line != nil ? $0.location.line! + shift : nil, character: $0.location.character ) return $0.with(location: shiftedLocation) } } } private extension Sequence where Element == RuleDescription { func violations(for filePath: URL?) -> [StyleViolation] { enumerated().map { index, ruleDescription in StyleViolation( ruleDescription: ruleDescription, location: Location(file: filePath, line: (index + 1) * 2, character: 1) ) } } }