@testable import SwiftLintBuiltInRules import TestHelpers import XCTest final class FileHeaderRuleTests: SwiftLintTestCase { private func validate(fileName: String, using configuration: Any) throws -> [StyleViolation] { let file = TestResources.path() .appending(path: "FileHeaderRuleFixtures", directoryHint: .isDirectory) .appending(path: fileName, directoryHint: .notDirectory) let rule = try FileHeaderRule(configuration: configuration) return rule.validate(file: SwiftLintFile(path: file)!) } func testFileHeaderWithDefaultConfiguration() { verifyRule(FileHeaderRule.description, skipCommentTests: true) } func testFileHeaderWithRequiredString() { let nonTriggeringExamples = [ Example("// **Header"), Example("//\n// **Header"), ] let triggeringExamples = [ Example("↓// Copyright\n"), Example("let foo = \"**Header\""), Example("let foo = 2 // **Header"), Example("let foo = 2\n// **Header"), Example("let foo = 2 // **Header"), ] let description = FileHeaderRule.description .with(nonTriggeringExamples: nonTriggeringExamples) .with(triggeringExamples: triggeringExamples) verifyRule(description, ruleConfiguration: ["required_string": "**Header"], stringDoesntViolate: false, skipCommentTests: true, skipDisableCommandTests: true, testMultiByteOffsets: false, testShebang: false) } func testFileHeaderWithRequiredPattern() { let nonTriggeringExamples = [ Example("// Copyright © 2016 Realm"), Example("//\n// Copyright © 2016 Realm)"), ] let triggeringExamples = [ Example("↓// Copyright\n"), Example("↓// Copyright © foo Realm"), Example("↓// Copyright © 2016 MyCompany"), ] let description = FileHeaderRule.description .with(nonTriggeringExamples: nonTriggeringExamples) .with(triggeringExamples: triggeringExamples) verifyRule(description, ruleConfiguration: ["required_pattern": "\\d{4} Realm"], stringDoesntViolate: false, skipCommentTests: true, testMultiByteOffsets: false) } func testFileHeaderWithRequiredStringAndURLComment() { let nonTriggeringExamples = [ Example("/* Check this url: https://github.com/realm/SwiftLint */") ] let triggeringExamples = [ Example("/* Check this url: https://github.com/apple/swift */") ] let description = FileHeaderRule.description .with(nonTriggeringExamples: nonTriggeringExamples) .with(triggeringExamples: triggeringExamples) let config = ["required_string": "/* Check this url: https://github.com/realm/SwiftLint */"] verifyRule(description, ruleConfiguration: config, stringDoesntViolate: false, skipCommentTests: true, testMultiByteOffsets: false) } func testFileHeaderWithForbiddenString() { let nonTriggeringExamples = [ Example("// Copyright\n"), Example("let foo = \"**All rights reserved.\""), Example("let foo = 2 // **All rights reserved."), Example("let foo = 2\n// **All rights reserved."), Example("let foo = 2 // **All rights reserved."), ] let triggeringExamples = [ Example("// ↓**All rights reserved."), Example("//\n// ↓**All rights reserved."), ] let description = FileHeaderRule.description .with(nonTriggeringExamples: nonTriggeringExamples) .with(triggeringExamples: triggeringExamples) verifyRule(description, ruleConfiguration: ["forbidden_string": "**All rights reserved."], skipCommentTests: true) } func testFileHeaderWithForbiddenPattern() { let nonTriggeringExamples = [ Example("// Copyright\n"), Example("// FileHeaderRuleTests.m\n"), Example("let foo = \"FileHeaderRuleTests.swift\""), Example("let foo = 2 // FileHeaderRuleTests.swift."), Example("let foo = 2\n // FileHeaderRuleTests.swift."), ] let triggeringExamples = [ Example("//↓ FileHeaderRuleTests.swift"), Example("//\n//↓ FileHeaderRuleTests.swift"), ] let description = FileHeaderRule.description .with(nonTriggeringExamples: nonTriggeringExamples) .with(triggeringExamples: triggeringExamples) verifyRule(description, ruleConfiguration: ["forbidden_pattern": "\\s\\w+\\.swift"], skipCommentTests: true) } func testFileHeaderWithForbiddenPatternAndDocComment() { let nonTriggeringExamples = [ Example("/// This is great tool with tests.\nclass GreatTool {}"), Example("class GreatTool {}"), ] let triggeringExamples = [ Example("// FileHeaderRule↓Tests.swift"), Example("//\n// FileHeaderRule↓Tests.swift"), ] let description = FileHeaderRule.description .with(nonTriggeringExamples: nonTriggeringExamples) .with(triggeringExamples: triggeringExamples) verifyRule(description, ruleConfiguration: ["forbidden_pattern": "[tT]ests"], skipCommentTests: true, testMultiByteOffsets: false) } func testFileHeaderWithRequiredStringUsingFilenamePlaceholder() { let configuration = ["required_string": "// SWIFTLINT_CURRENT_FILENAME"] // Non triggering tests XCTAssert(try validate(fileName: "FileNameMatchingSimple.swift", using: configuration).isEmpty) // Triggering tests XCTAssertEqual(try validate(fileName: "FileNameCaseMismatch.swift", using: configuration).count, 1) XCTAssertEqual(try validate(fileName: "FileNameMismatch.swift", using: configuration).count, 1) XCTAssertEqual(try validate(fileName: "FileNameMissing.swift", using: configuration).count, 1) } func testFileHeaderWithForbiddenStringUsingFilenamePlaceholder() { let configuration = ["forbidden_string": "// SWIFTLINT_CURRENT_FILENAME"] // Non triggering tests XCTAssert(try validate(fileName: "FileNameCaseMismatch.swift", using: configuration).isEmpty) XCTAssert(try validate(fileName: "FileNameMismatch.swift", using: configuration).isEmpty) XCTAssert(try validate(fileName: "FileNameMissing.swift", using: configuration).isEmpty) // Triggering tests XCTAssertEqual(try validate(fileName: "FileNameMatchingSimple.swift", using: configuration).count, 1) } func testFileHeaderWithRequiredPatternUsingFilenamePlaceholder() { let configuration1 = ["required_pattern": "// SWIFTLINT_CURRENT_FILENAME\n.*\\d{4}"] let configuration2 = [ "required_pattern": "// Copyright © \\d{4}\n// File: \"SWIFTLINT_CURRENT_FILENAME\"", ] // Non triggering tests XCTAssert(try validate(fileName: "FileNameMatchingSimple.swift", using: configuration1).isEmpty) XCTAssert(try validate(fileName: "FileNameMatchingComplex.swift", using: configuration2).isEmpty) // Triggering tests XCTAssertEqual(try validate(fileName: "FileNameCaseMismatch.swift", using: configuration1).count, 1) XCTAssertEqual(try validate(fileName: "FileNameMismatch.swift", using: configuration1).count, 1) XCTAssertEqual(try validate(fileName: "FileNameMissing.swift", using: configuration1).count, 1) } func testFileHeaderWithForbiddenPatternUsingFilenamePlaceholder() { let configuration1 = ["forbidden_pattern": "// SWIFTLINT_CURRENT_FILENAME\n.*\\d{4}"] let configuration2 = ["forbidden_pattern": "//.*(\\s|\")SWIFTLINT_CURRENT_FILENAME(\\s|\").*"] // Non triggering tests XCTAssert(try validate(fileName: "FileNameCaseMismatch.swift", using: configuration1).isEmpty) XCTAssert(try validate(fileName: "FileNameMismatch.swift", using: configuration1).isEmpty) XCTAssert(try validate(fileName: "FileNameMissing.swift", using: configuration1).isEmpty) XCTAssert(try validate(fileName: "FileNameCaseMismatch.swift", using: configuration2).isEmpty) XCTAssert(try validate(fileName: "FileNameMismatch.swift", using: configuration2).isEmpty) XCTAssert(try validate(fileName: "FileNameMissing.swift", using: configuration2).isEmpty) // Triggering tests XCTAssertEqual(try validate(fileName: "FileNameMatchingSimple.swift", using: configuration1).count, 1) XCTAssertEqual(try validate(fileName: "FileNameMatchingComplex.swift", using: configuration2).count, 1) } func testFileHeaderShouldBeEmpty() { let configuration = ["forbidden_pattern": "."] // Non triggering tests XCTAssert(try validate(fileName: "FileHeaderEmpty.swift", using: configuration).isEmpty) XCTAssert(try validate(fileName: "DocumentedType.swift", using: configuration).isEmpty) // Triggering tests XCTAssertEqual(try validate(fileName: "FileNameCaseMismatch.swift", using: configuration).count, 1) XCTAssertEqual(try validate(fileName: "FileNameMismatch.swift", using: configuration).count, 1) XCTAssertEqual(try validate(fileName: "FileNameMissing.swift", using: configuration).count, 1) } func testSimplePattern() { let description = FileHeaderRule.description .with(nonTriggeringExamples: [ Example(""" // Test enum Test {} """), Example(""" // Test """), Example(""" // Test """), ]) .with(triggeringExamples: []) verifyRule( description, ruleConfiguration: [ "required_pattern": #""" \/\/ Test """#, // The empty line at the end is important since YAML adds it as well in `|` blocks. ], skipCommentTests: true, testMultiByteOffsets: false ) } func testPattern() { let description = FileHeaderRule.description .with(nonTriggeringExamples: [ Example(""" // // Test.swift // Dummy App // // Created by Alice Bob on 3.9.2025. // Copyright © 2025 Dummy Corporation. All rights reserved. // enum Test {} """), ]) .with(triggeringExamples: []) verifyRule( description, ruleConfiguration: [ "required_pattern": #""" \/\/ \/\/ Test\.swift \/\/ .*? \/\/ \/\/ Created by .*? on \d{1,2}[\.\/]\d{1,2}[\.\/]\d{2,4}\. \/\/ Copyright © \d{4} Dummy Corporation\. All rights reserved\. \/\/ """#, // The empty line at the end is important since YAML adds it as well in `|` blocks. ], skipCommentTests: true, testMultiByteOffsets: false ) } }