Skip to content

Conversation

Danthewaann
Copy link
Contributor

Currently the golangcilint linter is providing the parent directory as the file argument to the golangci-lint linter. This doesn't work when working in an individual golang file that isn't apart of a standard golang package directory.

Here is the error I get when editing a single standalone golang file. which returns a 5 exit code:

ERRO Running error: context loading failed: no go files to analyze: running `go mod tidy` may solve the problem

Here is how to reproduce it:

# Directory structure
hello_world
└── main.go

1 directory, 1 file
// hello_world/main.go
package main

import "fmt"

func main() {
	fmt.Println("Hello, World!")
}
# Run golangci-lint
golangci-lint run --out-format json --issues-exit-code=0 --show-stats=false --print-issued-lines=false --print-linter-name=false hello_world

When I run golanci-lint directly against the main.go file, it works as expected:

$ golangci-lint run --out-format json --issues-exit-code=0 --show-stats=false --print-issued-lines=false --print-linter-name=false hello_world/main.go
{"Issues":[],"Report":{"Linters":[{"Name":"asasalint"},{"Name":"asciicheck"},{"Name":"bidichk"},{"Name":"bodyclose"},{"Name":"canonicalheader"},{"Name":"containedctx"},{"Name":"contextcheck"},{"Name":"copyloopvar"},{"Name":"cyclop"},{"Name":"decorder"},{"Name":"deadcode"},{"Name":"depguard"},{"Name":"dogsled"},{"Name":"dupl"},{"Name":"dupword"},{"Name":"durationcheck"},{"Name":"errcheck","Enabled":true,"EnabledByDefault":true},{"Name":"errchkjson"},{"Name":"errname"},{"Name":"errorlint"},{"Name":"execinquery"},{"Name":"exhaustive"},{"Name":"exhaustivestruct"},{"Name":"exhaustruct"},{"Name":"exportloopref"},{"Name":"exptostd"},{"Name":"forbidigo"},{"Name":"forcetypeassert"},{"Name":"fatcontext"},{"Name":"funlen"},{"Name":"gci"},{"Name":"ginkgolinter"},{"Name":"gocheckcompilerdirectives"},{"Name":"gochecknoglobals"},{"Name":"gochecknoinits"},{"Name":"gochecksumtype"},{"Name":"gocognit"},{"Name":"goconst"},{"Name":"gocritic"},{"Name":"gocyclo"},{"Name":"godot"},{"Name":"godox"},{"Name":"err113"},{"Name":"gofmt"},{"Name":"gofumpt"},{"Name":"goheader"},{"Name":"goimports"},{"Name":"golint"},{"Name":"mnd"},{"Name":"gomnd"},{"Name":"gomoddirectives"},{"Name":"gomodguard"},{"Name":"goprintffuncname"},{"Name":"gosec"},{"Name":"gosimple","Enabled":true,"EnabledByDefault":true},{"Name":"gosmopolitan"},{"Name":"govet","Enabled":true,"EnabledByDefault":true},{"Name":"grouper"},{"Name":"ifshort"},{"Name":"iface"},{"Name":"importas"},{"Name":"inamedparam"},{"Name":"ineffassign","Enabled":true,"EnabledByDefault":true},{"Name":"interfacebloat"},{"Name":"interfacer"},{"Name":"intrange"},{"Name":"ireturn"},{"Name":"lll"},{"Name":"loggercheck"},{"Name":"maintidx"},{"Name":"makezero"},{"Name":"maligned"},{"Name":"mirror"},{"Name":"misspell"},{"Name":"musttag"},{"Name":"nakedret"},{"Name":"nestif"},{"Name":"nilerr"},{"Name":"nilnesserr"},{"Name":"nilnil"},{"Name":"nlreturn"},{"Name":"noctx"},{"Name":"nonamedreturns"},{"Name":"nosnakecase"},{"Name":"nosprintfhostport"},{"Name":"paralleltest"},{"Name":"perfsprint"},{"Name":"prealloc"},{"Name":"predeclared"},{"Name":"promlinter"},{"Name":"protogetter"},{"Name":"reassign"},{"Name":"recvcheck"},{"Name":"revive"},{"Name":"rowserrcheck"},{"Name":"sloglint"},{"Name":"scopelint"},{"Name":"sqlclosecheck"},{"Name":"spancheck"},{"Name":"staticcheck","Enabled":true,"EnabledByDefault":true},{"Name":"structcheck"},{"Name":"stylecheck"},{"Name":"tagalign"},{"Name":"tagliatelle"},{"Name":"tenv"},{"Name":"testableexamples"},{"Name":"testifylint"},{"Name":"testpackage"},{"Name":"thelper"},{"Name":"tparallel"},{"Name":"typecheck","Enabled":true,"EnabledByDefault":true},{"Name":"unconvert"},{"Name":"unparam"},{"Name":"unused","Enabled":true,"EnabledByDefault":true},{"Name":"usestdlibvars"},{"Name":"usetesting"},{"Name":"varcheck"},{"Name":"varnamelen"},{"Name":"wastedassign"},{"Name":"whitespace"},{"Name":"wrapcheck"},{"Name":"wsl"},{"Name":"zerologlint"},{"Name":"nolintlint"}]}}

