stevearc / conform.nvim

Lightweight yet powerful formatter plugin for Neovim
MIT License
2.67k stars 138 forks source link

bug: gofumpt isn't formatting the file #387

Open Integralist opened 3 months ago

Integralist commented 3 months ago

Neovim version (nvim -v)

0.9.5

Operating system/version

MacOS 14.4.1

Add the debug logs

Log file

Log file: /Users/integralist/.local/state/nvim/conform.log 07:38:12[INFO] Run gofumpt on /Users/integralist/Code/example/example-api/cmd/maxmind/main.go 07:38:12[DEBUG] Run command: { "gofumpt" } 07:38:12[DEBUG] gofumpt exited with code 0 07:38:12[INFO] Run goimports on /Users/integralist/Code/example/example-api/cmd/maxmind/main.go 07:38:12[DEBUG] Run command: { "goimports", "-srcdir", "/Users/integralist/Code/example/example-api/cmd/maxmind" } 07:38:12[DEBUG] goimports exited with code 0 07:38:12[INFO] Run goimports-reviser on /Users/integralist/Code/example/example-api/cmd/maxmind/main.go 07:38:12[DEBUG] Creating temp file /Users/integralist/Code/example/example-api/cmd/maxmind/.conform.4003455.main.go 07:38:12[DEBUG] Run command: { "goimports-reviser", "-format", "/Users/integralist/Code/example/example-api/cmd/maxmind/.conform.4003455.main.go" } 07:38:13[DEBUG] goimports-reviser exited with code 0 07:38:13[DEBUG] Cleaning up temp file /Users/integralist/Code/example/example-api/cmd/maxmind/.conform.4003455.main.go

Formatters for this buffer: LSP: gopls gofumpt ready (go) /Users/integralist/.local/share/nvim/mason/bin/gofumpt goimports ready (go) /Users/integralist/.local/share/nvim/mason/bin/goimports goimports-reviser ready (go) /Users/integralist/.local/share/nvim/mason/bin/goimports-reviser

Describe the bug

It looks like only gofumpt is being run when it should be gofumpt -w <PATH>.

The -w writes the formatting changes back to disk, while gofumpt with no arguments defaults to displaying the whole file (with any potential changes included).

What is the severity of this bug?

tolerable (can work around it)

Steps To Reproduce

Example change from gofumpt on my Go file (see below for example code)...

- err := os.MkdirAll(d, 0775)
+ err := os.MkdirAll(d, 0o775)

The -d flag shows the diff:

gofumpt -d main.go

Expected Behavior

When I write to my buffer, I expect the call to gofumpt to apply the formatting changes to my file.

Minimal example file

package main

import (
    "log"
    "os"
)

func main() {
    err := os.MkdirAll("example", 0775)
    if err != nil {
        log.Fatalf("Error creating directory: %s", err)
    }
}

Minimal init.lua

-- DO NOT change the paths and don't remove the colorscheme
local root = vim.fn.fnamemodify("./.repro", ":p")

-- set stdpaths to use .repro
for _, name in ipairs({ "config", "data", "state", "cache" }) do
  vim.env[("XDG_%s_HOME"):format(name:upper())] = root .. "/" .. name
end

-- bootstrap lazy
local lazypath = root .. "/plugins/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
  vim.fn.system({
    "git",
    "clone",
    "--filter=blob:none",
    "--single-branch",
    "https://github.com/folke/lazy.nvim.git",
    lazypath,
  })
end
vim.opt.runtimepath:prepend(lazypath)

-- install plugins
local plugins = {
  "folke/tokyonight.nvim",
  {
    "stevearc/conform.nvim",
    config = function()
      require("conform").setup({
        log_level = vim.log.levels.DEBUG,
        -- add your config here
        formatters_by_ft = {
          go = { "gofumpt", "goimports", "goimports-reviser" },
        },
        format_on_save = function(bufnr)
          return { async = true, timeout_ms = 5000, lsp_fallback = true }
        end
      })
    end,
  },
  -- add any other plugins here
}
require("lazy").setup(plugins, {
  root = root .. "/plugins",
})

