diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 5687d95ef625..cc3c150186a0 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -153,5 +153,9 @@ jobs: - run: ./golangci-lint help - run: ./golangci-lint help linters + - run: ./golangci-lint help linters --json + - run: ./golangci-lint help formatters --json - run: ./golangci-lint linters + - run: ./golangci-lint formatters - run: ./golangci-lint version + - run: ./golangci-lint version --format json diff --git a/.golangci.yml b/.golangci.yml index c326529f35be..97b445c62781 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -182,6 +182,11 @@ linters: - gocritic text: "hugeParam:" + # The codes are close but this is not duplication. + - path: pkg/commands/(formatters|linters).go + linters: + - dupl + formatters: enable: - gofmt diff --git a/assets/cli-help.json b/assets/cli-help.json index 0a32cb993cfd..c8027460c012 100644 --- a/assets/cli-help.json +++ b/assets/cli-help.json @@ -1,4 +1,5 @@ { "enable": "Enabled by default linters:\nerrcheck: Errcheck is a program for checking for unchecked errors in Go code. These unchecked errors can be critical bugs in some cases.\ngovet: Vet examines Go source code and reports suspicious constructs. It is roughly the same as 'go vet' and uses its passes. [auto-fix]\nineffassign: Detects when assignments to existing variables are not used. [fast]\nstaticcheck: It's the set of rules from staticcheck. It's not the same thing as the staticcheck binary. The author of staticcheck doesn't support or approve the use of staticcheck as a library inside golangci-lint. [auto-fix]\nunused: Checks Go code for unused constants, variables, functions and types.", - "help": "Usage:\n golangci-lint run [flags]\n\nFlags:\n -c, --config PATH Read config from file path PATH\n --no-config Don't read config file\n --default string Default set of linters to enable (default \"standard\")\n -D, --disable strings Disable specific linter\n -E, --enable strings Enable specific linter\n --enable-only strings Override linters configuration section to only run the specific linter(s)\n --fast-only Filter enabled linters to run only fast linters\n -j, --concurrency int Number of CPUs to use (Default: Automatically set to match Linux container CPU quota and fall back to the number of logical CPUs in the machine)\n --modules-download-mode string Modules download mode. If not empty, passed as -mod=\u003cmode\u003e to go tools\n --issues-exit-code int Exit code when issues were found (default 1)\n --build-tags strings Build tags\n --timeout duration Timeout for total work. Disabled by default\n --tests Analyze tests (*_test.go) (default true)\n --allow-parallel-runners Allow multiple parallel golangci-lint instances running.\n If false (default) - golangci-lint acquires file lock on start.\n --allow-serial-runners Allow multiple golangci-lint instances running, but serialize them around a lock.\n If false (default) - golangci-lint exits with an error if it fails to acquire file lock on start.\n --path-prefix string Path prefix to add to output\n --show-stats Show statistics per linter (default true)\n --output.text.path stdout Output path can be either stdout, `stderr` or path to the file to write to.\n --output.text.print-linter-name Print linter name in the end of issue text. (default true)\n --output.text.print-issued-lines Print lines of code with issue. (default true)\n --output.text.colors Use colors. (default true)\n --output.json.path stdout Output path can be either stdout, `stderr` or path to the file to write to.\n --output.tab.path stdout Output path can be either stdout, `stderr` or path to the file to write to.\n --output.tab.print-linter-name Print linter name in the end of issue text. (default true)\n --output.tab.colors Use colors. (default true)\n --output.html.path stdout Output path can be either stdout, `stderr` or path to the file to write to.\n --output.checkstyle.path stdout Output path can be either stdout, `stderr` or path to the file to write to.\n --output.code-climate.path stdout Output path can be either stdout, `stderr` or path to the file to write to.\n --output.junit-xml.path stdout Output path can be either stdout, `stderr` or path to the file to write to.\n --output.junit-xml.extended Support extra JUnit XML fields.\n --output.teamcity.path stdout Output path can be either stdout, `stderr` or path to the file to write to.\n --output.sarif.path stdout Output path can be either stdout, `stderr` or path to the file to write to.\n --max-issues-per-linter int Maximum issues count per one linter. Set to 0 to disable (default 50)\n --max-same-issues int Maximum count of issues with the same text. Set to 0 to disable (default 3)\n --uniq-by-line Make issues output unique by line (default true)\n -n, --new Show only new issues: if there are unstaged changes or untracked files, only those changes are analyzed, else only changes in HEAD~ are analyzed.\n It's a super-useful option for integration of golangci-lint into existing large codebase.\n It's not practical to fix all existing issues at the moment of integration: much better to not allow issues in new code.\n For CI setups, prefer --new-from-rev=HEAD~, as --new can skip linting the current patch if any scripts generate unstaged files before golangci-lint runs.\n --new-from-rev REV Show only new issues created after git revision REV\n --new-from-patch PATH Show only new issues created in git patch with file path PATH\n --new-from-merge-base string Show only new issues created after the best common ancestor (merge-base against HEAD)\n --whole-files Show issues in any part of update files (requires new-from-rev or new-from-patch)\n --fix Fix found issues (if it's supported by the linter)\n --cpu-profile-path string Path to CPU profile output file\n --mem-profile-path string Path to memory profile output file\n --print-resources-usage Print avg and max memory usage of golangci-lint and total time\n --trace-path string Path to trace output file\n\nGlobal Flags:\n --color string Use color when printing; can be 'always', 'auto', or 'never' (default \"auto\")\n -h, --help Help for a command\n -v, --verbose Verbose output\n" + "help": "Usage:\n golangci-lint run [flags]\n\nFlags:\n -c, --config PATH Read config from file path PATH\n --no-config Don't read config file\n --default string Default set of linters to enable (default \"standard\")\n -D, --disable strings Disable specific linter\n -E, --enable strings Enable specific linter\n --enable-only strings Override linters configuration section to only run the specific linter(s)\n --fast-only Filter enabled linters to run only fast linters\n -j, --concurrency int Number of CPUs to use (Default: Automatically set to match Linux container CPU quota and fall back to the number of logical CPUs in the machine)\n --modules-download-mode string Modules download mode. If not empty, passed as -mod=\u003cmode\u003e to go tools\n --issues-exit-code int Exit code when issues were found (default 1)\n --build-tags strings Build tags\n --timeout duration Timeout for total work. Disabled by default\n --tests Analyze tests (*_test.go) (default true)\n --allow-parallel-runners Allow multiple parallel golangci-lint instances running.\n If false (default) - golangci-lint acquires file lock on start.\n --allow-serial-runners Allow multiple golangci-lint instances running, but serialize them around a lock.\n If false (default) - golangci-lint exits with an error if it fails to acquire file lock on start.\n --path-prefix string Path prefix to add to output\n --show-stats Show statistics per linter (default true)\n --output.text.path stdout Output path can be either stdout, `stderr` or path to the file to write to.\n --output.text.print-linter-name Print linter name in the end of issue text. (default true)\n --output.text.print-issued-lines Print lines of code with issue. (default true)\n --output.text.colors Use colors. (default true)\n --output.json.path stdout Output path can be either stdout, `stderr` or path to the file to write to.\n --output.tab.path stdout Output path can be either stdout, `stderr` or path to the file to write to.\n --output.tab.print-linter-name Print linter name in the end of issue text. (default true)\n --output.tab.colors Use colors. (default true)\n --output.html.path stdout Output path can be either stdout, `stderr` or path to the file to write to.\n --output.checkstyle.path stdout Output path can be either stdout, `stderr` or path to the file to write to.\n --output.code-climate.path stdout Output path can be either stdout, `stderr` or path to the file to write to.\n --output.junit-xml.path stdout Output path can be either stdout, `stderr` or path to the file to write to.\n --output.junit-xml.extended Support extra JUnit XML fields.\n --output.teamcity.path stdout Output path can be either stdout, `stderr` or path to the file to write to.\n --output.sarif.path stdout Output path can be either stdout, `stderr` or path to the file to write to.\n --max-issues-per-linter int Maximum issues count per one linter. Set to 0 to disable (default 50)\n --max-same-issues int Maximum count of issues with the same text. Set to 0 to disable (default 3)\n --uniq-by-line Make issues output unique by line (default true)\n -n, --new Show only new issues: if there are unstaged changes or untracked files, only those changes are analyzed, else only changes in HEAD~ are analyzed.\n It's a super-useful option for integration of golangci-lint into existing large codebase.\n It's not practical to fix all existing issues at the moment of integration: much better to not allow issues in new code.\n For CI setups, prefer --new-from-rev=HEAD~, as --new can skip linting the current patch if any scripts generate unstaged files before golangci-lint runs.\n --new-from-rev REV Show only new issues created after git revision REV\n --new-from-patch PATH Show only new issues created in git patch with file path PATH\n --new-from-merge-base string Show only new issues created after the best common ancestor (merge-base against HEAD)\n --whole-files Show issues in any part of update files (requires new-from-rev or new-from-patch)\n --fix Fix found issues (if it's supported by the linter)\n --cpu-profile-path string Path to CPU profile output file\n --mem-profile-path string Path to memory profile output file\n --print-resources-usage Print avg and max memory usage of golangci-lint and total time\n --trace-path string Path to trace output file\n\nGlobal Flags:\n --color string Use color when printing; can be 'always', 'auto', or 'never' (default \"auto\")\n -h, --help Help for a command\n -v, --verbose Verbose output\n", + "fmtHelp": "Usage:\n golangci-lint fmt [flags]\n\nFlags:\n -c, --config PATH Read config from file path PATH\n --no-config Don't read config file\n -E, --enable strings Enable specific formatter\n -d, --diff Display diffs instead of rewriting files\n\nGlobal Flags:\n --color string Use color when printing; can be 'always', 'auto', or 'never' (default \"auto\")\n -h, --help Help for a command\n -v, --verbose Verbose output\n" } diff --git a/docs/src/docs/contributing/website.mdx b/docs/src/docs/contributing/website.mdx index 60e32a7c7433..fd76bb2fedb8 100644 --- a/docs/src/docs/contributing/website.mdx +++ b/docs/src/docs/contributing/website.mdx @@ -45,7 +45,7 @@ Install Node.js (v20 or newer). Run: -```sh +```bash npm install --legacy-peer-deps npm run start ``` @@ -58,6 +58,6 @@ Also, there is no need to refresh a webpage: hot reload updates changed content To do it run: -```sh +```bash go run ./scripts/website/expand_templates/ ``` diff --git a/docs/src/docs/contributing/workflow.mdx b/docs/src/docs/contributing/workflow.mdx index 14a9ae48b180..c037e535eed5 100644 --- a/docs/src/docs/contributing/workflow.mdx +++ b/docs/src/docs/contributing/workflow.mdx @@ -19,7 +19,7 @@ Fork and clone [golangci-lint](https://github.com/golangci/golangci-lint) reposi A good way of making sure everything is all right is running the following: -```sh +```bash make build ./golangci-lint run -v ``` @@ -28,8 +28,8 @@ make build When you are satisfied with the changes, we suggest you run: -```sh -$ make test +```bash +make test ``` Which runs all the linters and tests. @@ -58,6 +58,7 @@ A GitHub Action [workflow](https://github.com/golangci/golangci-lint/blob/HEAD/. After making a release you need to update GitHub [Action config](https://github.com/golangci/golangci-lint/blob/HEAD/assets/github-action-config.json) by running: -```sh + +```bash make assets/github-action-config.json ``` diff --git a/docs/src/docs/usage/configuration.mdx b/docs/src/docs/usage/configuration.mdx index 07d678b06f91..8a53f6754efa 100644 --- a/docs/src/docs/usage/configuration.mdx +++ b/docs/src/docs/usage/configuration.mdx @@ -2,16 +2,23 @@ title: Configuration --- -The config file has lower priority than command-line options. If the same bool/string/int option is provided on the command-line +The config file has lower priority than command-line options. +If the same bool/string/int option is provided on the command-line and in the config file, the option from command-line will be used. Slice options (e.g. list of enabled/disabled linters) are combined from the command-line and config file. To see a list of linters enabled by your configuration use: -```sh +```bash golangci-lint linters ``` +To see a list of formatters enabled by your configuration use: + +```bash +golangci-lint formatters +``` + ## Config File GolangCI-Lint looks for config files in the following paths from the current working directory: @@ -37,8 +44,10 @@ The configuration file can be validated with the JSON Schema: https://golangci-l ## Command-Line Options +### run + ```sh -golangci-lint run -h +$ golangci-lint run -h {.RunHelpText} ``` @@ -48,6 +57,13 @@ in the format expected by the [pprof](https://github.com/google/pprof) visualiza When the `--trace-path` argument is specified, `golangci-lint` writes runtime tracing data in the format expected by the `go tool trace` command and visualization tool. +### fmt + +```sh +$ golangci-lint fmt -h +{.FmtHelpText} +``` + ## Cache GolangCI-Lint stores its cache in the subdirectory `golangci-lint` inside the [default user cache directory](https://pkg.go.dev/os#UserCacheDir). diff --git a/docs/src/docs/usage/false-positives.mdx b/docs/src/docs/usage/false-positives.mdx index 1a1c5dabd770..d01cdc9a13b2 100644 --- a/docs/src/docs/usage/false-positives.mdx +++ b/docs/src/docs/usage/false-positives.mdx @@ -16,7 +16,7 @@ Otherwise, some linters have dedicated configuration to exclude or disable rules An example with `staticcheck`: -```yml +```yaml linters-settings: staticcheck: checks: @@ -35,7 +35,7 @@ Also, you can use `issues.exclude-rules` config option for per-path or per-linte In the following example, all the reports that contains the sentences defined in `exclude` are excluded: -```yml +```yaml issues: exclude: - "Error return value of .((os\\.)?std(out|err)\\..*|.*Close|.*Flush|os\\.Remove(All)?|.*printf?|os\\.(Un)?Setenv). is not checked" @@ -45,7 +45,7 @@ issues: In the following example, all the reports from the linters (`linters`) that contains the text (`text`) are excluded: -```yml +```yaml issues: exclude-rules: - linters: @@ -55,7 +55,7 @@ issues: In the following example, all the reports from the linters (`linters`) that originated from the source (`source`) are excluded: -```yml +```yaml issues: exclude-rules: - linters: @@ -65,7 +65,7 @@ issues: In the following example, all the reports that contains the text (`text`) in the path (`path`) are excluded: -```yml +```yaml issues: exclude-rules: - path: path/to/a/file.go @@ -82,7 +82,7 @@ the `--path-prefix` parameter can be used to extend the paths before matching. In the following example, all the reports from the linters (`linters`) that concerns the path (`path`) are excluded: -```yml +```yaml issues: exclude-rules: - path: '(.+)_test\.go' @@ -94,7 +94,7 @@ issues: The opposite, excluding reports **except** for specific paths, is also possible. In the following example, only test files get checked: -```yml +```yaml issues: exclude-rules: - path-except: '(.+)_test\.go' @@ -105,7 +105,7 @@ issues: In the following example, all the reports related to the files (`exclude-files`) are excluded: -```yml +```yaml issues: exclude-files: - path/to/a/file.go @@ -113,7 +113,7 @@ issues: In the following example, all the reports related to the directories (`exclude-dirs`) are excluded: -```yml +```yaml issues: exclude-dirs: - path/to/a/dir/ @@ -174,18 +174,3 @@ Use `//nolint` instead of `// nolint` because machine-readable comments should h Some exclusions are considered common. To help golangci-lint users those common exclusions are provided through presets. {.ExclusionPresets} - -### Default Directory Exclusions - -By default, the reports from directory names, that match the following regular expressions, are excluded: - -- `third_party$` -- `examples$` -- `Godeps$` -- `builtin$` - -This option has been defined when Go modules was not existed and when the golangci-lint core was different, this is not something we still recommend. - -At some point, we will remove all those obsolete exclusions, but as it's a breaking changes it will only happen inside a major version. - -So we recommend setting `issues.exclude-dirs-use-default` to `false`. diff --git a/docs/src/docs/usage/formatters.mdx b/docs/src/docs/usage/formatters.mdx index 1281af245f0a..d14b85cc6a21 100644 --- a/docs/src/docs/usage/formatters.mdx +++ b/docs/src/docs/usage/formatters.mdx @@ -4,6 +4,12 @@ title: Formatters import { FaGithub, FaGitlab, FaArrowUp, FaCog } from "react-icons/fa"; +To see a list of supported formatters and which formatters are enabled/disabled: + +```bash +golangci-lint help formatters +``` + ## All Formatters {.Formatters} diff --git a/docs/src/docs/welcome/install.mdx b/docs/src/docs/welcome/install.mdx index a86f8147e58b..ef5112652cf1 100644 --- a/docs/src/docs/welcome/install.mdx +++ b/docs/src/docs/welcome/install.mdx @@ -29,7 +29,7 @@ Also, the action creates GitHub annotations for found issues (you don't need to Here is the other way to install golangci-lint: -```sh +```bash # binary will be $(go env GOPATH)/bin/golangci-lint curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin {.LatestVersion} @@ -51,7 +51,7 @@ and is constantly being improved. For any problems with `golangci-lint`, check o ### Binaries -```sh +```bash # binary will be $(go env GOPATH)/bin/golangci-lint curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin {.LatestVersion} @@ -73,7 +73,7 @@ so we recommend either using our binaries or be sure of the version of Go used t You can install a binary release on macOS using [brew](https://brew.sh/): -```sh +```bash brew install golangci-lint brew upgrade golangci-lint ``` @@ -82,7 +82,7 @@ Note: Previously we used a [Homebrew tap](https://github.com/golangci/homebrew-t isn't immediately available via Homebrew core due to manual updates that need to occur from Homebrew core maintainers. In this case, the tap formula, which is updated automatically, can be used to install the latest version of `golangci-lint`: -```sh +```bash brew tap golangci/tap brew install golangci/tap/golangci-lint ``` @@ -92,7 +92,7 @@ brew install golangci/tap/golangci-lint It can also be installed through [MacPorts](https://www.macports.org/) The MacPorts installation mode is community driven, and not officially maintained by golangci team. -```sh +```bash sudo port install golangci-lint ``` @@ -102,7 +102,7 @@ sudo port install golangci-lint You can install a binary on Windows using [chocolatey](https://community.chocolatey.org/packages/golangci-lint). -```sh +```bash choco install golangci-lint ``` @@ -110,7 +110,7 @@ choco install golangci-lint You can install a binary on Windows using [scoop](https://scoop.sh). -```sh +```bash scoop install main/golangci-lint ``` @@ -118,17 +118,17 @@ The scoop package is not officially maintained by golangci team. ### Docker -```sh +```bash docker run --rm -v $(pwd):/app -w /app golangci/golangci-lint:{.LatestVersion} golangci-lint run -v ``` Preserving cache between consecutive runs: -```sh +```bash docker run --rm -v $(pwd):/app -v ~/.cache/golangci-lint/{.LatestVersion}:/root/.cache -w /app golangci/golangci-lint:{.LatestVersion} golangci-lint run -v ``` Colored output: -```sh +```bash docker run -t --rm -v $(pwd):/app -w /app golangci/golangci-lint:{.LatestVersion} golangci-lint run -v ``` @@ -148,7 +148,7 @@ These installations aren't recommended because of the following points: 6. It allows installation from the main branch, which can't be considered stable. 7. It's slower than binary installation. -```sh +```bash go install github.com/golangci/golangci-lint/cmd/golangci-lint@{.LatestVersion} ``` diff --git a/docs/src/docs/welcome/quick-start.mdx b/docs/src/docs/welcome/quick-start.mdx index 99bd50f9fb70..857dc3291e46 100644 --- a/docs/src/docs/welcome/quick-start.mdx +++ b/docs/src/docs/welcome/quick-start.mdx @@ -6,19 +6,19 @@ title: Quick Start To run golangci-lint: -```sh +```bash golangci-lint run ``` It's an equivalent of: -```sh +```bash golangci-lint run ./... ``` You can choose which directories or files to analyze: -```sh +```bash golangci-lint run dir1 dir2/... golangci-lint run file1.go ``` @@ -36,7 +36,7 @@ $ golangci-lint help linters Pass `-E/--enable` to enable linter and `-D/--disable` to disable: -```sh +```bash golangci-lint run --disable-all -E errcheck ``` @@ -46,13 +46,13 @@ More information about available linters can be found in the [linters page](/usa To format your code: -```sh +```bash golangci-lint fmt ``` You can choose which directories or files to analyze: -```sh +```bash golangci-lint fmt dir1 dir2/... golangci-lint fmt file1.go ``` diff --git a/pkg/commands/formatters.go b/pkg/commands/formatters.go new file mode 100644 index 000000000000..8c5a056daf71 --- /dev/null +++ b/pkg/commands/formatters.go @@ -0,0 +1,112 @@ +package commands + +import ( + "fmt" + + "github.com/fatih/color" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/goformatters" + "github.com/golangci/golangci-lint/pkg/lint/linter" + "github.com/golangci/golangci-lint/pkg/lint/lintersdb" + "github.com/golangci/golangci-lint/pkg/logutils" +) + +type formattersOptions struct { + config.LoaderOptions +} + +type formattersCommand struct { + viper *viper.Viper + cmd *cobra.Command + + opts formattersOptions + + cfg *config.Config + + log logutils.Log + + dbManager *lintersdb.Manager +} + +func newFormattersCommand(logger logutils.Log) *formattersCommand { + c := &formattersCommand{ + viper: viper.New(), + cfg: config.NewDefault(), + log: logger, + } + + formattersCmd := &cobra.Command{ + Use: "formatters", + Short: "List current formatters configuration", + Args: cobra.NoArgs, + ValidArgsFunction: cobra.NoFileCompletions, + RunE: c.execute, + PreRunE: c.preRunE, + SilenceUsage: true, + } + + fs := formattersCmd.Flags() + fs.SortFlags = false // sort them as they are defined here + + setupConfigFileFlagSet(fs, &c.opts.LoaderOptions) + setupLintersFlagSet(c.viper, fs) + + c.cmd = formattersCmd + + return c +} + +func (c *formattersCommand) preRunE(cmd *cobra.Command, args []string) error { + loader := config.NewLoader(c.log.Child(logutils.DebugKeyConfigReader), c.viper, cmd.Flags(), c.opts.LoaderOptions, c.cfg, args) + + err := loader.Load(config.LoadOptions{Validation: true}) + if err != nil { + return fmt.Errorf("can't load config: %w", err) + } + + dbManager, err := lintersdb.NewManager(c.log.Child(logutils.DebugKeyLintersDB), c.cfg, + lintersdb.NewLinterBuilder(), lintersdb.NewPluginModuleBuilder(c.log), lintersdb.NewPluginGoBuilder(c.log)) + if err != nil { + return err + } + + c.dbManager = dbManager + + return nil +} + +func (c *formattersCommand) execute(_ *cobra.Command, _ []string) error { + enabledLintersMap, err := c.dbManager.GetEnabledLintersMap() + if err != nil { + return fmt.Errorf("can't get enabled formatters: %w", err) + } + + var enabledFormatters []*linter.Config + var disabledFormatters []*linter.Config + + for _, lc := range c.dbManager.GetAllSupportedLinterConfigs() { + if lc.Internal { + continue + } + + if !goformatters.IsFormatter(lc.Name()) { + continue + } + + if enabledLintersMap[lc.Name()] == nil { + disabledFormatters = append(disabledFormatters, lc) + } else { + enabledFormatters = append(enabledFormatters, lc) + } + } + + color.Green("Enabled by your configuration formatters:\n") + printFormatters(enabledFormatters) + color.Red("\nDisabled by your configuration formatters:\n") + printFormatters(disabledFormatters) + + return nil +} diff --git a/pkg/commands/help.go b/pkg/commands/help.go index f9e5c8949320..8accd001b269 100644 --- a/pkg/commands/help.go +++ b/pkg/commands/help.go @@ -1,10 +1,6 @@ package commands import ( - "encoding/json" - "fmt" - "maps" - "slices" "strings" "unicode" "unicode/utf8" @@ -12,23 +8,10 @@ import ( "github.com/fatih/color" "github.com/spf13/cobra" - "github.com/golangci/golangci-lint/pkg/config" - "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/lint/lintersdb" "github.com/golangci/golangci-lint/pkg/logutils" ) -type linterHelp struct { - Name string `json:"name"` - Desc string `json:"description"` - Groups []string `json:"groups"` - Fast bool `json:"fast"` - AutoFix bool `json:"autoFix"` - Deprecated bool `json:"deprecated"` - Since string `json:"since"` - OriginalURL string `json:"originalURL,omitempty"` -} - type helpOptions struct { JSON bool } @@ -60,136 +43,36 @@ func newHelpCommand(logger logutils.Log) *helpCommand { Short: "Help about linters", Args: cobra.NoArgs, ValidArgsFunction: cobra.NoFileCompletions, - RunE: c.execute, - PreRunE: c.preRunE, - } - - helpCmd.AddCommand(lintersCmd) - - fs := lintersCmd.Flags() - fs.SortFlags = false // sort them as they are defined here - - fs.BoolVar(&c.opts.JSON, "json", false, color.GreenString("Display as JSON")) - - c.cmd = helpCmd - - return c -} - -func (c *helpCommand) preRunE(_ *cobra.Command, _ []string) error { - // The command doesn't depend on the real configuration. - // It just needs the list of all plugins and all presets. - dbManager, err := lintersdb.NewManager(c.log.Child(logutils.DebugKeyLintersDB), config.NewDefault(), lintersdb.NewLinterBuilder()) - if err != nil { - return err + RunE: c.lintersExecute, + PreRunE: c.lintersPreRunE, } - c.dbManager = dbManager + fsLinter := lintersCmd.Flags() + fsLinter.SortFlags = false // sort them as they are defined here - return nil -} + fsLinter.BoolVar(&c.opts.JSON, "json", false, color.GreenString("Display as JSON")) -func (c *helpCommand) execute(_ *cobra.Command, _ []string) error { - if c.opts.JSON { - return c.printJSON() - } - - c.print() - - return nil -} + helpCmd.AddCommand(lintersCmd) -func (c *helpCommand) printJSON() error { - var linters []linterHelp - - for _, lc := range c.dbManager.GetAllSupportedLinterConfigs() { - if lc.Internal { - continue - } - - groups := []string{config.GroupAll} - - if !lc.IsSlowLinter() { - groups = append(groups, config.GroupFast) - } - - linters = append(linters, linterHelp{ - Name: lc.Name(), - Desc: formatDescription(lc.Linter.Desc()), - Groups: slices.Concat(groups, slices.Collect(maps.Keys(lc.Groups))), - Fast: !lc.IsSlowLinter(), - AutoFix: lc.CanAutoFix, - Deprecated: lc.IsDeprecated(), - Since: lc.Since, - OriginalURL: lc.OriginalURL, - }) + formattersCmd := &cobra.Command{ + Use: "formatters", + Short: "Help about formatters", + Args: cobra.NoArgs, + ValidArgsFunction: cobra.NoFileCompletions, + RunE: c.formattersExecute, + PreRunE: c.formattersPreRunE, } - return json.NewEncoder(c.cmd.OutOrStdout()).Encode(linters) -} + fsFormatter := formattersCmd.Flags() + fsFormatter.SortFlags = false // sort them as they are defined here -func (c *helpCommand) print() { - var enabledLCs, disabledLCs []*linter.Config - for _, lc := range c.dbManager.GetAllSupportedLinterConfigs() { - if lc.Internal { - continue - } - - if lc.FromGroup(config.GroupStandard) { - enabledLCs = append(enabledLCs, lc) - } else { - disabledLCs = append(disabledLCs, lc) - } - } + fsFormatter.BoolVar(&c.opts.JSON, "json", false, color.GreenString("Display as JSON")) - color.Green("Enabled by default linters:\n") - printLinters(enabledLCs) + helpCmd.AddCommand(formattersCmd) - color.Red("\nDisabled by default linters:\n") - printLinters(disabledLCs) -} + c.cmd = helpCmd -func printLinters(lcs []*linter.Config) { - slices.SortFunc(lcs, func(a, b *linter.Config) int { - if a.IsDeprecated() && b.IsDeprecated() { - return strings.Compare(a.Name(), b.Name()) - } - - if a.IsDeprecated() { - return 1 - } - - if b.IsDeprecated() { - return -1 - } - - return strings.Compare(a.Name(), b.Name()) - }) - - for _, lc := range lcs { - desc := formatDescription(lc.Linter.Desc()) - - deprecatedMark := "" - if lc.IsDeprecated() { - deprecatedMark = " [" + color.RedString("deprecated") + "]" - } - - var capabilities []string - if !lc.IsSlowLinter() { - capabilities = append(capabilities, color.BlueString("fast")) - } - if lc.CanAutoFix { - capabilities = append(capabilities, color.GreenString("auto-fix")) - } - - var capability string - if capabilities != nil { - capability = " [" + strings.Join(capabilities, ", ") + "]" - } - - _, _ = fmt.Fprintf(logutils.StdOut, "%s%s: %s%s\n", - color.YellowString(lc.Name()), deprecatedMark, desc, capability) - } + return c } func formatDescription(desc string) string { diff --git a/pkg/commands/help_formatters.go b/pkg/commands/help_formatters.go new file mode 100644 index 000000000000..3aaa9cde5ce5 --- /dev/null +++ b/pkg/commands/help_formatters.go @@ -0,0 +1,119 @@ +package commands + +import ( + "encoding/json" + "fmt" + "slices" + "strings" + + "github.com/fatih/color" + "github.com/spf13/cobra" + + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/goformatters" + "github.com/golangci/golangci-lint/pkg/lint/linter" + "github.com/golangci/golangci-lint/pkg/lint/lintersdb" + "github.com/golangci/golangci-lint/pkg/logutils" +) + +type formatterHelp struct { + Name string `json:"name"` + Desc string `json:"description"` + Deprecated bool `json:"deprecated"` + Since string `json:"since"` + OriginalURL string `json:"originalURL,omitempty"` +} + +func (c *helpCommand) formattersPreRunE(_ *cobra.Command, _ []string) error { + // The command doesn't depend on the real configuration. + dbManager, err := lintersdb.NewManager(c.log.Child(logutils.DebugKeyLintersDB), config.NewDefault(), lintersdb.NewLinterBuilder()) + if err != nil { + return err + } + + c.dbManager = dbManager + + return nil +} + +func (c *helpCommand) formattersExecute(_ *cobra.Command, _ []string) error { + if c.opts.JSON { + return c.formattersPrintJSON() + } + + c.formattersPrint() + + return nil +} + +func (c *helpCommand) formattersPrintJSON() error { + var formatters []formatterHelp + + for _, lc := range c.dbManager.GetAllSupportedLinterConfigs() { + if lc.Internal { + continue + } + + if !goformatters.IsFormatter(lc.Name()) { + continue + } + + formatters = append(formatters, formatterHelp{ + Name: lc.Name(), + Desc: formatDescription(lc.Linter.Desc()), + Deprecated: lc.IsDeprecated(), + Since: lc.Since, + OriginalURL: lc.OriginalURL, + }) + } + + return json.NewEncoder(c.cmd.OutOrStdout()).Encode(formatters) +} + +func (c *helpCommand) formattersPrint() { + var lcs []*linter.Config + for _, lc := range c.dbManager.GetAllSupportedLinterConfigs() { + if lc.Internal { + continue + } + + if !goformatters.IsFormatter(lc.Name()) { + continue + } + + lcs = append(lcs, lc) + } + + color.Green("Disabled by default formatters:\n") + printFormatters(lcs) +} + +func printFormatters(lcs []*linter.Config) { + slices.SortFunc(lcs, func(a, b *linter.Config) int { + if a.IsDeprecated() && b.IsDeprecated() { + return strings.Compare(a.Name(), b.Name()) + } + + if a.IsDeprecated() { + return 1 + } + + if b.IsDeprecated() { + return -1 + } + + return strings.Compare(a.Name(), b.Name()) + }) + + for _, lc := range lcs { + desc := formatDescription(lc.Linter.Desc()) + + deprecatedMark := "" + if lc.IsDeprecated() { + deprecatedMark = " [" + color.RedString("deprecated") + "]" + } + + _, _ = fmt.Fprintf(logutils.StdOut, "%s%s: %s\n", + color.YellowString(lc.Name()), deprecatedMark, desc) + } +} diff --git a/pkg/commands/help_linters.go b/pkg/commands/help_linters.go new file mode 100644 index 000000000000..bca4e5406ab5 --- /dev/null +++ b/pkg/commands/help_linters.go @@ -0,0 +1,152 @@ +package commands + +import ( + "encoding/json" + "fmt" + "maps" + "slices" + "strings" + + "github.com/fatih/color" + "github.com/spf13/cobra" + + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/goformatters" + "github.com/golangci/golangci-lint/pkg/lint/linter" + "github.com/golangci/golangci-lint/pkg/lint/lintersdb" + "github.com/golangci/golangci-lint/pkg/logutils" +) + +type linterHelp struct { + Name string `json:"name"` + Desc string `json:"description"` + Groups []string `json:"groups"` + Fast bool `json:"fast"` + AutoFix bool `json:"autoFix"` + Deprecated bool `json:"deprecated"` + Since string `json:"since"` + OriginalURL string `json:"originalURL,omitempty"` +} + +func (c *helpCommand) lintersPreRunE(_ *cobra.Command, _ []string) error { + // The command doesn't depend on the real configuration. + dbManager, err := lintersdb.NewManager(c.log.Child(logutils.DebugKeyLintersDB), config.NewDefault(), lintersdb.NewLinterBuilder()) + if err != nil { + return err + } + + c.dbManager = dbManager + + return nil +} + +func (c *helpCommand) lintersExecute(_ *cobra.Command, _ []string) error { + if c.opts.JSON { + return c.lintersPrintJSON() + } + + c.lintersPrint() + + return nil +} + +func (c *helpCommand) lintersPrintJSON() error { + var linters []linterHelp + + for _, lc := range c.dbManager.GetAllSupportedLinterConfigs() { + if lc.Internal { + continue + } + + if goformatters.IsFormatter(lc.Name()) { + continue + } + + groups := []string{config.GroupAll} + + if !lc.IsSlowLinter() { + groups = append(groups, config.GroupFast) + } + + linters = append(linters, linterHelp{ + Name: lc.Name(), + Desc: formatDescription(lc.Linter.Desc()), + Groups: slices.Concat(groups, slices.Collect(maps.Keys(lc.Groups))), + Fast: !lc.IsSlowLinter(), + AutoFix: lc.CanAutoFix, + Deprecated: lc.IsDeprecated(), + Since: lc.Since, + OriginalURL: lc.OriginalURL, + }) + } + + return json.NewEncoder(c.cmd.OutOrStdout()).Encode(linters) +} + +func (c *helpCommand) lintersPrint() { + var enabledLCs, disabledLCs []*linter.Config + for _, lc := range c.dbManager.GetAllSupportedLinterConfigs() { + if lc.Internal { + continue + } + + if goformatters.IsFormatter(lc.Name()) { + continue + } + + if lc.FromGroup(config.GroupStandard) { + enabledLCs = append(enabledLCs, lc) + } else { + disabledLCs = append(disabledLCs, lc) + } + } + + color.Green("Enabled by default linters:\n") + printLinters(enabledLCs) + + color.Red("\nDisabled by default linters:\n") + printLinters(disabledLCs) +} + +func printLinters(lcs []*linter.Config) { + slices.SortFunc(lcs, func(a, b *linter.Config) int { + if a.IsDeprecated() && b.IsDeprecated() { + return strings.Compare(a.Name(), b.Name()) + } + + if a.IsDeprecated() { + return 1 + } + + if b.IsDeprecated() { + return -1 + } + + return strings.Compare(a.Name(), b.Name()) + }) + + for _, lc := range lcs { + desc := formatDescription(lc.Linter.Desc()) + + deprecatedMark := "" + if lc.IsDeprecated() { + deprecatedMark = " [" + color.RedString("deprecated") + "]" + } + + var capabilities []string + if !lc.IsSlowLinter() { + capabilities = append(capabilities, color.BlueString("fast")) + } + if lc.CanAutoFix { + capabilities = append(capabilities, color.GreenString("auto-fix")) + } + + var capability string + if capabilities != nil { + capability = " [" + strings.Join(capabilities, ", ") + "]" + } + + _, _ = fmt.Fprintf(logutils.StdOut, "%s%s: %s%s\n", + color.YellowString(lc.Name()), deprecatedMark, desc, capability) + } +} diff --git a/pkg/commands/linters.go b/pkg/commands/linters.go index a93814f0f851..1ddd63f8f105 100644 --- a/pkg/commands/linters.go +++ b/pkg/commands/linters.go @@ -8,6 +8,7 @@ import ( "github.com/spf13/viper" "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/goformatters" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/lint/lintersdb" "github.com/golangci/golangci-lint/pkg/logutils" @@ -91,6 +92,10 @@ func (c *lintersCommand) execute(_ *cobra.Command, _ []string) error { continue } + if goformatters.IsFormatter(lc.Name()) { + continue + } + if enabledLintersMap[lc.Name()] == nil { disabledLCs = append(disabledLCs, lc) } else { diff --git a/pkg/commands/root.go b/pkg/commands/root.go index 2544738ffea5..e739cc12f57a 100644 --- a/pkg/commands/root.go +++ b/pkg/commands/root.go @@ -59,6 +59,7 @@ func newRootCommand(info BuildInfo) *rootCommand { // Each command uses a dedicated configuration structure to avoid side effects of bindings. rootCmd.AddCommand( newLintersCommand(log).cmd, + newFormattersCommand(log).cmd, newRunCommand(log, info).cmd, newFmtCommand(log, info).cmd, newMigrateCommand(log, info).cmd, diff --git a/pkg/commands/run.go b/pkg/commands/run.go index 88eff67b303f..76735afe9384 100644 --- a/pkg/commands/run.go +++ b/pkg/commands/run.go @@ -53,8 +53,6 @@ const ( ) const ( - // envHelpRun value: "1". - envHelpRun = "HELP_RUN" envMemProfileRate = "GL_MEM_PROFILE_RATE" ) diff --git a/scripts/website/dump_info/main.go b/scripts/website/dump_info/main.go index e9e86f2adabc..bb76b5b58c6d 100644 --- a/scripts/website/dump_info/main.go +++ b/scripts/website/dump_info/main.go @@ -152,25 +152,40 @@ func saveCLIHelp(dst string) error { lintersOutParts := bytes.Split(lintersOut, []byte("\n\n")) - helpCmd := exec.Command("./golangci-lint", "run", "-h") - helpCmd.Env = append(helpCmd.Env, os.Environ()...) - helpCmd.Env = append(helpCmd.Env, "HELP_RUN=1") // make default concurrency stable: don't depend on machine CPU number - help, err := helpCmd.Output() + rumCmdHelp, err := getCmdHelp("run") if err != nil { - return fmt.Errorf("can't run help cmd: %w", err) + return err } - helpLines := bytes.Split(help, []byte("\n")) - shortHelp := bytes.Join(helpLines[2:], []byte("\n")) + fmtCmdHelp, err := getCmdHelp("fmt") + if err != nil { + return err + } data := types.CLIHelp{ - Enable: string(lintersOutParts[0]), - Help: string(shortHelp), + Enable: string(lintersOutParts[0]), + RunCmdHelp: rumCmdHelp, + FmtCmdHelp: fmtCmdHelp, } return saveToJSONFile(dst, data) } +func getCmdHelp(name string) (string, error) { + helpCmd := exec.Command("./golangci-lint", name, "-h") + helpCmd.Env = append(helpCmd.Env, os.Environ()...) + + help, err := helpCmd.Output() + if err != nil { + return "", fmt.Errorf("can't run help cmd: %w", err) + } + + helpLines := bytes.Split(help, []byte("\n")) + shortHelp := bytes.Join(helpLines[2:], []byte("\n")) + + return string(shortHelp), nil +} + func saveToJSONFile(dst string, data any) error { file, err := os.Create(dst) if err != nil { diff --git a/scripts/website/expand_templates/main.go b/scripts/website/expand_templates/main.go index 8a517f331443..a628af832a50 100644 --- a/scripts/website/expand_templates/main.go +++ b/scripts/website/expand_templates/main.go @@ -131,7 +131,8 @@ func buildTemplateContext() (map[string]string, error) { "Formatters": getLintersListMarkdown(false, filepath.Join("assets", "formatters-info.json")), "ExclusionPresets": exclusions, "ThanksList": getThanksList(), - "RunHelpText": helps.Help, + "RunHelpText": helps.RunCmdHelp, + "FmtHelpText": helps.FmtCmdHelp, "ChangeLog": string(changeLog), "LatestVersion": latestVersion, }, nil diff --git a/scripts/website/types/types.go b/scripts/website/types/types.go index 19568cf19ba2..cd667590bc9a 100644 --- a/scripts/website/types/types.go +++ b/scripts/website/types/types.go @@ -5,8 +5,9 @@ import ( ) type CLIHelp struct { - Enable string `json:"enable"` - Help string `json:"help"` + Enable string `json:"enable"` + RunCmdHelp string `json:"help"` + FmtCmdHelp string `json:"fmtHelp"` } type ExcludeRule struct {