Skip to content

Commit

Permalink
fix(utils): handle edge case for is_subpath (#1655)
Browse files Browse the repository at this point in the history
Co-authored-by: pynappo <lehtien.david@gmail.com>
  • Loading branch information
saytaM12 and pynappo authored Jan 14, 2025
1 parent 343886b commit 83222b3
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 5 deletions.
21 changes: 16 additions & 5 deletions lua/neo-tree/utils/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -862,26 +862,37 @@ M.normalize_path = function(path)
path = path:sub(1, 1):upper() .. path:sub(2)
-- Turn mixed forward and back slashes into all forward slashes
-- using NeoVim's logic
path = vim.fs.normalize(path)
path = vim.fs.normalize(path, { win = true })
-- Now use backslashes, as expected by the rest of Neo-Tree's code
path = path:gsub("/", M.path_separator)
end
return path
end

---Check if a path is a subpath of another.
--@param base string The base path.
--@param path string The path to check is a subpath.
--@return boolean boolean True if it is a subpath, false otherwise.
---@param base string The base path.
---@param path string The path to check is a subpath.
---@return boolean boolean True if it is a subpath, false otherwise.
M.is_subpath = function(base, path)
if not M.truthy(base) or not M.truthy(path) then
return false
elseif base == path then
return true
end

base = M.normalize_path(base)
path = M.normalize_path(path)
return string.sub(path, 1, string.len(base)) == base
if path:sub(1, #base) == base then
local base_parts = M.split(base, M.path_separator)
local path_parts = M.split(path, M.path_separator)
for i, part in ipairs(base_parts) do
if path_parts[i] ~= part then
return false
end
end
return true
end
return false
end

---The file system path separator for the current platform.
Expand Down
66 changes: 66 additions & 0 deletions tests/neo-tree/utils/path_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
pcall(require, "luacov")
local utils = require("neo-tree.utils")

describe("is_subpath", function()
local common_tests = function()
-- Relative paths
assert.are.same(true, utils.is_subpath("a", "a/subpath"))
assert.are.same(false, utils.is_subpath("a", "b/c"))
assert.are.same(false, utils.is_subpath("a", "b"))
end
it("should work with unix paths", function()
local old = utils.is_windows
utils.is_windows = false
common_tests()
assert.are.same(true, utils.is_subpath("/a", "/a/subpath"))
assert.are.same(false, utils.is_subpath("/a", "/b/c"))

-- Edge cases
assert.are.same(false, utils.is_subpath("", ""))
assert.are.same(true, utils.is_subpath("/", "/"))

-- Paths with trailing slashes
assert.are.same(true, utils.is_subpath("/a/", "/a/subpath"))
assert.are.same(true, utils.is_subpath("/a/", "/a/subpath/"))
assert.are.same(true, utils.is_subpath("/a", "/a/subpath"))
assert.are.same(true, utils.is_subpath("/a", "/a/subpath/"))

-- Paths with different casing
assert.are.same(true, utils.is_subpath("/TeSt", "/TeSt/subpath"))
assert.are.same(false, utils.is_subpath("/A", "/a/subpath"))
assert.are.same(false, utils.is_subpath("/A", "/a/subpath"))
utils.is_windows = old
end)
it("should work on windows paths", function()
local old = utils.is_windows
utils.is_windows = true
common_tests()
assert.are.same(true, utils.is_subpath("C:", "C:"))
assert.are.same(false, utils.is_subpath("C:", "D:"))
assert.are.same(true, utils.is_subpath("C:/A", [[C:\A]]))

-- Test Windows paths with backslashes
assert.are.same(true, utils.is_subpath([[C:\Users\user]], [[C:\Users\user\Documents]]))
assert.are.same(false, utils.is_subpath([[C:\Users\user]], [[D:\Users\user]]))
assert.are.same(false, utils.is_subpath([[C:\Users\user]], [[C:\Users\usera]]))

-- Test Windows paths with forward slashes
assert.are.same(true, utils.is_subpath("C:/Users/user", "C:/Users/user/Documents"))
assert.are.same(false, utils.is_subpath("C:/Users/user", "D:/Users/user"))
assert.are.same(false, utils.is_subpath("C:/Users/user", "C:/Users/usera"))

-- Test Windows paths with drive letters
assert.are.same(true, utils.is_subpath("C:", "C:/Users/user"))
assert.are.same(false, utils.is_subpath("C:", "D:/Users/user"))

-- Test Windows paths with UNC paths
assert.are.same(true, utils.is_subpath([[\\server\share]], [[\\server\share\folder]]))
assert.are.same(false, utils.is_subpath([[\\server\share]], [[\\server2\share]]))

-- Test Windows paths with trailing backslashes
assert.are.same(true, utils.is_subpath([[C:\Users\user\]], [[C:\Users\user\Documents]]))
assert.are.same(true, utils.is_subpath("C:/Users/user/", "C:/Users/user/Documents"))

utils.is_windows = old
end)
end)

0 comments on commit 83222b3

Please sign in to comment.