vim.cmd.colorscheme("tokyonight")
-- add anything else here

Additional context

Here is the log when using the repro.lua above

Log file: /Users/integralist/.local/state/nvim/conform.log
          08:09:50[DEBUG] Cleaning up temp file /private/tmp/test-vim-project/.conform.1014787.main.go
          08:10:07[DEBUG] Running formatters on /private/tmp/test-vim-project/main.go: { "gofumpt", "goimports", "goimports-reviser" }
          08:10:07[INFO] Run gofumpt on /private/tmp/test-vim-project/main.go
          08:10:07[DEBUG] Run command: { "gofumpt" }
          08:10:07[DEBUG] gofumpt exited with code 0
          08:10:07[INFO] Run goimports on /private/tmp/test-vim-project/main.go
          08:10:07[DEBUG] Run command: { "goimports", "-srcdir", "/private/tmp/test-vim-project" }
          08:10:07[DEBUG] goimports exited with code 0
          08:10:07[INFO] Run goimports-reviser on /private/tmp/test-vim-project/main.go
          08:10:07[DEBUG] Creating temp file /private/tmp/test-vim-project/.conform.1494182.main.go
          08:10:07[DEBUG] Run command: { "goimports-reviser", "-format", "/private/tmp/test-vim-project/.conform.1494182.main.go" }
          08:10:07[DEBUG] goimports-reviser exited with code 0
          08:10:07[DEBUG] Cleaning up temp file /private/tmp/test-vim-project/.conform.1494182.main.go

Formatters for this buffer:
LSP: gopls
gofumpt ready (go) /Users/integralist/.local/share/nvim/mason/bin/gofumpt
goimports ready (go) /Users/integralist/.local/share/nvim/mason/bin/goimports
goimports-reviser ready (go) /Users/integralist/.local/share/nvim/mason/bin/goimports-reviser
metal3d commented 2 months ago

I've got exactly the same problem using lazyvim. The code is only formatted by goimports. That means that my imports are not split the way I like (with gofumpt) and what you mention, about the octal form, isn't set.

I really don't know if the problem isn't LSP or if conform.nvim isn't working correctly. But this is a big problem. Each time I fix something in a file, I need to quit neovim to force gofumpt and push the code. Each time I save the file, the format is not OK for the project.

I search for days how to fix this.

metal3d commented 2 months ago

I actually found a "workaround", but that "breaks lazyvim" (an alert appears).

If I do:

  {
    "stevearc/conform.nvim",
    config = function(_, opts)
      require("conform").setup({
        go = { "gofumpt" },
      })
    end,
  },

So, now, only gofumpt is used.

But, as I mentioned, do not do this as it break LazyVim to format others source files

metal3d commented 2 months ago

Well, I think I've found a good "workaround" even if I'm not entirely satisfied with the way it works.

For no apparent reason, the conform plugin won't use gofumpt, no matter what list of formatters you give it. And what's worse, gopls uses gofumpt and gets the job done.

So, to let gopls do the job that conform doesn't do (this isn't a criticism, I love the plugin, but it doesn't work well for Go) is to remove the go formatter altogether. That way, gopls will do the job.

So, with LazyVim, I did this:

-- ~/.config/nvim/lua/plugins/go.lua
return {
  {
    "stevearc/conform.nvim",
    opts = function(_, opts)
      -- stop formating Go files, leave gopls to the job with gofumpt
      opts.formatters_by_ft["go"] = {}
    end,
  }
}

On the other hand, gofumpt doesn't tell me to change the mode to 0oXXX (sonarlint does) - I don't know why it suggests this diff. OK, sorry, it works actually.

But as far as imports are concerned, it finally lists and formats them as I want.

In other words, your issue is completely valid: conform doesn't use gofumpt and I have no explanation.

Integralist commented 2 months ago

I tried adding the following before conform.setup...

conform.formatters.gofumpt = {
    prepend_args = { "-w", vim.api.nvim_buf_get_name(0) },
}

...but it just errors...

Error detected while processing BufWritePre Autocommands for "*":
Formatter failed. See :ConformInfo for details