@WhoIsSethDaniel
Copy link
Contributor

This breaks 'normal' setups where there is a go.mod. golangci-lint won't be able to find types etc... that are not inside the current file.

I do see the problem you are seeing, but I don't have a good idea for how to get around it.

@WhoIsSethDaniel
Copy link
Contributor

WhoIsSethDaniel commented Jul 7, 2025

One possibility is to run go env GOMOD in the directory of the current file and check what is returned. If it is /dev/null there is no go.mod for that file. In that case use :p over :h.

@Danthewaann
Copy link
Contributor Author

One possibility is to run go env GOMOD in the directory of the current file and check what is returned. If it is /dev/null there is no go.mod for that file. In that case use :p over :h.

I've pushed up 292100c with this change.

@WhoIsSethDaniel
Copy link
Contributor

It looks like when GO111MODULE is set to off or auto the GOMOD variable can be an empty string when looking at a single file with no go.mod. It only seems to be set to /dev/null when GO111MODULE is set to on and there is no go.mod. I changed your code to also check for an empty string and it worked as expected.

This will fail to work if you have a session with a one-off file and other files within a project since, as currently written, it will only evaluate the filename_modifier once for the entire session.

@WhoIsSethDaniel
Copy link
Contributor

WhoIsSethDaniel commented Jul 8, 2025

FWIW, it doesn't seem like the current golangci-lint configuration can work with multiple projects within a single session. I can't get it to work anyway. So maybe that's not so important.

@mfussenegger
Copy link
Owner

@cativovo @JonnyLoughlin does this look okay to you?

Overall I'm not too happy about how golangcilint gets more complex and complex. Compared to other linters this thing is a beast.

@JonnyWhitney
Copy link

I can confirm this doesn't break any existing set ups for me.

The reference from golangcilint that I can find for linting without a go.mod file is this issue from 4 years ago which never even lead to documentation changes. It seems the "correct" (if you can call it that) way to do this would just be running the example as GO111MODULE=off golangci-lint run [...] hello_world. From my testing, this works for the initial example (if I am even understanding correctly), without referencing the file name. Not sure how implementing that within the nvim-lint config would/could work. Just setting it in the env field on the linter didn't work for me. I have no experience with go before the module system so this situation isn't something I've considered/ do not really see the use case for, so it is hard for me to really know if that would be a viable solution.

Thoughts separate from this specific issue and morose on golangci and nvim-lint in general:

I agree that overall complexity of golangcilint's config, relative to other linters, feels like a lot. Golangci can be used in so may different ways and I don't think the burden of ensuring nvim-lint works, without further configuration, with all of them should fall on you. I don't think it is reasonable nvim-lint to work out of the box with every possible golangci-lint use-case. Users are always free to modify the linter arguments to suite their specific config needs.

At a certain point, I wonder if it would be worth providing a reasonable set of default args to run recent versions of golangci-lint with default options, and then maybe document the actual golangcilint.lua file with some common args override examples?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants