Skip to content

Commit faa5859

Browse files
authored
Introduce guarded filepath provider for file collection (#6435)
1 parent 9a634bc commit faa5859

File tree

8 files changed

+61
-8
lines changed

8 files changed

+61
-8
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ jobs:
3838
name: SPM, macOS ${{ matrix.macOS }}, Xcode ${{ matrix.xcode }}
3939
runs-on: macos-${{ matrix.macOS }}
4040
strategy:
41+
fail-fast: false
4142
matrix:
4243
include:
4344
- macOS: '14'

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ default.profraw
3939

4040
SwiftLint.xcodeproj
4141
SwiftLint.pkg
42-
*.zip
42+
/*.zip
4343
benchmark_*
4444
docs/
4545
rule_docs/
@@ -73,4 +73,4 @@ oss-check-summary.md
7373

7474

7575
# VS Code
76-
.vscode
76+
.vscode

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020
[SimplyDanny](https://github.com/SimplyDanny)
2121
[#6423](https://github.com/realm/SwiftLint/issues/6423)
2222

23+
* Inform users about files being skipped due to impossible file system representation
24+
instead of crashing.
25+
[SimplyDanny](https://github.com/SimplyDanny)
26+
[#6419](https://github.com/realm/SwiftLint/issues/6419)
27+
2328
* Ignore `override` functions in `async_without_await` rule.
2429
[SimplyDanny](https://github.com/SimplyDanny)
2530
[#6416](https://github.com/realm/SwiftLint/issues/6416)

Package.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ let package = Package(
203203
.testTarget(
204204
name: "IntegrationTests",
205205
dependencies: [
206+
"SwiftLintCore",
206207
"SwiftLintFramework",
207208
"TestHelpers",
208209
],

Source/SwiftLintCore/Extensions/URL+SwiftLint.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@ public extension URL {
55
withUnsafeFileSystemRepresentation { String(cString: $0!) }
66
}
77

8+
var filepathGuarded: String? {
9+
withUnsafeFileSystemRepresentation { ptr in
10+
guard let ptr else {
11+
Issue.genericError(
12+
"File with URL '\(self)' cannot be represented as a file system path; skipping it"
13+
).print()
14+
return nil
15+
}
16+
return String(cString: ptr)
17+
}
18+
}
19+
820
var isSwiftFile: Bool {
921
filepath.isFile && pathExtension == "swift"
1022
}

Source/SwiftLintFramework/Extensions/FileManager+SwiftLint.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ extension FileManager: LintableFileManager, @unchecked @retroactive Sendable {
7171
}
7272

7373
private func collectFiles(atPath absolutePath: URL, excluder: Excluder) -> [String] {
74-
guard let enumerator = enumerator(atPath: absolutePath.filepath) else {
74+
guard let root = absolutePath.filepathGuarded, let enumerator = enumerator(atPath: root) else {
7575
return []
7676
}
7777

@@ -80,7 +80,9 @@ extension FileManager: LintableFileManager, @unchecked @retroactive Sendable {
8080

8181
while let element = enumerator.nextObject() as? String {
8282
let absoluteElementPath = URL(fileURLWithPath: element, relativeTo: absolutePath)
83-
let absoluteStandardizedElementPath = absoluteElementPath.standardized.filepath
83+
guard let absoluteStandardizedElementPath = absoluteElementPath.standardized.filepathGuarded else {
84+
continue
85+
}
8486
if absoluteElementPath.path.isFile {
8587
if absoluteElementPath.pathExtension == "swift",
8688
!excluder.excludes(path: absoluteStandardizedElementPath) {

Tests/IntegrationTests/ConfigPathResolutionTests.swift

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import SwiftLintFramework
33
import TestHelpers
44
import XCTest
55

6-
final class ConfigPathResolutionTests: SwiftLintTestCase {
6+
@testable import SwiftLintCore
7+
8+
final class ConfigPathResolutionTests: SwiftLintTestCase, @unchecked Sendable {
79
private func fixturePath(_ scenario: String) -> URL {
810
URL(fileURLWithPath: #filePath)
911
.deletingLastPathComponent()
@@ -16,10 +18,8 @@ final class ConfigPathResolutionTests: SwiftLintTestCase {
1618
let scenarioPath = fixturePath(scenario).filepath
1719

1820
let previousDir = FileManager.default.currentDirectoryPath
19-
defer {
20-
_ = FileManager.default.changeCurrentDirectoryPath(previousDir)
21-
}
2221
XCTAssert(FileManager.default.changeCurrentDirectoryPath(scenarioPath))
22+
defer { _ = FileManager.default.changeCurrentDirectoryPath(previousDir) }
2323

2424
let config = Configuration(configurationFiles: configFile.map { [$0] } ?? [])
2525
let files = config.lintableFiles(
@@ -169,4 +169,36 @@ final class ConfigPathResolutionTests: SwiftLintTestCase {
169169
.contains("explicit_type_interface")
170170
)
171171
}
172+
173+
#if !os(Windows)
174+
func testUnicodePrivateUseAreaCharacterInPath() async throws {
175+
let fixture = fixturePath("_8_unicode_private_use_area")
176+
177+
let process = Process()
178+
process.executableURL = URL(filePath: "/usr/bin/env", directoryHint: .notDirectory)
179+
process.arguments = ["unzip", "-o", fixture.appending(path: "app.zip").filepath, "-d", fixture.filepath]
180+
try process.run()
181+
process.waitUntilExit()
182+
defer { try? FileManager.default.removeItem(at: fixture.appending(path: "App")) }
183+
184+
if #available(macOS 26, *) {
185+
XCTAssertEqual(
186+
lintableFilePaths(in: "_8_unicode_private_use_area/App"),
187+
["Resources/Settings.bundle/androidx.core:core-bundle.swift"]
188+
)
189+
} else {
190+
let console = await Issue.captureConsole {
191+
XCTAssert(lintableFilePaths(in: "_8_unicode_private_use_area/App").isEmpty)
192+
}
193+
XCTAssert(
194+
console.contains(
195+
"""
196+
error: File with URL 'androidx.core:core-bundle.swift' \
197+
cannot be represented as a file system path; skipping it
198+
"""
199+
)
200+
)
201+
}
202+
}
203+
#endif
172204
}
Binary file not shown.

0 commit comments

Comments
 (0)