And if I check the log I see...

07:26:14[DEBUG] Running formatters on /Users/integralist/Code/example/cmd/epp/main.go: { "gofumpt", "goimports", "goimports-reviser" }
07:26:14[INFO] Run gofumpt on /Users/integralist/Code/example/cmd/epp/main.go
07:26:14[DEBUG] Run command: { "gofumpt", "-w", "" }
07:26:14[INFO] gofumpt exited with code 2
07:26:14[DEBUG] gofumpt stdout: { "" }
07:26:14[DEBUG] gofumpt stderr: { "stat : no such file or directory", "" }

So my approach for getting the file name failed. @metal3d do you know of a way to get the buffer name?

Integralist commented 2 months ago

I then tried moving it to here where I knew I could get the buffer number...

      format_on_save = function(bufnr)
        -- disable with a global or buffer-local variable
        if vim.g.disable_autoformat or vim.b[bufnr].disable_autoformat then
          return
        end

        conform.formatters.gofumpt = {
          prepend_args = { "-w", vim.api.nvim_buf_get_name(bufnr) },
        }

        return { async = true, timeout_ms = 5000, lsp_fallback = true }
      end

Which looks like it worked, but I still get the Error detected while processing BufWritePre Autocommands for "*": error, and if I check the logs I see...

07:30:51[DEBUG] Running formatters on /Users/integralist/Code/example/cmd/epp/main.go: { "gofumpt", "goimports", "goimports-reviser" }
07:30:51[INFO] Run gofumpt on /Users/integralist/Code/example/cmd/epp/main.go
07:30:51[DEBUG] Run command: { "gofumpt", "-w", "/Users/integralist/Code/examplecmd/epp/main.go" }
07:30:51[DEBUG] gofumpt exited with code 0
07:30:51[INFO] Run goimports on /Users/integralist/Code/example/cmd/epp/main.go
07:30:51[DEBUG] Run command: { "goimports", "-srcdir", "/Users/integralist/Code/example/cmd/epp" }
07:30:51[DEBUG] goimports exited with code 0
07:30:51[INFO] Run goimports-reviser on /Users/integralist/Code/example/cmd/epp/main.go
07:30:51[DEBUG] Creating temp file /Users/integralist/Code/example/cmd/epp/.conform.3587335.main.go
07:30:51[DEBUG] Run command: { "goimports-reviser", "-format", "/Users/integralist/Code/example/cmd/epp/.conform.3587335.main.go" }
07:30:51[INFO] goimports-reviser exited with code 1
07:30:51[DEBUG] goimports-reviser stdout: nil
07:30:51[DEBUG] goimports-reviser stderr: { "2024/05/02 07:30:51 Failed to fix file: 1:2: expected 'package', found 'EOF'", "" }
07:30:51[DEBUG] Cleaning up temp file /Users/integralist/Code/example/cmd/epp/.conform.3587335.main.go
07:30:51[WARN] Aborting because a formatter returned empty output for buffer /Users/integralist/Code/domainr/mustang/cmd/epp/main.go
07:30:51[ERROR] Formatter 'goimports-reviser' error: 2024/05/02 07:30:51 Failed to fix file: 1:2: expected 'package', found 'EOF'
Integralist commented 2 months ago

Looks like formatting with gofumpt like this and the use of goimports-reviser are conflicting somehow?

metal3d commented 2 months ago

I will investigate too.

That's probably the only problem that I have to fix among all the plugins that are activated in my NeoVim setup.

And because I'm developing a lot with Go, I really need to fix this behavior.

Unfortunately I'm not comfortable with Lua yet. After 20 years using Vim then NeoVim, I was using vim-plug and CoC until last week. CoC-Go plugin has no problem with gopls and gofumpt but I wonder if it actually doesn't only use gopls with the gofumpt option. And so, if the behavior is the same than deactivating "conform" like I did...

I decided to give LazyVim a chance, and I like it. Because "conform" is activated when we use the "Extras" setup for Go, I finally get this problem.

