Skip to content

Commit d3c336c

Browse files
committed
Use realpath from the tests to do symlink resolution.
1 parent 01094d1 commit d3c336c

File tree

2 files changed

+38
-52
lines changed

2 files changed

+38
-52
lines changed

Sources/SwiftFormat/Utilities/FileIterator.swift

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -168,32 +168,41 @@ public struct FileIterator: Sequence, IteratorProtocol {
168168
/// exist or is not a supported file type. If `followSymlinks` is `true`, the returned URL may be
169169
/// different from the given URL; otherwise, it will be the same.
170170
private func fileAndType(at url: URL, followSymlinks: Bool) -> (URL, FileAttributeType)? {
171-
func typeOfFile(at url: URL) -> FileAttributeType? {
172-
// We cannot use `URL.resourceValues(forKeys:)` here because it appears to behave incorrectly on
173-
// Linux.
174-
return try? FileManager.default.attributesOfItem(atPath: url.path)[.type] as? FileAttributeType
171+
var url = url
172+
if followSymlinks {
173+
url = realPath(of: url)
175174
}
176175

177-
guard var fileType = typeOfFile(at: url) else {
176+
// We cannot use `URL.resourceValues(forKeys:)` here because it appears to behave incorrectly on
177+
// Linux.
178+
guard
179+
let type =
180+
try? FileManager.default.attributesOfItem(atPath: url.path)[.type] as? FileAttributeType
181+
else {
178182
return nil
179183
}
180184

181-
// We would use `standardizedFileURL.path` here as we do in the iterator above to ensure that
182-
// path components like `.` and `..` are resolved, but the standardized URLs returned by
183-
// Foundation pre-Swift-6.0 resolve symlinks. This causes the file type of a URL and its
184-
// standardized path to not match.
185-
var visited: Set<String> = [url.absoluteString]
186-
var url = url
187-
while followSymlinks && fileType == .typeSymbolicLink,
188-
let destination = try? FileManager.default.destinationOfSymbolicLink(atPath: url.path)
189-
{
190-
url = URL(fileURLWithPath: destination, relativeTo: url)
191-
// If this URL is in the visited set, we must have a symlink cycle. Ignore it gracefully.
192-
guard !visited.contains(url.absoluteString), let newType = typeOfFile(at: url) else {
193-
return nil
185+
return (url, type)
186+
}
187+
188+
/// Assuming the given URL is a file URL, resolves all symlinks in its path.
189+
///
190+
/// - Note: We need this because `URL.resolvingSymlinksInPath()` not only resolves symlinks but also standardizes the
191+
/// path by stripping away `private` prefixes.
192+
@_spi(Internal)
193+
public func realPath(of url: URL) -> URL {
194+
#if canImport(Darwin)
195+
return url.path.withCString { path in
196+
guard let realpath = Darwin.realpath(path, nil) else {
197+
return url
194198
}
195-
visited.insert(url.absoluteString)
196-
fileType = newType
199+
let result = URL(fileURLWithPath: String(cString: realpath))
200+
free(realpath)
201+
return result
197202
}
198-
return (url, fileType)
203+
#else
204+
// Non-Darwin platforms don't have the `/private` stripping issue, so we can just use `self.resolvingSymlinksInPath`
205+
// here.
206+
return url.resolvingSymlinksInPath()
207+
#endif
199208
}

Tests/SwiftFormatTests/Utilities/FileIteratorTests.swift

Lines changed: 8 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,18 @@
11
@_spi(Internal) @_spi(Testing) import SwiftFormat
22
import XCTest
33

4-
extension URL {
5-
/// Assuming this is a file URL, resolves all symlinks in the path.
6-
///
7-
/// - Note: We need this because `URL.resolvingSymlinksInPath()` not only resolves symlinks but also standardizes the
8-
/// path by stripping away `private` prefixes. Since sourcekitd is not performing this standardization, using
9-
/// `resolvingSymlinksInPath` can lead to slightly mismatched URLs between the sourcekit-lsp response and the test
10-
/// assertion.
11-
fileprivate var realpath: URL {
12-
#if canImport(Darwin)
13-
return self.path.withCString { path in
14-
guard let realpath = Darwin.realpath(path, nil) else {
15-
return self
16-
}
17-
let result = URL(fileURLWithPath: String(cString: realpath))
18-
free(realpath)
19-
return result
20-
}
21-
#else
22-
// Non-Darwin platforms don't have the `/private` stripping issue, so we can just use `self.resolvingSymlinksInPath`
23-
// here.
24-
return self.resolvingSymlinksInPath()
25-
#endif
26-
}
27-
}
28-
294
final class FileIteratorTests: XCTestCase {
305
private var tmpdir: URL!
316

327
override func setUpWithError() throws {
33-
tmpdir = try FileManager.default.url(
34-
for: .itemReplacementDirectory,
35-
in: .userDomainMask,
36-
appropriateFor: FileManager.default.temporaryDirectory,
37-
create: true
38-
).realpath
8+
tmpdir = realPath(
9+
of: try FileManager.default.url(
10+
for: .itemReplacementDirectory,
11+
in: .userDomainMask,
12+
appropriateFor: FileManager.default.temporaryDirectory,
13+
create: true
14+
)
15+
)
3916

4017
// Create a simple file tree used by the tests below.
4118
try touch("project/real1.swift")

0 commit comments

Comments
 (0)