diff --git a/Sources/CommandLine.swift b/Sources/CommandLine.swift index 18879b15..774e03ec 100644 --- a/Sources/CommandLine.swift +++ b/Sources/CommandLine.swift @@ -581,15 +581,16 @@ func processArguments(_ args: [String], environment: [String: String] = [:], in print("warning: --stdin-path option only applies when using stdin", as: .warning) } let stdinURL = try parsePath(stdinPath, for: "stdin-path", in: directory) - let resourceValues = try getResourceValues( + // Try to get resource values, but if file doesn't exist, just use the path + let resourceValues = try? getResourceValues( for: stdinURL.standardizedFileURL, keys: [.creationDateKey, .pathKey] ) var formatOptions = options.formatOptions ?? .default formatOptions.fileInfo = FileInfo( - filePath: resourceValues.path, - creationDate: resourceValues.creationDate + filePath: resourceValues?.path ?? stdinURL.standardizedFileURL.path, + creationDate: resourceValues?.creationDate ) options.formatOptions = formatOptions } @@ -793,7 +794,8 @@ func processArguments(_ args: [String], environment: [String: String] = [:], in return } - let resourceValues = try getResourceValues( + // Try to get resource values, but allow nil for non-existing files + let resourceValues = try? getResourceValues( for: stdinURL.standardizedFileURL, keys: [.creationDateKey, .pathKey] ) diff --git a/Sources/SwiftFormat.swift b/Sources/SwiftFormat.swift index ea7cd73b..3fd21520 100644 --- a/Sources/SwiftFormat.swift +++ b/Sources/SwiftFormat.swift @@ -312,7 +312,7 @@ public func enumerateFiles(withInputURL inputURL: URL, handler: handler) } -func collectFileInfo(inputURL: URL, options: Options, resourceValues: URLResourceValues) -> FileInfo { +func collectFileInfo(inputURL: URL, options: Options, resourceValues: URLResourceValues?) -> FileInfo { let fileHeaderRuleEnabled = options.rules?.contains(FormatRule.fileHeader.name) ?? false let shouldGetGitInfo = fileHeaderRuleEnabled && options.formatOptions?.fileHeader.needsGitInfo == true @@ -320,8 +320,8 @@ func collectFileInfo(inputURL: URL, options: Options, resourceValues: URLResourc let gitInfo = shouldGetGitInfo ? GitFileInfo(url: inputURL) : nil return FileInfo( - filePath: resourceValues.path, - creationDate: gitInfo?.creationDate ?? resourceValues.creationDate, + filePath: resourceValues?.path ?? inputURL.path, + creationDate: gitInfo?.creationDate ?? resourceValues?.creationDate, replacements: [ .author: ReplacementType(gitInfo?.author), .authorName: ReplacementType(gitInfo?.authorName), diff --git a/Tests/CommandLineTests.swift b/Tests/CommandLineTests.swift index 95205f3a..aaa0f1e2 100644 --- a/Tests/CommandLineTests.swift +++ b/Tests/CommandLineTests.swift @@ -1001,6 +1001,96 @@ final class CommandLineTests: XCTestCase { } } + func testStdinPathWithNonExistingFile() throws { + var output = [String]() + CLI.print = { message, type in + switch type { + case .raw, .content: + output.append(message) + case .error, .warning: + XCTFail(message) + case .info, .success: + break + } + } + var readCount = 0 + CLI.readLine = { + readCount += 1 + switch readCount { + case 1: + return "func foo()\n" + case 2: + return "{\n" + case 3: + return "bar()\n" + case 4: + return "}" + default: + return nil + } + } + + // Use a path that doesn't exist + let nonExistingPath = "/tmp/deleted_file_\(UUID().uuidString).swift" + + _ = processArguments([ + "", + "stdin", + "--stdin-path", nonExistingPath, + ], in: "") + + // Should still format the input, despite file not existing + XCTAssertEqual(output, [""" + func foo() { + bar() + } + + """]) + } + + func testStdinPathWithNonExistingFileExcluded() throws { + var output = [String]() + CLI.print = { message, type in + switch type { + case .raw, .content: + output.append(message) + case .error, .warning: + XCTFail(message) + case .info, .success: + break + } + } + var readCount = 0 + CLI.readLine = { + readCount += 1 + switch readCount { + case 1: + return "func foo()\n" + case 2: + return "{\n" + case 3: + return "bar()\n" + case 4: + return "}" + default: + return nil + } + } + + // Use a path that doesn't exist but matches exclusion pattern + let nonExistingPath = "/tmp/excluded/deleted_file.swift" + + _ = processArguments([ + "", + "stdin", + "--stdin-path", nonExistingPath, + "--exclude", "/tmp/excluded", + ], in: "") + + // Should NOT format because the path is excluded + XCTAssertEqual(output, ["func foo()\n{\nbar()\n}"]) + } + func testSwiftVersionFileWithNoConfigFile() throws { var errors = [String]() diff --git a/Tests/Rules/FileHeaderTests.swift b/Tests/Rules/FileHeaderTests.swift index 781db84a..f1d6accd 100644 --- a/Tests/Rules/FileHeaderTests.swift +++ b/Tests/Rules/FileHeaderTests.swift @@ -728,6 +728,42 @@ final class FileHeaderTests: XCTestCase { let options = FormatOptions(fileHeader: "// {file}.", fileInfo: FileInfo()) XCTAssertThrowsError(try format(input, rules: [.fileHeader], options: options)) } + + func testFileHeaderWithFilePathButNoCreationDate() { + let input = """ + let foo = bar + """ + let output = """ + // File: test.swift + + let foo = bar + """ + let fileInfo = FileInfo(filePath: "/path/to/test.swift", creationDate: nil) + let options = FormatOptions(fileHeader: "// File: {file}", fileInfo: fileInfo) + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testFileHeaderWithFilePathButNoCreationDateDoesNotUseCreatedPlaceholder() { + let input = """ + let foo = bar + """ + let fileInfo = FileInfo(filePath: "/path/to/test.swift", creationDate: nil) + let options = FormatOptions(fileHeader: "// Created: {created}", fileInfo: fileInfo) + XCTAssertThrowsError(try format(input, rules: [.fileHeader], options: options)) + } + + func testFileHeaderWithExistingHeaderAndNoCreationDate() { + let input = """ + // Existing header + // Created on 2020-01-01 + + let foo = bar + """ + let fileInfo = FileInfo(filePath: "/path/to/test.swift", creationDate: nil) + let options = FormatOptions(fileHeader: "// New header\n// Created: {created}", fileInfo: fileInfo) + // When creation date is unavailable and template uses {created}, throws error even if file has existing header + XCTAssertThrowsError(try format(input, rules: [.fileHeader], options: options)) + } } private enum TestDateFormat: String {