I need to train a bit on Lua (it's a very simple language) and to read the plugin source code.

Anyway, the plugin is very well.

metal3d commented 2 months ago

This is what I have now in my "golang.lua" plugin in LazyVim:

return {
  {
    "williamboman/mason.nvim",
    opts = function(_, opts)
      opts.ensure_installed = opts.ensure_installed or {}
      vim.list_extend(opts.ensure_installed, { "goimports-reviser" })
    end,
  },
  {
    "stevearc/conform.nvim",
    opts = function(_, opts)
      -- stop using conform for go, gopls sould do the job
      opts.formatters_by_ft["go"] = { "goimports-reviser" }
    end,
  },
}

And I you mentionned, the FileMode is still "0775" instead of "0o775".

If I remove everything as opts.formatters_by_ft["go"] = { } so gofumpt is OK with gopls.

So, there is a real problem and it's unclear where to fix this...

metal3d commented 2 months ago

@Integralist it seems that I finally found how to make it work. The problem comes from gopls that fails using STDIN (see the description after the lua snippet):

return {
  {
    "williamboman/mason.nvim",
    opts = function(_, opts)
      opts.ensure_installed = opts.ensure_installed or {}
      vim.list_extend(opts.ensure_installed, { "goimports-reviser" })
    end,
  },
  {
    "stevearc/conform.nvim",
    opts = {
      formatters_by_ft = {
        go = { "goimports-reviser", "gofumpt" },
      },
      formatters = {
        gofumpt = {
          command = "gofumpt",
          args = { "$FILENAME" },
        },
      },
    },
  },
}

You can avoid the mason part, this is for my LazyVim setup.

Actually, in the README file, there is an example to configure a formatter. Conform uses the standard output to read the result, so no need to set "-w".

But, Conform sends the content of the file to stdin. And the is the problem... take a look:


$ cat cmd/test/foo.go | gofumpt 
package main

import (
        "log"
        "os"
)

func main() {
        err := os.MkdirAll("example", 0775)
        if err != nil {
                log.Fatalf("Error creating directory: %s", err)
        }
}

$ gofumpt cmd/test/foo.go 
package main

import (
        "log"
        "os"
)

func main() {
        err := os.MkdirAll("example", 0o775)
        if err != nil {
                log.Fatalf("Error creating directory: %s", err)
        }
}

gofumpt fails with STDIN, so I set "$FILENAME" as input and that's OK. We can also set stdin to false I guess.

metal3d commented 2 months ago

See https://github.com/mvdan/gofumpt/issues/117#issuecomment-813349378 and https://github.com/mvdan/gofumpt/issues/277 maybe, there are several information.

But it only affects imports, not what we can see about modfiles.

Integralist commented 2 months ago

Wow. Epic discovery. Thanks for your perseverance with this. Very appreciated 🙇‍♂️

metal3d commented 2 months ago

😄 I found your issue because I had the same 😉

I'm as stubborn as a mule and I couldn't stay on a failure. The verbose mode guided me quite a bit, and I reproduced what "Conform" was doing to see the result. And that's when it hit me.

It was your example that really helped me, because my problem only concerned the import order and I thought I'd found the solution. But I realized that it went further than that. And the unformatted file.mod is a real proof of concept.

In any case, you've guided me a long way towards the solution, with your tests and your example. That's the strength of sharing with free software 😄

metal3d commented 2 months ago

It's not completely OK...


  {
    "stevearc/conform.nvim",
    opts = {
      formatters_by_ft = {
        go = { "gofumpt", "goimports-reviser" },
      },
      formatters = {
        gofumpt = {
          command = "gofumpt",
          args = { "$FILENAME" },
        },
      },
    },
  },

Now, whatever the change I do, it's overwritten by one of the formatter...

metal3d commented 2 months ago

OK... fixed, the stdin = false option was missing. This works:

  {
    "stevearc/conform.nvim",
    opts = {
      formatters_by_ft = {
        go = { "gofumpt", "goimports-reviser" },
      },
      formatters = {
        gofumpt = {
          command = "gofumpt",
          args = { "$FILENAME" },
          stdin = false,
        },
      },
    },
  },
stevearc commented 2 months ago

So is the issue here that gofumpt produces different formatting when run on stdin vs when run on a file? If so, could you open a PR to change the default configuration? Might help other users avoid the same issues.

Integralist commented 2 months ago

@metal3d your solution didn't work for me 😞

Here we can see I have a test file that indicates the file wasn't formatted with gofumpt (we can see the octal issue too)...

Screenshot 2024-05-08 at 11 29 31

Now here is my config...

https://github.com/Integralist/nvim/blob/bdfbc1924a97b9f2f4c3d94330ab8e7deaf296a3/lua/plugins/lint-and-format.lua#L74-L162

I've copied the formatters { ... } block that you had but when I edit the main.go file I have it doesn't get formatted.

If I check the debug log I see...

Screenshot 2024-05-08 at 11 30 01
metal3d commented 2 months ago

That's weird. I don't have the same log. It doesn't create temporary file on my side.

It works as expected as soon as I added the argument and set stdin to false.

I will make some others tests in a few hours.

metal3d commented 2 months ago

So is the issue here that gofumpt produces different formatting when run on stdin vs when run on a file? If so, could you open a PR to change the default configuration? Might help other users avoid the same issues.

I will make a few more tests as @Integralist still has a problem. But, yes, I will make a pull request as soon as possible.

Integralist commented 2 months ago

Thanks @metal3d I appreciate your efforts here ❤️

Integralist commented 2 months ago

Just wondering if anyone had any further updates on this issue.

I know y'all are doing this off your own back, so of course I appreciate that it's likely no one has had any time to spare to investigate this further. So just to reiterate I'm not pushing for answers but am just interested if there are any updates that might help give me some ideas of what to try next. Thanks! 🙇🏻

If not then that's fine, I might just have to set up my own auto0command to apply gofumpt in the time being.

Integralist commented 2 months ago

For anyone interested this is what I'm doing manually...

vim.api.nvim_create_autocmd({ "BufWritePost" }, {
  group = vim.api.nvim_create_augroup("GoFumpt", { clear = true }),
  pattern = "*.go",
  command = "silent !gofumpt -w %"
})
Integralist commented 2 months ago

I should add that the above workaround isn't ideal as it causes race conditions with conform's formatters from what I can tell.

williamhjcho commented 5 days ago

Hello

I just stumbled upon this issue after trying getting stuck trying to make conform work with gofumpt

looking at the conform.log the command is running just by itself (probably just doing a buffer pipe into the gofumpt command?)

19:32:30[DEBUG] Running formatters on /Users/whjc/dotfiles/main.go: { "gofumpt" }
19:32:30[INFO] Run gofumpt on /Users/whjc/dotfiles/main.go
19:32:30[DEBUG] Run command: { "gofumpt" }
19:32:30[DEBUG] Run default CWD: /Users/whjc/dotfiles
19:32:30[DEBUG] gofumpt exited with code 0
$ cat main.go | gofumpt

This just prints the main.go file as is, which is why we're not seeing any changes to the file.


But then trying to add the file as arg, seemed to have worked (with the default args like stdin=true, etc)

  {
    "stevearc/conform.nvim",
    opts = {
      formatters_by_ft = {
        go = { "gofumpt" },
      },
      formatters = {
        gofumpt = {
          args = { "$FILENAME" },
        },
      },
    },
  }
19:35:55[INFO] Run gofumpt on /Users/whjc/dotfiles/main.go
19:35:55[DEBUG] Run command: { "gofumpt", "/Users/whjc/dotfiles/main.go" }
19:35:55[DEBUG] Run default CWD: /Users/whjc/dotfiles
19:35:55[DEBUG] gofumpt exited with code 0
$ gofumpt main.go
# or 
$ cat main.go | gofumpt main.go

both yield the same formatted stdout, as expected 🤷

not sure if this is the ideal, but it seems to be working so far

williamhjcho commented 5 days ago

Actually this isn't enough, depending on how the file save events are setup, it will output the formatted value, overriding any file changes if any.

then it looks like gofumpt just isn't processing stdin pipes? not sure how to solve this issue only on conform