From 3dc413d6fd279dfff777a9f9a964697a16c5aabc Mon Sep 17 00:00:00 2001
From: Folke Lemaitre <folke.lemaitre@gmail.com>
Date: Sun, 15 Oct 2023 08:36:15 +0200
Subject: [PATCH] fix(plugin): improved dir/dev merging. Fixes #993

---
 lua/lazy/core/plugin.lua   | 38 +++++++++++++++++++++++++++-----------
 lua/lazy/types.lua         |  1 +
 tests/core/plugin_spec.lua | 37 +++++++++++++++++++++++++++++++++++++
 3 files changed, 65 insertions(+), 11 deletions(-)

diff --git a/lua/lazy/core/plugin.lua b/lua/lazy/core/plugin.lua
index dbfcb46..a915d8f 100644
--- a/lua/lazy/core/plugin.lua
+++ b/lua/lazy/core/plugin.lua
@@ -84,8 +84,11 @@ function Spec:add(plugin, results)
     end
   end
 
+  ---@type string?
+  local dir
+
   if plugin.dir then
-    plugin.dir = Util.norm(plugin.dir)
+    dir = Util.norm(plugin.dir)
     -- local plugin
     plugin.name = plugin.name or Spec.get_name(plugin.dir)
   elseif plugin.url then
@@ -99,16 +102,6 @@ function Spec:add(plugin, results)
         end
       end
     end
-    -- dev plugins
-    if
-      plugin.dev
-      and (not Config.options.dev.fallback or vim.fn.isdirectory(Config.options.dev.path .. "/" .. plugin.name) == 1)
-    then
-      plugin.dir = Config.options.dev.path .. "/" .. plugin.name
-    else
-      -- remote plugin
-      plugin.dir = Config.options.root .. "/" .. plugin.name
-    end
   elseif is_ref then
     plugin.name = plugin[1]
   else
@@ -121,6 +114,17 @@ function Spec:add(plugin, results)
     return
   end
 
+  -- dev plugins
+  if
+    plugin.dev
+    and (not Config.options.dev.fallback or vim.fn.isdirectory(Config.options.dev.path .. "/" .. plugin.name) == 1)
+  then
+    dir = Config.options.dev.path .. "/" .. plugin.name
+  elseif plugin.dev == false then
+    -- explicitely select the default path
+    dir = Config.options.root .. "/" .. plugin.name
+  end
+
   if type(plugin.config) == "table" then
     self:warn(
       "{" .. plugin.name .. "}: setting a table to `Plugin.config` is deprecated. Please use `Plugin.opts` instead"
@@ -134,12 +138,15 @@ function Spec:add(plugin, results)
 
   M.last_fid = M.last_fid + 1
   plugin._ = {
+    dir = dir,
     fid = M.last_fid,
     fpid = fpid,
     dep = fpid ~= nil,
     module = self.importing,
   }
   self.fragments[plugin._.fid] = plugin
+  -- remote plugin
+  plugin.dir = plugin._.dir or (plugin.name and (Config.options.root .. "/" .. plugin.name)) or nil
 
   if fpid then
     local parent = self.fragments[fpid]
@@ -328,11 +335,14 @@ end
 
 function Spec:report(level)
   level = level or vim.log.levels.ERROR
+  local count = 0
   for _, notif in ipairs(self.notifs) do
     if notif.level >= level then
       Util.notify(notif.msg, { level = notif.level })
+      count = count + 1
     end
   end
+  return count
 end
 
 ---@param spec LazySpec|LazySpecImport
@@ -448,6 +458,12 @@ function Spec:merge(old, new)
     Util.extend(new.dependencies, old.dependencies)
   end
 
+  local new_dir = new._.dir or old._.dir or (new.name and (Config.options.root .. "/" .. new.name)) or nil
+  if new_dir ~= new.dir then
+    self:warn("Plugin `" .. new.name .. "` changed `dir`:\n- from: `" .. new.dir .. "`\n- to: `" .. new_dir .. "`")
+  end
+  new.dir = new_dir
+
   new._.super = old
   setmetatable(new, { __index = old })
 
diff --git a/lua/lazy/types.lua b/lua/lazy/types.lua
index 54c0930..0434359 100644
--- a/lua/lazy/types.lua
+++ b/lua/lazy/types.lua
@@ -18,6 +18,7 @@
 ---@field cond? boolean
 ---@field super? LazyPlugin
 ---@field module? string
+---@field dir? string Explicit dir or dev set for this plugin
 
 ---@alias PluginOpts table|fun(self:LazyPlugin, opts:table):table?
 
diff --git a/tests/core/plugin_spec.lua b/tests/core/plugin_spec.lua
index 15eb060..cae8542 100644
--- a/tests/core/plugin_spec.lua
+++ b/tests/core/plugin_spec.lua
@@ -50,6 +50,43 @@ describe("plugin spec url/name", function()
   end
 end)
 
+describe("plugin spec dir", function()
+  local tests = {
+    {
+      "~/projects/gitsigns.nvim",
+      { "lewis6991/gitsigns.nvim", opts = {}, dev = true },
+      { "lewis6991/gitsigns.nvim" },
+    },
+    {
+      "~/projects/gitsigns.nvim",
+      { "lewis6991/gitsigns.nvim", opts = {}, dev = true },
+      { "gitsigns.nvim" },
+    },
+    {
+      "~/projects/gitsigns.nvim",
+      { "lewis6991/gitsigns.nvim", opts = {} },
+      { "lewis6991/gitsigns.nvim", dev = true },
+    },
+    {
+      "~/projects/gitsigns.nvim",
+      { "lewis6991/gitsigns.nvim", opts = {} },
+      { "gitsigns.nvim", dev = true },
+    },
+  }
+
+  for _, test in ipairs(tests) do
+    local dir = vim.fn.expand(test[1])
+    local input = vim.list_slice(test, 2)
+    it("parses dir " .. vim.inspect(input):gsub("%s+", " "), function()
+      local spec = Plugin.Spec.new(input)
+      local plugins = vim.tbl_values(spec.plugins)
+      assert(spec:report() == 0)
+      assert.equal(1, #plugins)
+      assert.same(dir, plugins[1].dir)
+    end)
+  end
+end)
+
 describe("plugin spec opt", function()
   it("handles dependencies", function()
     Config.options.defaults.lazy = false