Skip to content

Conversation

sublee
Copy link

@sublee sublee commented Sep 26, 2025

When developing a golangci-lint plugin, it’s common to first implement the analyzer using the go/analysis interface and then wrap it with the golangci-lint plugin interface in a separate module. This separation is useful because the go/analysis interface is a generic abstraction that can be reused outside golangci-lint.

During development, a plugin may depend on an unreleased analyzer implementation and declare a replace directive in its go.mod. However, the current golangci-lint custom command ignores these directives. As a result, the custom build only succeeds if the plugin and the analyzer are bundled within the same go.mod.

This PR copies replace directives from the plugin's go.mod into the temporary go.mod used by golangci-lint custom. This ensures that the custom build correctly recognizes both the plugin and any modules it references via replace. As a result, plugins and analyzers can now be managed as separate modules.

@CLAassistant
Copy link

CLAassistant commented Sep 26, 2025

CLA assistant check
All committers have signed the CLA.

@ldez
Copy link
Member

ldez commented Sep 26, 2025

You don't need that.

You only need to use path with the "Automatic way": https://golangci-lint.run/docs/plugins/module-plugins/#the-automatic-way

Or to follow the "manual way": https://golangci-lint.run/docs/plugins/module-plugins/#the-manual-way

@ldez ldez added the linter: custom About custom/private linters label Sep 26, 2025
@sublee
Copy link
Author

sublee commented Sep 26, 2025

Thanks for the review, @ldez!

You're right that a replace in the plugin module itself is fine. The issue is that golangci-lint custom builds a temporary repo whose go.mod does not inherit the plugin's replace, so it tries to fetch the analyzer from VCS.

For example.

  1. .custom-gcl.yml points to ./myplugin by path option
  2. ./myplugin/go.mod has replace example.com/myanalyzer => ../myanalyzer (unpublished)
  3. Running golangci-lint custom fails downloading example.com/myanalyzer

I've thought the root cause is the temporary go.mod generated by custom lacks the plugin's replace. This PR copies the plugin's replace into that temporary go.mod, so local analyzer dependency can be resolved.

If there's an existing "as-is" automatic way that makes custom adopt the plugin's replace without this change, could you point me to it?

@ldez
Copy link
Member

ldez commented Sep 26, 2025

A plugin must contain the Analyzer so I don't understand what your plugin is.

During development, you don't need replace directive for your plugin; you only need to reference your plugin path inside the path field.

@sublee
Copy link
Author

sublee commented Sep 27, 2025

You are write. It could be solved with both import and path options:

plugins:
  - module: example.com/myanalyzer
    import: example.com/myanalyzer/myplugin
    path: .

When the repository looks like:

myanalyzer/
  .custom-gcl.yml
  go.mod -- does not depend on golangci-lint
  ...
  myplugin/
    go.mod -- depends on golangci-lint
    ...

Thanks for your advice!

@ldez
Copy link
Member

ldez commented Sep 27, 2025

A plugin should not depend on golangci-lint, but on the plugin-module-register

See the example: https://github.com/golangci/example-plugin-module-linter

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
declined linter: custom About custom/private linters
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants