From 6a507bad18a4fb184792a4b36c0f8bd675ce172e Mon Sep 17 00:00:00 2001 From: "neovim-backports[bot]" <175700243+neovim-backports[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 08:49:19 -0800 Subject: [PATCH] =?UTF-8?q?fix(vim.fs):=20abspath(".")=20returns=20"/?= =?UTF-8?q?=E2=80=A6/."=20(#36584)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix(vim.fs): abspath(".") returns "/…/." #36583 (cherry picked from commit 1f9d9cb2e56c484562960d6088ac877322500600) Co-authored-by: Justin M. Keyes --- runtime/lua/vim/fs.lua | 9 +++++++-- test/functional/lua/fs_spec.lua | 24 +++++++++++++++++------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index c4c75558f2..338b4c94b0 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -437,7 +437,7 @@ function M.root(source, marker) for _, mark in ipairs(markers) do local paths = M.find(mark, { upward = true, - path = vim.fn.fnamemodify(path, ':p:h'), + path = M.abspath(path), }) if #paths ~= 0 then @@ -747,6 +747,8 @@ end --- @param path string Path --- @return string Absolute path function M.abspath(path) + -- TODO(justinmk): mark f_fnamemodify as API_FAST and use it, ":p:h" should be safe... + vim.validate('path', path, 'string') -- Expand ~ to user's home directory @@ -773,7 +775,10 @@ function M.abspath(path) -- Convert cwd path separator to `/` cwd = cwd:gsub(os_sep, '/') - -- Prefix is not needed for expanding relative paths, as `cwd` already contains it. + if path == '.' then + return cwd + end + -- Prefix is not needed for expanding relative paths, `cwd` already contains it. return M.joinpath(cwd, path) end diff --git a/test/functional/lua/fs_spec.lua b/test/functional/lua/fs_spec.lua index fd75077ae9..9a86aa6d04 100644 --- a/test/functional/lua/fs_spec.lua +++ b/test/functional/lua/fs_spec.lua @@ -412,23 +412,31 @@ describe('vim.fs', function() ) end) - it('uses cwd for unnamed buffers', function() + it('returns CWD (absolute path) for unnamed buffers', function() + assert(n.fn.isabsolutepath(test_source_path) == 1) command('new') - eq(test_source_path, exec_lua([[return vim.fs.root(0, 'CMakePresets.json')]])) + eq( + t.fix_slashes(test_source_path), + t.fix_slashes(exec_lua([[return vim.fs.root(0, 'CMakePresets.json')]])) + ) end) - it("uses cwd for buffers with non-empty 'buftype'", function() + it("returns CWD (absolute path) for buffers with non-empty 'buftype'", function() + assert(n.fn.isabsolutepath(test_source_path) == 1) command('new') command('set buftype=nofile') command('file lua://') - eq(test_source_path, exec_lua([[return vim.fs.root(0, 'CMakePresets.json')]])) + eq( + t.fix_slashes(test_source_path), + t.fix_slashes(exec_lua([[return vim.fs.root(0, 'CMakePresets.json')]])) + ) end) - it('returns an absolute path for an invalid filename', function() + it('returns CWD (absolute path) if no match is found', function() assert(n.fn.isabsolutepath(test_source_path) == 1) eq( t.fix_slashes(test_source_path), - t.fix_slashes(exec_lua([[return vim.fs.root('file://asd', 'CMakePresets.json')]])) + t.fix_slashes(exec_lua([[return vim.fs.root('file://bogus', 'CMakePresets.json')]])) ) end) end) @@ -612,7 +620,9 @@ describe('vim.fs', function() local cwd = assert(t.fix_slashes(assert(vim.uv.cwd()))) local home = t.fix_slashes(assert(vim.uv.os_homedir())) - it('works', function() + it('expands relative paths', function() + assert(n.fn.isabsolutepath(cwd) == 1) + eq(cwd, vim.fs.abspath('.')) eq(cwd .. '/foo', vim.fs.abspath('foo')) eq(cwd .. '/././foo', vim.fs.abspath('././foo')) eq(cwd .. '/.././../foo', vim.fs.abspath('.././../foo'))