From 152355e36f97ef5cf1c420181237f2e89e653b28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danny=20M=C3=B6sch?= Date: Wed, 25 Dec 2024 23:33:33 +0100 Subject: [PATCH] Improve performance of excluded files filter (#5157) The current algorithm is like "collect all included files and subtract all excluded files". Collecting all included and all excluded files relies on the file system. This can become slow when the patterns used to exclude files resolve to a large number of files. The new approach only collects all lintable files and checks them against the exclude patterns. This can be done by in-memory string-regex-match and does therefore not require file system accesses. --- BUILD | 1 + CHANGELOG.md | 4 + MODULE.bazel | 1 + Package.resolved | 9 +++ Package.swift | 2 + .../Extensions/String+SwiftLint.swift | 9 +++ .../Configuration+CommandLine.swift | 23 +++--- .../Configuration+LintableFiles.swift | 80 +++++++------------ Source/SwiftLintFramework/Helpers/Glob.swift | 23 ++++++ Tests/IntegrationTests/IntegrationTests.swift | 4 +- .../ConfigurationTests.swift | 37 ++++----- Tests/SwiftLintFrameworkTests/GlobTests.swift | 27 +++++++ bazel/repos.bzl | 7 ++ tools/oss-check | 14 ++-- 14 files changed, 145 insertions(+), 96 deletions(-) diff --git a/BUILD b/BUILD index 453ce4e04..bf7e15a9e 100644 --- a/BUILD +++ b/BUILD @@ -89,6 +89,7 @@ swift_library( ":SourceKittenFramework.wrapper", "@sourcekitten_com_github_jpsim_yams//:Yams", "@swiftlint_com_github_scottrhoyt_swifty_text_table//:SwiftyTextTable", + "@com_github_ileitch_swift-filename-matcher//:FilenameMatcher" ] + select({ "@platforms//os:linux": ["@com_github_krzyzanowskim_cryptoswift//:CryptoSwift"], "//conditions:default": [":DyldWarningWorkaround"], diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bc4d8264..b575048de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,10 @@ is enabled by default. [SimplyDanny](https://github.com/SimplyDanny) +* Improve performance when exclude patterns resolve to a large set of files. + [SimplyDanny](https://github.com/SimplyDanny) + [#5018](https://github.com/realm/SwiftLint/issues/5018) + #### Bug Fixes * Ignore super calls with trailing closures in `unneeded_override` rule. diff --git a/MODULE.bazel b/MODULE.bazel index ff17e604d..a016dbbe0 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -14,6 +14,7 @@ bazel_dep(name = "sourcekitten", version = "0.36.0", repo_name = "com_github_jps bazel_dep(name = "swift-syntax", version = "600.0.0", repo_name = "SwiftSyntax") bazel_dep(name = "swift_argument_parser", version = "1.3.1.1", repo_name = "sourcekitten_com_github_apple_swift_argument_parser") bazel_dep(name = "yams", version = "5.1.3", repo_name = "sourcekitten_com_github_jpsim_yams") +bazel_dep(name = "swift-filename-matcher", version = "2.0.0", repo_name = "com_github_ileitch_swift-filename-matcher") swiftlint_repos = use_extension("//bazel:repos.bzl", "swiftlint_repos_bzlmod") use_repo( diff --git a/Package.resolved b/Package.resolved index baa9895fe..b5cb643c4 100644 --- a/Package.resolved +++ b/Package.resolved @@ -36,6 +36,15 @@ "version" : "1.3.1" } }, + { + "identity" : "swift-filename-matcher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ileitch/swift-filename-matcher", + "state" : { + "revision" : "516ff95f6a06c7a9eff8e944e989c7af076c5cdb", + "version" : "2.0.0" + } + }, { "identity" : "swift-syntax", "kind" : "remoteSourceControl", diff --git a/Package.swift b/Package.swift index 2e4cb6dde..e7e7ea10b 100644 --- a/Package.swift +++ b/Package.swift @@ -36,6 +36,7 @@ let package = Package( .package(url: "https://github.com/scottrhoyt/SwiftyTextTable.git", from: "0.9.0"), .package(url: "https://github.com/JohnSundell/CollectionConcurrencyKit.git", from: "0.2.0"), .package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", .upToNextMinor(from: "1.8.4")), + .package(url: "https://github.com/ileitch/swift-filename-matcher", .upToNextMinor(from: "2.0.0")), ], targets: [ .plugin( @@ -85,6 +86,7 @@ let package = Package( .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), .product(name: "SwiftyTextTable", package: "SwiftyTextTable"), .product(name: "Yams", package: "Yams"), + .product(name: "FilenameMatcher", package: "swift-filename-matcher"), "SwiftLintCoreMacros", ], swiftSettings: swiftFeatures + strictConcurrency diff --git a/Source/SwiftLintCore/Extensions/String+SwiftLint.swift b/Source/SwiftLintCore/Extensions/String+SwiftLint.swift index 49839438b..73d93adf9 100644 --- a/Source/SwiftLintCore/Extensions/String+SwiftLint.swift +++ b/Source/SwiftLintCore/Extensions/String+SwiftLint.swift @@ -68,11 +68,20 @@ public extension String { /// Returns a new string, converting the path to a canonical absolute path. /// + /// > Important: This method might use an incorrect working directory internally. This can cause test failures + /// in Bazel builds but does not seem to cause trouble in production. + /// /// - returns: A new `String`. func absolutePathStandardized() -> String { bridge().absolutePathRepresentation().bridge().standardizingPath } + /// Like ``absolutePathStandardized()`` but with the working directory that's used everywhere else. + var normalized: String { + let cwd = FileManager.default.currentDirectoryPath.bridge().standardizingPath + return bridge().absolutePathRepresentation(rootDirectory: cwd) + } + var isFile: Bool { if self.isEmpty { return false diff --git a/Source/SwiftLintFramework/Configuration+CommandLine.swift b/Source/SwiftLintFramework/Configuration+CommandLine.swift index c24f5c9c5..987542a5b 100644 --- a/Source/SwiftLintFramework/Configuration+CommandLine.swift +++ b/Source/SwiftLintFramework/Configuration+CommandLine.swift @@ -257,15 +257,12 @@ extension Configuration { guard options.forceExclude else { return files } - let scriptInputPaths = files.compactMap(\.path) - - if options.useExcludingByPrefix { - return filterExcludedPathsByPrefix(in: scriptInputPaths) - .map(SwiftLintFile.init(pathDeferringReading:)) - } - return filterExcludedPaths(excludedPaths(), in: scriptInputPaths) - .map(SwiftLintFile.init(pathDeferringReading:)) + return ( + visitor.options.useExcludingByPrefix + ? filterExcludedPathsByPrefix(in: scriptInputPaths) + : filterExcludedPaths(in: scriptInputPaths) + ).map(SwiftLintFile.init(pathDeferringReading:)) } if !options.quiet { let filesInfo: String @@ -277,14 +274,12 @@ extension Configuration { queuedPrintError("\(options.capitalizedVerb) Swift files \(filesInfo)") } - let excludeLintableFilesBy = options.useExcludingByPrefix - ? Configuration.ExcludeBy.prefix - : .paths(excludedPaths: excludedPaths()) - return options.paths.flatMap { + return visitor.options.paths.flatMap { self.lintableFiles( inPath: $0, - forceExclude: options.forceExclude, - excludeBy: excludeLintableFilesBy) + forceExclude: visitor.options.forceExclude, + excludeByPrefix: visitor.options.useExcludingByPrefix + ) } } diff --git a/Source/SwiftLintFramework/Configuration/Configuration+LintableFiles.swift b/Source/SwiftLintFramework/Configuration/Configuration+LintableFiles.swift index 30ea7be97..67dc6d00c 100644 --- a/Source/SwiftLintFramework/Configuration/Configuration+LintableFiles.swift +++ b/Source/SwiftLintFramework/Configuration/Configuration+LintableFiles.swift @@ -1,25 +1,21 @@ import Foundation extension Configuration { - public enum ExcludeBy { - case prefix - case paths(excludedPaths: [String]) - } - // MARK: Lintable Paths + /// Returns the files that can be linted by SwiftLint in the specified parent path. /// /// - parameter path: The parent path in which to search for lintable files. Can be a directory or a /// file. /// - parameter forceExclude: Whether or not excludes defined in this configuration should be applied even if /// `path` is an exact match. - /// - parameter excludeByPrefix: Whether or not uses excluding by prefix algorithm. + /// - parameter excludeByPrefix: Whether or not it uses the exclude-by-prefix algorithm. /// /// - returns: Files to lint. public func lintableFiles(inPath path: String, forceExclude: Bool, - excludeBy: ExcludeBy) -> [SwiftLintFile] { - lintablePaths(inPath: path, forceExclude: forceExclude, excludeBy: excludeBy) + excludeByPrefix: Bool) -> [SwiftLintFile] { + lintablePaths(inPath: path, forceExclude: forceExclude, excludeByPrefix: excludeByPrefix) .compactMap(SwiftLintFile.init(pathDeferringReading:)) } @@ -29,24 +25,21 @@ extension Configuration { /// file. /// - parameter forceExclude: Whether or not excludes defined in this configuration should be applied even if /// `path` is an exact match. - /// - parameter excludeByPrefix: Whether or not uses excluding by prefix algorithm. + /// - parameter excludeByPrefix: Whether or not it uses the exclude-by-prefix algorithm. /// - parameter fileManager: The lintable file manager to use to search for lintable files. /// /// - returns: Paths for files to lint. internal func lintablePaths( inPath path: String, forceExclude: Bool, - excludeBy: ExcludeBy, + excludeByPrefix: Bool, fileManager: some LintableFileManager = FileManager.default ) -> [String] { if fileManager.isFile(atPath: path) { if forceExclude { - switch excludeBy { - case .prefix: - return filterExcludedPathsByPrefix(in: [path.absolutePathStandardized()]) - case .paths(let excludedPaths): - return filterExcludedPaths(excludedPaths, in: [path.absolutePathStandardized()]) - } + return excludeByPrefix + ? filterExcludedPathsByPrefix(in: [path.normalized]) + : filterExcludedPaths(in: [path.normalized]) } // If path is a file and we're not forcing excludes, skip filtering with excluded/included paths return [path] @@ -57,35 +50,29 @@ extension Configuration { .flatMap(Glob.resolveGlob) .parallelFlatMap { fileManager.filesToLint(inPath: $0, rootDirectory: rootDirectory) } - switch excludeBy { - case .prefix: - return filterExcludedPathsByPrefix(in: pathsForPath, includedPaths) - case .paths(let excludedPaths): - return filterExcludedPaths(excludedPaths, in: pathsForPath, includedPaths) - } + return excludeByPrefix + ? filterExcludedPathsByPrefix(in: pathsForPath + includedPaths) + : filterExcludedPaths(in: pathsForPath + includedPaths) } /// Returns an array of file paths after removing the excluded paths as defined by this configuration. /// - /// - parameter fileManager: The lintable file manager to use to expand the excluded paths into all matching paths. - /// - parameter paths: The input paths to filter. + /// - parameter paths: The input paths to filter. /// /// - returns: The input paths after removing the excluded paths. - public func filterExcludedPaths( - _ excludedPaths: [String], - in paths: [String]... - ) -> [String] { - let allPaths = paths.flatMap { $0 } + public func filterExcludedPaths(in paths: [String]) -> [String] { #if os(Linux) - let result = NSMutableOrderedSet(capacity: allPaths.count) - result.addObjects(from: allPaths) + let result = NSMutableOrderedSet(capacity: paths.count) + result.addObjects(from: paths) #else - let result = NSMutableOrderedSet(array: allPaths) + let result = NSMutableOrderedSet(array: paths) #endif - - result.minusSet(Set(excludedPaths)) - // swiftlint:disable:next force_cast - return result.map { $0 as! String } + let exclusionPatterns = self.excludedPaths.flatMap { + Glob.createFilenameMatchers(root: rootDirectory, pattern: $0) + } + return result.array + .parallelCompactMap { exclusionPatterns.anyMatch(filename: $0 as! String) ? nil : $0 as? String } + // swiftlint:disable:previous force_cast } /// Returns the file paths that are excluded by this configuration using filtering by absolute path prefix. @@ -94,25 +81,12 @@ extension Configuration { /// algorithm `filterExcludedPaths`. /// /// - returns: The input paths after removing the excluded paths. - public func filterExcludedPathsByPrefix(in paths: [String]...) -> [String] { - let allPaths = paths.flatMap { $0 } + public func filterExcludedPathsByPrefix(in paths: [String]) -> [String] { let excludedPaths = self.excludedPaths - .parallelFlatMap { @Sendable in Glob.resolveGlob($0) } - .map { $0.absolutePathStandardized() } - return allPaths.filter { path in + .parallelFlatMap { Glob.resolveGlob($0) } + .map(\.normalized) + return paths.filter { path in !excludedPaths.contains { path.hasPrefix($0) } } } - - /// Returns the file paths that are excluded by this configuration after expanding them using the specified file - /// manager. - /// - /// - parameter fileManager: The file manager to get child paths in a given parent location. - /// - /// - returns: The expanded excluded file paths. - public func excludedPaths(fileManager: some LintableFileManager = FileManager.default) -> [String] { - excludedPaths - .flatMap(Glob.resolveGlob) - .parallelFlatMap { fileManager.filesToLint(inPath: $0, rootDirectory: rootDirectory) } - } } diff --git a/Source/SwiftLintFramework/Helpers/Glob.swift b/Source/SwiftLintFramework/Helpers/Glob.swift index 4ef3cfbac..9ef79f1ca 100644 --- a/Source/SwiftLintFramework/Helpers/Glob.swift +++ b/Source/SwiftLintFramework/Helpers/Glob.swift @@ -1,3 +1,4 @@ +import FilenameMatcher import Foundation #if os(Linux) @@ -36,6 +37,28 @@ struct Glob { .map { $0.absolutePathStandardized() } } + static func createFilenameMatchers(root: String, pattern: String) -> [FilenameMatcher] { + var absolutPathPattern = pattern + if !pattern.starts(with: root) { + // If the root is not already part of the pattern, prepend it. + absolutPathPattern = root + (root.hasSuffix("/") ? "" : "/") + absolutPathPattern + } + if pattern.hasSuffix(".swift") || pattern.hasSuffix("/**") { + // Suffix is already well defined. + return [FilenameMatcher(pattern: absolutPathPattern)] + } + if pattern.hasSuffix("/") { + // Matching all files in the folder. + return [FilenameMatcher(pattern: absolutPathPattern + "**")] + } + // The pattern could match files in the last folder in the path or all contained files if the last component + // represents folders. + return [ + FilenameMatcher(pattern: absolutPathPattern), + FilenameMatcher(pattern: absolutPathPattern + "/**"), + ] + } + // MARK: Private private static func expandGlobstar(pattern: String) -> [String] { diff --git a/Tests/IntegrationTests/IntegrationTests.swift b/Tests/IntegrationTests/IntegrationTests.swift index c4830a3ed..fcf3e561d 100644 --- a/Tests/IntegrationTests/IntegrationTests.swift +++ b/Tests/IntegrationTests/IntegrationTests.swift @@ -19,7 +19,7 @@ final class IntegrationTests: SwiftLintTestCase { let swiftFiles = config.lintableFiles( inPath: "", forceExclude: false, - excludeBy: .paths(excludedPaths: config.excludedPaths())) + excludeByPrefix: false) XCTAssert( swiftFiles.contains(where: { #filePath.bridge().absolutePathRepresentation() == $0.path }), "current file should be included" @@ -40,7 +40,7 @@ final class IntegrationTests: SwiftLintTestCase { let swiftFiles = config.lintableFiles( inPath: "", forceExclude: false, - excludeBy: .paths(excludedPaths: config.excludedPaths())) + excludeByPrefix: false) let storage = RuleStorage() let corrections = swiftFiles.parallelFlatMap { Linter(file: $0, configuration: config).collect(into: storage).correct(using: storage) diff --git a/Tests/SwiftLintFrameworkTests/ConfigurationTests.swift b/Tests/SwiftLintFrameworkTests/ConfigurationTests.swift index f88852c94..2fa222e1a 100644 --- a/Tests/SwiftLintFrameworkTests/ConfigurationTests.swift +++ b/Tests/SwiftLintFrameworkTests/ConfigurationTests.swift @@ -288,10 +288,9 @@ final class ConfigurationTests: SwiftLintTestCase { excludedPaths: ["directory/excluded", "directory/ExcludedFile.swift"] ) - let excludedPaths = configuration.excludedPaths(fileManager: fileManager) let paths = configuration.lintablePaths(inPath: "", forceExclude: false, - excludeBy: .paths(excludedPaths: excludedPaths), + excludeByPrefix: false, fileManager: fileManager) XCTAssertEqual(["directory/File1.swift", "directory/File2.swift"].absolutePathsStandardized(), paths) } @@ -299,10 +298,9 @@ final class ConfigurationTests: SwiftLintTestCase { func testForceExcludesFile() { let fileManager = TestFileManager() let configuration = Configuration(excludedPaths: ["directory/ExcludedFile.swift"]) - let excludedPaths = configuration.excludedPaths(fileManager: fileManager) let paths = configuration.lintablePaths(inPath: "directory/ExcludedFile.swift", forceExclude: true, - excludeBy: .paths(excludedPaths: excludedPaths), + excludeByPrefix: false, fileManager: fileManager) XCTAssertEqual([], paths) } @@ -311,10 +309,9 @@ final class ConfigurationTests: SwiftLintTestCase { let fileManager = TestFileManager() let configuration = Configuration(includedPaths: ["directory"], excludedPaths: ["directory/ExcludedFile.swift", "directory/excluded"]) - let excludedPaths = configuration.excludedPaths(fileManager: fileManager) let paths = configuration.lintablePaths(inPath: "", forceExclude: true, - excludeBy: .paths(excludedPaths: excludedPaths), + excludeByPrefix: false, fileManager: fileManager) XCTAssertEqual(["directory/File1.swift", "directory/File2.swift"].absolutePathsStandardized(), paths) } @@ -322,10 +319,9 @@ final class ConfigurationTests: SwiftLintTestCase { func testForceExcludesDirectory() { let fileManager = TestFileManager() let configuration = Configuration(excludedPaths: ["directory/excluded", "directory/ExcludedFile.swift"]) - let excludedPaths = configuration.excludedPaths(fileManager: fileManager) let paths = configuration.lintablePaths(inPath: "directory", forceExclude: true, - excludeBy: .paths(excludedPaths: excludedPaths), + excludeByPrefix: false, fileManager: fileManager) XCTAssertEqual(["directory/File1.swift", "directory/File2.swift"].absolutePathsStandardized(), paths) } @@ -333,19 +329,17 @@ final class ConfigurationTests: SwiftLintTestCase { func testForceExcludesDirectoryThatIsNotInExcludedButHasChildrenThatAre() { let fileManager = TestFileManager() let configuration = Configuration(excludedPaths: ["directory/excluded", "directory/ExcludedFile.swift"]) - let excludedPaths = configuration.excludedPaths(fileManager: fileManager) let paths = configuration.lintablePaths(inPath: "directory", forceExclude: true, - excludeBy: .paths(excludedPaths: excludedPaths), + excludeByPrefix: false, fileManager: fileManager) XCTAssertEqual(["directory/File1.swift", "directory/File2.swift"].absolutePathsStandardized(), paths) } func testLintablePaths() { - let excluded = Configuration.default.excludedPaths(fileManager: TestFileManager()) let paths = Configuration.default.lintablePaths(inPath: Mock.Dir.level0, forceExclude: false, - excludeBy: .paths(excludedPaths: excluded)) + excludeByPrefix: false) let filenames = paths.map { $0.bridge().lastPathComponent }.sorted() let expectedFilenames = [ "DirectoryLevel1.swift", @@ -361,7 +355,7 @@ final class ConfigurationTests: SwiftLintTestCase { let configuration = Configuration(includedPaths: ["**/Level2"]) let paths = configuration.lintablePaths(inPath: Mock.Dir.level0, forceExclude: true, - excludeBy: .paths(excludedPaths: configuration.excludedPaths)) + excludeByPrefix: false) let filenames = paths.map { $0.bridge().lastPathComponent }.sorted() let expectedFilenames = ["Level2.swift", "Level3.swift"] @@ -374,10 +368,9 @@ final class ConfigurationTests: SwiftLintTestCase { excludedPaths: [Mock.Dir.level3.stringByAppendingPathComponent("*.swift")] ) - let excludedPaths = configuration.excludedPaths() let lintablePaths = configuration.lintablePaths(inPath: "", forceExclude: false, - excludeBy: .paths(excludedPaths: excludedPaths)) + excludeByPrefix: false) XCTAssertEqual(lintablePaths, []) } @@ -492,7 +485,7 @@ extension ConfigurationTests { ) let paths = configuration.lintablePaths(inPath: Mock.Dir.level0, forceExclude: false, - excludeBy: .prefix) + excludeByPrefix: true) let filenames = paths.map { $0.bridge().lastPathComponent } XCTAssertEqual(filenames, ["Level2.swift"]) } @@ -502,7 +495,7 @@ extension ConfigurationTests { let configuration = Configuration(excludedPaths: ["Level1/Level2/Level3/Level3.swift"]) let paths = configuration.lintablePaths(inPath: "Level1/Level2/Level3/Level3.swift", forceExclude: true, - excludeBy: .prefix) + excludeByPrefix: true) XCTAssertEqual([], paths) } @@ -512,7 +505,7 @@ extension ConfigurationTests { excludedPaths: ["Level1/Level1.swift"]) let paths = configuration.lintablePaths(inPath: "Level1", forceExclude: true, - excludeBy: .prefix) + excludeByPrefix: true) let filenames = paths.map { $0.bridge().lastPathComponent }.sorted() XCTAssertEqual(["Level2.swift", "Level3.swift"], filenames) } @@ -526,7 +519,7 @@ extension ConfigurationTests { ) let paths = configuration.lintablePaths(inPath: ".", forceExclude: true, - excludeBy: .prefix) + excludeByPrefix: true) let filenames = paths.map { $0.bridge().lastPathComponent }.sorted() XCTAssertEqual(["Level0.swift", "Level1.swift"], filenames) } @@ -540,7 +533,7 @@ extension ConfigurationTests { ) let paths = configuration.lintablePaths(inPath: ".", forceExclude: true, - excludeBy: .prefix) + excludeByPrefix: true) let filenames = paths.map { $0.bridge().lastPathComponent } XCTAssertEqual(["Level0.swift"], filenames) } @@ -552,7 +545,7 @@ extension ConfigurationTests { excludedPaths: ["Level1/*/*.swift", "Level1/*/*/*.swift"]) let paths = configuration.lintablePaths(inPath: "Level1", forceExclude: false, - excludeBy: .prefix) + excludeByPrefix: true) let filenames = paths.map { $0.bridge().lastPathComponent }.sorted() XCTAssertEqual(filenames, ["Level1.swift"]) } @@ -606,7 +599,7 @@ extension ConfigurationTests { private extension Sequence where Element == String { func absolutePathsStandardized() -> [String] { - map { $0.absolutePathStandardized() } + map(\.normalized) } } diff --git a/Tests/SwiftLintFrameworkTests/GlobTests.swift b/Tests/SwiftLintFrameworkTests/GlobTests.swift index 04730dc09..81b53677b 100644 --- a/Tests/SwiftLintFrameworkTests/GlobTests.swift +++ b/Tests/SwiftLintFrameworkTests/GlobTests.swift @@ -82,4 +82,31 @@ final class GlobTests: SwiftLintTestCase { let files = Glob.resolveGlob(mockPath.stringByAppendingPathComponent("**/*.swift")) XCTAssertEqual(files.sorted(), expectedFiles.sorted()) } + + func testCreateFilenameMatchers() { + func assertGlobMatch(root: String, pattern: String, filename: String) { + let matchers = Glob.createFilenameMatchers(root: root, pattern: pattern) + XCTAssert(matchers.anyMatch(filename: filename)) + } + + assertGlobMatch(root: "/a/b/", pattern: "c/*.swift", filename: "/a/b/c/d.swift") + assertGlobMatch(root: "/a", pattern: "**/*.swift", filename: "/a/b/c/d.swift") + assertGlobMatch(root: "/a", pattern: "**/*.swift", filename: "/a/b.swift") + assertGlobMatch(root: "", pattern: "**/*.swift", filename: "/a/b.swift") + assertGlobMatch(root: "", pattern: "a/**/b.swift", filename: "a/b.swift") + assertGlobMatch(root: "", pattern: "a/**/b.swift", filename: "a/c/b.swift") + assertGlobMatch(root: "", pattern: "**/*.swift", filename: "a.swift") + assertGlobMatch(root: "", pattern: "a/**/*.swift", filename: "a/b/c.swift") + assertGlobMatch(root: "", pattern: "a/**/*.swift", filename: "a/b.swift") + assertGlobMatch(root: "/a/b", pattern: "/a/b/c/*.swift", filename: "/a/b/c/d.swift") + assertGlobMatch(root: "/a/", pattern: "/a/b/c/*.swift", filename: "/a/b/c/d.swift") + + assertGlobMatch(root: "", pattern: "/a/b/c", filename: "/a/b/c/d.swift") + assertGlobMatch(root: "", pattern: "/a/b/c/", filename: "/a/b/c/d.swift") + assertGlobMatch(root: "", pattern: "/a/b/c/*.swift", filename: "/a/b/c/d.swift") + assertGlobMatch(root: "", pattern: "/d.swift/*.swift", filename: "/d.swift/e.swift") + assertGlobMatch(root: "", pattern: "/a/**", filename: "/a/b/c/d.swift") + assertGlobMatch(root: "", pattern: "**/*Test*", filename: "/a/b/c/MyTest2.swift") + assertGlobMatch(root: "", pattern: "**/*Test*", filename: "/a/b/MyTests/c.swift") + } } diff --git a/bazel/repos.bzl b/bazel/repos.bzl index 47f2aa9d3..53d99188e 100644 --- a/bazel/repos.bzl +++ b/bazel/repos.bzl @@ -64,6 +64,13 @@ def swiftlint_repos(bzlmod = False): url = "https://github.com/krzyzanowskim/CryptoSwift/archive/refs/tags/1.8.4.tar.gz", ) + http_archive( + name = "com_github_ileitch_swift-filename-matcher", + sha256 = "1adbb1eb042910f996689827f7dee217bebf7c5178f34178bcfe468b5b3268a2", + strip_prefix = "swift-filename-matcher-2.0.0", + url = "https://github.com/ileitch/swift-filename-matcher/archive/refs/tags/2.0.0.tar.gz", + ) + def _swiftlint_repos_bzlmod(_): swiftlint_repos(bzlmod = True) diff --git a/tools/oss-check b/tools/oss-check index 77a70c836..f1d0a05ca 100755 --- a/tools/oss-check +++ b/tools/oss-check @@ -45,6 +45,7 @@ end.parse! class Repo attr_accessor :name attr_accessor :github_location + attr_accessor :keep_config attr_accessor :config attr_accessor :commit_hash attr_accessor :branch_exit_value @@ -52,9 +53,10 @@ class Repo attr_accessor :main_exit_value attr_accessor :main_duration - def initialize(name, github_location, config=nil) + def initialize(name, github_location, keep_config=false, config=nil) @name = name @github_location = github_location + @keep_config = keep_config @config = config end @@ -140,7 +142,9 @@ def setup_repos puts "Cloning #{repo}" perform("git clone #{repo.git_url} --depth 1 #{dir} 2> /dev/null") swiftlint_config = "#{dir}/.swiftlint.yml" - FileUtils.rm_rf(swiftlint_config) + if !repo.keep_config + FileUtils.rm_rf(swiftlint_config) + end if repo.config File.open(swiftlint_config, 'a') do |file| file.puts(repo.config) @@ -292,7 +296,7 @@ end @repos = [ Repo.new('Aerial', 'JohnCoates/Aerial'), Repo.new('Alamofire', 'Alamofire/Alamofire'), - Repo.new('Brave', 'brave/brave-ios'), + Repo.new('Brave', 'brave/brave-ios', true), Repo.new('DuckDuckGo', 'duckduckgo/iOS'), Repo.new('Firefox', 'mozilla-mobile/firefox-ios'), Repo.new('Kickstarter', 'kickstarter/ios-oss'), @@ -303,9 +307,9 @@ end Repo.new('Quick', 'Quick/Quick'), Repo.new('Realm', 'realm/realm-swift'), Repo.new('Sourcery', 'krzysztofzablocki/Sourcery'), - Repo.new('Swift', 'apple/swift', 'included: stdlib'), + Repo.new('Swift', 'apple/swift', false, 'included: stdlib'), Repo.new('VLC', 'videolan/vlc-ios'), - Repo.new('Wire', 'wireapp/wire-ios', 'excluded: wire-ios/Templates/Viper'), + Repo.new('Wire', 'wireapp/wire-ios', false, 'excluded: wire-ios/Templates/Viper'), Repo.new('WordPress', 'wordpress-mobile/WordPress-iOS') ]