Skip to content

Commit 32d75f6

Browse files
committed
Introduce path ancestry methods for AbsolutePath
This allows checking if a path is an ancestor or descendent of a given path, and deprecates the "contains" method which is confusingly named and may lead readers of the code to incorrectly conclude that a string containment check is being done, rather than a semantic comparison of the actual path components.
1 parent 3cabb66 commit 32d75f6

File tree

2 files changed

+53
-8
lines changed

2 files changed

+53
-8
lines changed

Sources/TSCBasic/Path.swift

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -911,10 +911,42 @@ extension AbsolutePath {
911911
///
912912
/// This method is strictly syntactic and does not access the file system
913913
/// in any way.
914+
@available(*, deprecated, renamed: "isDescendantOfOrEqual(to:)")
914915
public func contains(_ other: AbsolutePath) -> Bool {
915-
return self.components.starts(with: other.components)
916+
return isDescendantOfOrEqual(to: other)
916917
}
917918

919+
/// Returns true if the path is an ancestor of the given path.
920+
///
921+
/// This method is strictly syntactic and does not access the file system
922+
/// in any way.
923+
public func isAncestor(of descendant: AbsolutePath) -> Bool {
924+
return descendant.components.dropLast().starts(with: self.components)
925+
}
926+
927+
/// Returns true if the path is an ancestor of or equal to the given path.
928+
///
929+
/// This method is strictly syntactic and does not access the file system
930+
/// in any way.
931+
public func isAncestorOfOrEqual(to descendant: AbsolutePath) -> Bool {
932+
return descendant.components.starts(with: self.components)
933+
}
934+
935+
/// Returns true if the path is a descendant of the given path.
936+
///
937+
/// This method is strictly syntactic and does not access the file system
938+
/// in any way.
939+
public func isDescendant(of ancestor: AbsolutePath) -> Bool {
940+
return self.components.dropLast().starts(with: ancestor.components)
941+
}
942+
943+
/// Returns true if the path is a descendant of or equal to the given path.
944+
///
945+
/// This method is strictly syntactic and does not access the file system
946+
/// in any way.
947+
public func isDescendantOfOrEqual(to ancestor: AbsolutePath) -> Bool {
948+
return self.components.starts(with: ancestor.components)
949+
}
918950
}
919951

920952
extension PathValidationError: CustomNSError {

Tests/TSCBasicTests/PathTests.swift

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -289,13 +289,26 @@ class PathTests: XCTestCase {
289289
XCTAssertTrue(AbsolutePath("/2.1") >= AbsolutePath("/2"));
290290
}
291291

292-
func testContains() {
293-
XCTAssertTrue(AbsolutePath("/a/b/c/d/e/f").contains(AbsolutePath("/a/b/c/d")))
294-
XCTAssertTrue(AbsolutePath("/a/b/c/d/e/f.swift").contains(AbsolutePath("/a/b/c")))
295-
XCTAssertTrue(AbsolutePath("/").contains(AbsolutePath("/")))
296-
XCTAssertTrue(AbsolutePath("/foo/bar").contains(AbsolutePath("/")))
297-
XCTAssertFalse(AbsolutePath("/foo/bar").contains(AbsolutePath("/foo/bar/baz")))
298-
XCTAssertFalse(AbsolutePath("/foo/bar").contains(AbsolutePath("/bar")))
292+
func testAncestry() {
293+
XCTAssertTrue(AbsolutePath("/a/b/c/d/e/f").isDescendantOfOrEqual(to: AbsolutePath("/a/b/c/d")))
294+
XCTAssertTrue(AbsolutePath("/a/b/c/d/e/f.swift").isDescendantOfOrEqual(to: AbsolutePath("/a/b/c")))
295+
XCTAssertTrue(AbsolutePath("/").isDescendantOfOrEqual(to: AbsolutePath("/")))
296+
XCTAssertTrue(AbsolutePath("/foo/bar").isDescendantOfOrEqual(to: AbsolutePath("/")))
297+
XCTAssertFalse(AbsolutePath("/foo/bar").isDescendantOfOrEqual(to: AbsolutePath("/foo/bar/baz")))
298+
XCTAssertFalse(AbsolutePath("/foo/bar").isDescendantOfOrEqual(to: AbsolutePath("/bar")))
299+
300+
XCTAssertFalse(AbsolutePath("/foo/bar").isDescendant(of: AbsolutePath("/foo/bar")))
301+
XCTAssertTrue(AbsolutePath("/foo/bar").isDescendant(of: AbsolutePath("/foo")))
302+
303+
XCTAssertTrue(AbsolutePath("/a/b/c/d").isAncestorOfOrEqual(to: AbsolutePath("/a/b/c/d/e/f")))
304+
XCTAssertTrue(AbsolutePath("/a/b/c").isAncestorOfOrEqual(to: AbsolutePath("/a/b/c/d/e/f.swift")))
305+
XCTAssertTrue(AbsolutePath("/").isAncestorOfOrEqual(to: AbsolutePath("/")))
306+
XCTAssertTrue(AbsolutePath("/").isAncestorOfOrEqual(to: AbsolutePath("/foo/bar")))
307+
XCTAssertFalse(AbsolutePath("/foo/bar/baz").isAncestorOfOrEqual(to: AbsolutePath("/foo/bar")))
308+
XCTAssertFalse(AbsolutePath("/bar").isAncestorOfOrEqual(to: AbsolutePath("/foo/bar")))
309+
310+
XCTAssertFalse(AbsolutePath("/foo/bar").isAncestor(of: AbsolutePath("/foo/bar")))
311+
XCTAssertTrue(AbsolutePath("/foo").isAncestor(of: AbsolutePath("/foo/bar")))
299312
}
300313

301314
func testAbsolutePathValidation() {

0 commit comments

Comments
 (0)