From 3c2a46b16f99ea36320c4d58d6b207646ed171e9 Mon Sep 17 00:00:00 2001 From: JP Simard Date: Fri, 11 Mar 2022 10:01:33 -0500 Subject: [PATCH] Use alternate implementation of glob with globstar support Adapted from https://gist.github.com/efirestone/ce01ae109e08772647eb061b3bb387c3 The implementation from Pathos seems buggy. Fixes https://github.com/realm/SwiftLint/issues/3891 --- CHANGELOG.md | 2 + Package.resolved | 9 -- Package.swift | 4 +- Source/SwiftLintFramework/Helpers/Glob.swift | 107 +++++++++++++++++- .../ConfigurationTests.swift | 4 +- Tests/SwiftLintFrameworkTests/GlobTests.swift | 27 +++-- 6 files changed, 121 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a78a95227f..ced9141682 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,9 @@ * Support recursive globs. [funzin](https://github.com/funzin) + [JP Simard](https://github.com/jpsim) [#3789](https://github.com/realm/SwiftLint/issues/3789) + [#3891](https://github.com/realm/SwiftLint/issues/3891) #### Bug Fixes diff --git a/Package.resolved b/Package.resolved index 71b77a1dd7..ba2941ed03 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,15 +1,6 @@ { "object": { "pins": [ - { - "package": "Pathos", - "repositoryURL": "https://github.com/dduan/Pathos", - "state": { - "branch": null, - "revision": "8697a340a25e9974d4bbdee80a4c361c74963c00", - "version": "0.4.2" - } - }, { "package": "SourceKitten", "repositoryURL": "https://github.com/jpsim/SourceKitten.git", diff --git a/Package.swift b/Package.swift index 247fff9fd7..ecdf96f7b1 100644 --- a/Package.swift +++ b/Package.swift @@ -27,7 +27,6 @@ let package = Package( .package(url: "https://github.com/jpsim/SourceKitten.git", from: "0.31.1"), .package(url: "https://github.com/jpsim/Yams.git", from: "4.0.2"), .package(url: "https://github.com/scottrhoyt/SwiftyTextTable.git", from: "0.9.0"), - .package(url: "https://github.com/dduan/Pathos", from: "0.4.2") ] + (addCryptoSwift ? [.package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", .upToNextMinor(from: "1.4.3"))] : []), targets: [ .executableTarget( @@ -42,9 +41,8 @@ let package = Package( name: "SwiftLintFramework", dependencies: [ .product(name: "SourceKittenFramework", package: "SourceKitten"), - "Pathos", "SwiftSyntax", - "Yams" + "Yams", ] + (addCryptoSwift ? ["CryptoSwift"] : []) + (staticSwiftSyntax ? ["lib_InternalSwiftSyntaxParser"] : []) diff --git a/Source/SwiftLintFramework/Helpers/Glob.swift b/Source/SwiftLintFramework/Helpers/Glob.swift index 261d944dbf..e8cd5aa439 100644 --- a/Source/SwiftLintFramework/Helpers/Glob.swift +++ b/Source/SwiftLintFramework/Helpers/Glob.swift @@ -1,5 +1,18 @@ import Foundation -import Pathos + +#if canImport(Darwin) +import Darwin + +private let globFunction = Darwin.glob +#elseif canImport(Glibc) +import Glibc + +private let globFunction = Glibc.glob +#else +#error("Unsupported platform") +#endif + +// Adapted from https://gist.github.com/efirestone/ce01ae109e08772647eb061b3bb387c3 struct Glob { static func resolveGlob(_ pattern: String) -> [String] { @@ -8,14 +21,96 @@ struct Glob { return [pattern] } + defer { isDirectoryCache.removeAll() } + + return expandGlobstar(pattern: pattern) + .reduce(into: [String]()) { paths, pattern in + var globResult = glob_t() + defer { globfree(&globResult) } + + if globFunction(pattern, GLOB_TILDE | GLOB_BRACE | GLOB_MARK, nil, &globResult) == 0 { + paths.append(contentsOf: populateFiles(globResult: globResult)) + } + } + .unique + .sorted() + .map { $0.absolutePathStandardized() } + } + + // MARK: Private + + private static var isDirectoryCache = [String: Bool]() + + private static func expandGlobstar(pattern: String) -> [String] { + guard pattern.contains("**") else { + return [pattern] + } + + var results = [String]() + var parts = pattern.components(separatedBy: "**") + let firstPart = parts.removeFirst() + var lastPart = parts.joined(separator: "**") + + let fileManager = FileManager.default + + var directories: [String] + + let searchPath = firstPart.isEmpty ? fileManager.currentDirectoryPath : firstPart do { - let paths = try Path(pattern).glob() - return try paths.compactMap { path in - try path.absolute().description + directories = try fileManager.subpathsOfDirectory(atPath: searchPath).compactMap { subpath in + let fullPath = firstPart.bridge().appendingPathComponent(subpath) + guard isDirectory(path: fullPath) else { return nil } + return fullPath } } catch { - queuedPrintError(error.localizedDescription) - return [] + directories = [] + queuedPrintError("Error parsing file system item: \(error)") + } + + // Check the base directory for the glob star as well. + directories.insert(firstPart, at: 0) + + // Include the globstar root directory ("dir/") in a pattern like "dir/**" or "dir/**/" + if lastPart.isEmpty { + results.append(firstPart) + lastPart = "*" + } + + for directory in directories { + let partiallyResolvedPattern: String + if directory.isEmpty { + partiallyResolvedPattern = lastPart.starts(with: "/") ? String(lastPart.dropFirst()) : lastPart + } else { + partiallyResolvedPattern = directory.bridge().appendingPathComponent(lastPart) + } + results.append(contentsOf: expandGlobstar(pattern: partiallyResolvedPattern)) + } + + return results + } + + private static func isDirectory(path: String) -> Bool { + if let isDirectory = isDirectoryCache[path] { + return isDirectory + } + + var isDirectoryBool = ObjCBool(false) + var isDirectory = FileManager.default.fileExists(atPath: path, isDirectory: &isDirectoryBool) + isDirectory = isDirectory && isDirectoryBool.boolValue + + isDirectoryCache[path] = isDirectory + + return isDirectory + } + + private static func populateFiles(globResult: glob_t) -> [String] { +#if os(Linux) + let matchCount = globResult.gl_pathc +#else + let matchCount = globResult.gl_matchc +#endif + return (0..