diff --git a/CHANGELOG.md b/CHANGELOG.md index 6aae9e341..923a40a78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ ##### Bug Fixes -- None. +- Fix index/report exclude regression. ## 2.12.2 (2023-03-19) diff --git a/Sources/Shared/Configuration.swift b/Sources/Shared/Configuration.swift index ea3d97dd4..a2c8b3cc6 100644 --- a/Sources/Shared/Configuration.swift +++ b/Sources/Shared/Configuration.swift @@ -321,13 +321,9 @@ public final class Configuration { // MARK: - Private private func buildFilenameMatchers(with patterns: [String]) -> [FilenameMatcher] { + // TODO: respect filesystem case sensitivity. let pwd = FilePath.current.string - - return patterns.map { - let pattern = $0.hasPrefix("/") ? $0 : "\(pwd)/\($0)" - // TODO: respect filesystem case sensitivity. - return FilenameMatcher(pattern: pattern, caseSensitive: false) - } + return patterns.map { FilenameMatcher(relativePattern: $0, to: pwd, caseSensitive: false) } } private func configurationPath(withUserProvided path: FilePath?) throws -> FilePath? { diff --git a/Sources/Shared/Extensions/FilenameMatcher+Extension.swift b/Sources/Shared/Extensions/FilenameMatcher+Extension.swift new file mode 100644 index 000000000..3e6467a48 --- /dev/null +++ b/Sources/Shared/Extensions/FilenameMatcher+Extension.swift @@ -0,0 +1,23 @@ +import Foundation +import FilenameMatcher + +public extension FilenameMatcher { + init(relativePattern: String, to base: String, caseSensitive: Bool) { + let patternComponents = relativePattern.split(separator: "/", omittingEmptySubsequences: false) + let parentTraversalCount = patternComponents.firstIndex { $0 != ".." } ?? 0 + + if parentTraversalCount > base.split(separator: "/").count { + self.init(pattern: "") + return + } + + let baseComponents = base.split(separator: "/", omittingEmptySubsequences: false) + let traversedPattern = patternComponents.dropFirst(parentTraversalCount).joined(separator: "/") + let traversedBaseParts = baseComponents.dropLast(parentTraversalCount) + let traversedBase = traversedBaseParts.joined(separator: "/") + let normalizedBase = traversedBase.hasSuffix("/") ? traversedBase : "\(traversedBase)/" + let shouldPrependPwd = !["/", "*"].contains { relativePattern.hasPrefix($0) } + let pattern = shouldPrependPwd ? "\(normalizedBase)\(traversedPattern)" : traversedPattern + self.init(pattern: pattern, caseSensitive: caseSensitive) + } +} diff --git a/Tests/PeripheryTests/Extensions/FilenameMatcherTests.swift b/Tests/PeripheryTests/Extensions/FilenameMatcherTests.swift new file mode 100644 index 000000000..6e726d68a --- /dev/null +++ b/Tests/PeripheryTests/Extensions/FilenameMatcherTests.swift @@ -0,0 +1,43 @@ +import Foundation +import Shared +import XCTest +import FilenameMatcher + +final class FilenameMatcherTests: XCTestCase { + func testRelativePatterns() { + assertMatch(filename: "/Sources/File.swift", relativeTo: "/", pattern: "/Sources/File.swift") + assertMatch(filename: "/Sources/File.swift", relativeTo: "/", pattern: "Sources/File.swift") + assertMatch(filename: "/Sources/File.swift", relativeTo: "/", pattern: "*/Sources/File.swift") + assertMatch(filename: "/Sources/File.swift", relativeTo: "/", pattern: "**/Sources/File.swift") + + assertMatch(filename: "/a/b/Sources/File.swift", relativeTo: "/", pattern: "*/Sources/File.swift") + assertMatch(filename: "/a/b/Sources/File.swift", relativeTo: "/", pattern: "**/Sources/File.swift") + assertMatch(filename: "/a/b/Sources/File.swift", relativeTo: "/", pattern: "a/**/File.swift") + + assertMatch(filename: "/a/b/Sources/File.swift", relativeTo: "/a", pattern: "*/Sources/File.swift") + assertMatch(filename: "/a/b/Sources/File.swift", relativeTo: "/a", pattern: "**/Sources/File.swift") + assertMatch(filename: "/a/b/c/Sources/File.swift", relativeTo: "/b", pattern: "/**/File.swift") + + assertMatch(filename: "/a/Sources/File.swift", relativeTo: "/a/b", pattern: "../Sources/File.swift") + assertMatch(filename: "/a/Sources/File.swift", relativeTo: "/a/b/c/d", pattern: "../../../Sources/*.swift") + assertMatch(filename: "/a/b/c/d/Sources/File.swift", relativeTo: "/a/b", pattern: "../*/Sources/File.swift") + assertMatch(filename: "/a/b/c/d/Sources/File.swift", relativeTo: "/a/b", pattern: "../**/File.swift") + assertMatch(filename: "/a/b/c/d/Sources/File.swift", relativeTo: "/a/b/c", pattern: "../../**/File.swift") + + assertNotMatch(filename: "/Sources/File.swift", relativeTo: "/", pattern: "/x/**/Sources/File.swift") + assertNotMatch(filename: "/Sources/File.swift", relativeTo: "/", pattern: "../../**/File.swift") + assertNotMatch(filename: "/a/Sources/File.swift", relativeTo: "/", pattern: "/Sources/File.swift") + } + + // MARK: - Private + + private func assertMatch(filename: String, relativeTo base: String, pattern: String, file: StaticString = #file, line: UInt = #line) { + let matcher = FilenameMatcher(relativePattern: pattern, to: base, caseSensitive: false) + XCTAssertTrue(matcher.match(filename: filename), file: file, line: line) + } + + private func assertNotMatch(filename: String, relativeTo base: String, pattern: String, file: StaticString = #file, line: UInt = #line) { + let matcher = FilenameMatcher(relativePattern: pattern, to: base, caseSensitive: false) + XCTAssertFalse(matcher.match(filename: filename), file: file, line: line) + } +}