diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index aa4e3e9..b934ad1 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -18,7 +18,6 @@ name: Checks permissions: checks: write contents: read - pull-requests: read jobs: checks: name: Checks @@ -42,13 +41,11 @@ jobs: - name: Dependency Licenses Review run: make check-dependency-licenses - name: Check for spelling errors - uses: reviewdog/action-misspell@v1 - with: - exclude: ./vendor/* - fail_on_error: true - github_token: ${{ secrets.GITHUB_TOKEN }} - ignore: importas - reporter: github-check + uses: crate-ci/typos@v1 + env: + CLICOLOR: "1" + - name: Delete typos binary + run: rm typos - name: Check if source code files have license header run: make check-addlicense - name: REUSE Compliance Check diff --git a/.golangci.yaml b/.golangci.yaml index bb06be4..187f726 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -56,7 +56,6 @@ linters: - ineffassign - intrange - iotamixing - - misspell - modernize - nilerr - nolintlint diff --git a/.typos.toml b/.typos.toml new file mode 100644 index 0000000..c8a1685 --- /dev/null +++ b/.typos.toml @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2025 SAP SE +# +# SPDX-License-Identifier: Apache-2.0 + +[default.extend-words] + +[files] +extend-exclude = [ + "go.mod", + "vendor/", +] diff --git a/Makefile b/Makefile index 961c3c7..a197b52 100644 --- a/Makefile +++ b/Makefile @@ -110,6 +110,11 @@ run-shellcheck: FORCE install-shellcheck @printf "\e[1;36m>> shellcheck\e[0m\n" @find . \( -path './vendor/*' -prune \) -o -type f \( -name '*.bash' -o -name '*.ksh' -o -name '*.zsh' -o -name '*.sh' -o -name '*.shlib' \) -exec shellcheck {} + +run-typos: FORCE + @printf "\e[1;36m>> typos\e[0m\n" + @printf "\e[1;36m>> Typos install instructions can be found here https://github.com/crate-ci/typos#install \e[0m\n" + @typos + build/cover.out: FORCE | build @printf "\e[1;36m>> Running tests\e[0m\n" @env $(GO_TESTENV) go test -shuffle=on -coverprofile=build/coverprofile.out $(GO_BUILDFLAGS) -ldflags '-s -w -X github.com/sapcc/go-api-declarations/bininfo.binName=go-makefile-maker -X github.com/sapcc/go-api-declarations/bininfo.version=$(BININFO_VERSION) -X github.com/sapcc/go-api-declarations/bininfo.commit=$(BININFO_COMMIT_HASH) -X github.com/sapcc/go-api-declarations/bininfo.buildDate=$(BININFO_BUILD_DATE) $(GO_LDFLAGS)' -covermode=count -coverpkg=$(subst $(space),$(comma),$(GO_COVERPKGS)) $(GO_TESTFLAGS) $(GO_TESTPKGS) @@ -212,6 +217,7 @@ help: FORCE @printf " \e[36mcheck\e[0m Run the test suite (unit tests and golangci-lint).\n" @printf " \e[36mrun-golangci-lint\e[0m Install and run golangci-lint. Installing is used in CI, but you should probably install golangci-lint using your package manager.\n" @printf " \e[36mrun-shellcheck\e[0m Install and run shellcheck. Installing is used in CI, but you should probably install shellcheck using your package manager.\n" + @printf " \e[36mrun-typos\e[0m Check for spelling errors using typos.\n" @printf " \e[36mbuild/cover.out\e[0m Run tests and generate coverage report.\n" @printf " \e[36mbuild/cover.html\e[0m Generate an HTML file with source code annotations from the coverage report.\n" @printf " \e[36mcheck-addlicense\e[0m Check license headers in all non-vendored .go files with addlicense.\n" diff --git a/README.md b/README.md index ab1de62..8049a65 100644 --- a/README.md +++ b/README.md @@ -82,8 +82,8 @@ The config file has the following sections: * [renovate](#renovate) * [reuse](#reuse) * [shellCheck](#shellCheck) -* [spellCheck](#spellcheck) * [testPackages](#testpackages) +* [typos](#typos) * [variables](#variables) * [verbatim](#verbatim) * [githubWorkflow](#githubworkflow) @@ -473,18 +473,23 @@ Whether to run [`ShellCheck`](https://www.shellcheck.net/) on all shell scripts `opts` specifies additional options to pass to `shellcheck`. The `-e` option can be used to ignore specific shellcheck warnings. -### `spellCheck` +### `typos` ```yaml -spellCheck: - ignoreWords: - - example - - exampleTwo +typos: + enabled: true + extendExcludes: + - internal/compress/constants.go + - internal/compress/testdata/ + extendWords: + reenforced: reenforced # do not correct to the semantically different word "reinforced" ``` -`golangci-lint` (if `golangciLint.createConfig` is `true`) and the spell check GitHub workflow (`githubWorkflow.spellCheck`) use [`misspell`][misspell] to check for spelling errors. +Whether to run [`typos`](https://github.com/crate-ci/typos) to check for spelling mistakes. + +`typos.extendExcludes` maps to typos `files.extend-excludes` and allows to exclude some files or paths from spell checking. -If `spellCheck.ignoreWords` is defined then both `golangci-lint` and spell check workflow will give this word list to `misspell` so that they can be ignored during its checks. +`typos.extendWords` maps to typos `default.extend-words` and allows to add custom word mappings to the dictionary. The key is hereby the wrong spelling and the value the correct spelling. ### `testPackages` @@ -714,7 +719,6 @@ githubWorkflow: [doublestar-pattern]: https://github.com/bmatcuk/doublestar#patterns [go-licence-detector]: https://github.com/elastic/go-licence-detector [govulncheck]: https://github.com/golang/vuln -[misspell]: https://github.com/client9/misspell [ref-onpushpull]: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#onpushpull_requestpaths [ref-pattern-cheat-sheet]: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet [ref-runs-on]: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on diff --git a/internal/core/config.go b/internal/core/config.go index aafec8f..f207075 100644 --- a/internal/core/config.go +++ b/internal/core/config.go @@ -38,11 +38,13 @@ type Configuration struct { Nix NixConfig `yaml:"nix"` Renovate RenovateConfig `yaml:"renovate"` ShellCheck ShellCheckConfiguration `yaml:"shellCheck"` - SpellCheck SpellCheckConfiguration `yaml:"spellCheck"` - Test TestConfiguration `yaml:"testPackages"` - Reuse ReuseConfiguration `yaml:"reuse"` - Verbatim string `yaml:"verbatim"` - VariableValues map[string]string `yaml:"variables"` + // Deprecated: use `typos` instead. + SpellCheck SpellCheckConfiguration `yaml:"spellCheck"` + Test TestConfiguration `yaml:"testPackages"` + Typos TyposConfiguration `yaml:"typos"` + Reuse ReuseConfiguration `yaml:"reuse"` + Verbatim string `yaml:"verbatim"` + VariableValues map[string]string `yaml:"variables"` } // Variable returns the value of this variable if it's overridden in the config, @@ -139,6 +141,18 @@ type SpellCheckConfiguration struct { IgnoreWords []string `yaml:"ignoreWords"` } +// TyposConfiguration appears in type Configuration. +type TyposConfiguration struct { + Enabled Option[bool] `yaml:"enabled"` + ExtendExcludes []string `yaml:"extendExcludes"` + ExtendWords map[string]string `yaml:"extendWords"` +} + +// IsEnabled encodes that the default state for the Enabled field is `true`. +func (c TyposConfiguration) IsEnabled() bool { + return c.Enabled.UnwrapOr(true) +} + /////////////////////////////////////////////////////////////////////////////// // GitHub workflow configuration @@ -214,6 +228,11 @@ func (s ShellCheckConfiguration) AllIgnorePaths(g GolangConfiguration) []string return s.IgnorePaths } +// IsEnabled encodes that the default state for the Enabled field is `true`. +func (s ShellCheckConfiguration) IsEnabled() bool { + return s.Enabled.UnwrapOr(true) +} + type PackageRule struct { MatchPackageNames []string `yaml:"matchPackageNames" json:"matchPackageNames,omitempty"` MatchUpdateTypes []string `yaml:"matchUpdateTypes" json:"matchUpdateTypes,omitempty"` @@ -298,6 +317,10 @@ type NixConfig struct { // Helper functions func (c *Configuration) Validate() { + if len(c.SpellCheck.IgnoreWords) > 0 { + logg.Fatal("SpellCheck/misspell is deprecated, please migrate to typos") + } + if c.Dockerfile.Enabled { if c.Metadata.URL == "" { logg.Fatal("metadata.url must be set when docker.enabled is true") diff --git a/internal/core/constants.go b/internal/core/constants.go index 371652e..25950fc 100644 --- a/internal/core/constants.go +++ b/internal/core/constants.go @@ -65,6 +65,6 @@ const ( GoCoverageReportAction = "fgrosse/go-coverage-report@v1.2.0" GolangciLintAction = "golangci/golangci-lint-action@v9" GoreleaserAction = "goreleaser/goreleaser-action@v6" - MisspellAction = "reviewdog/action-misspell@v1" ReuseAction = "fsfe/reuse-action@v6" + TyposAction = "crate-ci/typos@v1" ) diff --git a/internal/ghworkflow/workflow_checks.go b/internal/ghworkflow/workflow_checks.go index c28d6d9..d942660 100644 --- a/internal/ghworkflow/workflow_checks.go +++ b/internal/ghworkflow/workflow_checks.go @@ -4,9 +4,6 @@ package ghworkflow import ( - "fmt" - "strings" - "github.com/sapcc/go-makefile-maker/internal/core" ) @@ -27,7 +24,7 @@ func checksWorkflow(cfg core.Configuration) { }, }) - if cfg.ShellCheck.Enabled.UnwrapOr(true) { + if cfg.ShellCheck.IsEnabled() { // delete the pretty out of date installed version of shellcheck so that make install-shellcheck installs the current version if !ghwCfg.IsSelfHostedRunner { j.addStep(jobStep{ @@ -48,27 +45,19 @@ func checksWorkflow(cfg core.Configuration) { }) } - if !ghwCfg.IsSelfHostedRunner { - with := map[string]any{ - "exclude": "./vendor/*", - "reporter": "github-check", - "fail_on_error": true, - "github_token": "${{ secrets.GITHUB_TOKEN }}", - "ignore": "importas", //nolint:misspell //importas is a valid linter name, so we always ignore it - } - ignoreWords := cfg.SpellCheck.IgnoreWords - if len(ignoreWords) > 0 { - with["ignore"] = fmt.Sprintf("%s,%s", with["ignore"], strings.Join(ignoreWords, ",")) - } - - w.Permissions.Checks = tokenScopeWrite // for nicer output in pull request diffs - w.Permissions.PullRequests = tokenScopeRead // for private repos - j.addStep(jobStep{ - Name: "Check for spelling errors", - Uses: core.MisspellAction, - With: with, - }) - } + j.addStep(jobStep{ + Name: "Check for spelling errors", + Uses: core.TyposAction, + Env: map[string]string{ + "CLICOLOR": "1", + }, + }) + // TyposAction drops its binary into the repository root; if we do not clean this up, + // `reuse lint` will complain about it not having license information + j.addStep(jobStep{ + Name: "Delete typos binary", + Run: "rm typos", + }) if ghwCfg.License.IsEnabled() { j.addStep(jobStep{ diff --git a/internal/golangcilint/golangci.yaml.tmpl b/internal/golangcilint/golangci.yaml.tmpl index 20a6b05..4cc1325 100644 --- a/internal/golangcilint/golangci.yaml.tmpl +++ b/internal/golangcilint/golangci.yaml.tmpl @@ -56,7 +56,6 @@ linters: - ineffassign - intrange - iotamixing - - misspell - modernize - nilerr - nolintlint @@ -157,16 +156,9 @@ linters: {{- if .WithControllerGen }} modernize: disable: - # omitzero requires removing omitempty tags in kubernetes api struct types which are nested, which is intepreted by controller-gen and breaks the CRDs. + # omitzero requires removing omitempty tags in kubernetes api struct types which are nested, which is interpreted by controller-gen and breaks the CRDs. - omitzero {{- end }} - {{- if .MisspellIgnoreWords }} - misspell: - ignore-rules: - {{- range .MisspellIgnoreWords }} - - {{ . }} - {{- end }} - {{- end }} perfsprint: # modernize generates nicer fix code concat-loop: false diff --git a/internal/golangcilint/golangci_lint.go b/internal/golangcilint/golangci_lint.go index d8c72a3..b94f604 100644 --- a/internal/golangcilint/golangci_lint.go +++ b/internal/golangcilint/golangci_lint.go @@ -28,14 +28,13 @@ func RenderConfig(cfg core.Configuration, sr golang.ScanResult) { } must.Succeed(util.WriteFileFromTemplate(".golangci.yaml", configTemplate, map[string]any{ - "EnableVendoring": cfg.Golang.EnableVendoring, - "GoMinorVersion": must.Return(strconv.Atoi(strings.Split(sr.GoVersion, ".")[1])), - "ModulePath": sr.ModulePath, - "MisspellIgnoreWords": cfg.SpellCheck.IgnoreWords, - "ErrcheckExcludes": cfg.GolangciLint.ErrcheckExcludes, - "SkipDirs": cfg.GolangciLint.SkipDirs, - "Timeout": timeout, - "ReviveRules": cfg.GolangciLint.ReviveRules, - "WithControllerGen": cfg.ControllerGen.Enabled.UnwrapOr(sr.KubernetesController), + "EnableVendoring": cfg.Golang.EnableVendoring, + "GoMinorVersion": must.Return(strconv.Atoi(strings.Split(sr.GoVersion, ".")[1])), + "ModulePath": sr.ModulePath, + "ErrcheckExcludes": cfg.GolangciLint.ErrcheckExcludes, + "SkipDirs": cfg.GolangciLint.SkipDirs, + "Timeout": timeout, + "ReviveRules": cfg.GolangciLint.ReviveRules, + "WithControllerGen": cfg.ControllerGen.Enabled.UnwrapOr(sr.KubernetesController), })) } diff --git a/internal/makefile/makefile.go b/internal/makefile/makefile.go index cc0b9a5..f20f7c1 100644 --- a/internal/makefile/makefile.go +++ b/internal/makefile/makefile.go @@ -113,7 +113,7 @@ endif prepareStaticRecipe = append(prepareStaticRecipe, "install-goimports", "install-golangci-lint") } - if cfg.ShellCheck.Enabled.UnwrapOr(true) { + if cfg.ShellCheck.IsEnabled() { prepare.addRule(rule{ description: "Install shellcheck required by run-shellcheck/static-check", phony: true, @@ -348,7 +348,7 @@ endif }, }) - if cfg.ShellCheck.Enabled.UnwrapOr(true) { + if cfg.ShellCheck.IsEnabled() { // add target to run shellcheck var ignorePathArgs strings.Builder for _, path := range cfg.ShellCheck.AllIgnorePaths(cfg.Golang) { @@ -375,6 +375,19 @@ endif }) } + if cfg.Typos.IsEnabled() { + test.addRule(rule{ + description: "Check for spelling errors using typos.", + phony: true, + target: "run-typos", + recipe: []string{ + `@printf "\e[1;36m>> typos\e[0m\n"`, + `@printf "\e[1;36m>> Typos install instructions can be found here https://github.com/crate-ci/typos#install \e[0m\n"`, + `@typos`, + }, + }) + } + // add targets for test runner incl. coverage report testRule := rule{ description: "Run tests and generate coverage report.", diff --git a/internal/nix/nix-shell.go b/internal/nix/nix-shell.go index f060b41..f6bc927 100644 --- a/internal/nix/nix-shell.go +++ b/internal/nix/nix-shell.go @@ -50,9 +50,12 @@ func RenderShell(cfg core.Configuration, sr golang.ScanResult, renderGoreleaserC if cfg.Renovate.Enabled { packages = append(packages, "renovate") } - if cfg.Reuse.Enabled.UnwrapOr(true) { + if cfg.Reuse.IsEnabled() { packages = append(packages, "reuse") } + if cfg.Typos.IsEnabled() { + packages = append(packages, "typos") + } packages = append(packages, cfg.Nix.ExtraPackages...) slices.Sort(packages) diff --git a/internal/typos/typos.go b/internal/typos/typos.go new file mode 100644 index 0000000..0b502f0 --- /dev/null +++ b/internal/typos/typos.go @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company +// SPDX-License-Identifier: Apache-2.0 + +package typos + +import ( + _ "embed" + + "github.com/sapcc/go-bits/must" + + "github.com/sapcc/go-makefile-maker/internal/core" + "github.com/sapcc/go-makefile-maker/internal/util" +) + +var ( + //go:embed typos.toml.tmpl + typosConfigTemplate string +) + +func RenderConfig(cfg core.Configuration) { + extendExcludes := []string{"go.mod"} + if cfg.Golang.EnableVendoring { + extendExcludes = append(extendExcludes, "vendor/") + } + extendExcludes = append(extendExcludes, cfg.Typos.ExtendExcludes...) + + must.Succeed(util.WriteFileFromTemplate(".typos.toml", typosConfigTemplate, map[string]any{ + "ExtendExcludes": extendExcludes, + "ExtendWords": cfg.Typos.ExtendWords, + })) +} diff --git a/internal/typos/typos.toml.tmpl b/internal/typos/typos.toml.tmpl new file mode 100644 index 0000000..d1772a3 --- /dev/null +++ b/internal/typos/typos.toml.tmpl @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2025 SAP SE +# +# SPDX-License-Identifier: Apache-2.0 + +[default.extend-words] +{{ range $key, $value := .ExtendWords -}} +{{$key}} = "{{$value}}" +{{ end }} +[files] +extend-exclude = [ +{{- range .ExtendExcludes}} + "{{.}}",{{ end }} +] diff --git a/main.go b/main.go index dd46292..313fc55 100644 --- a/main.go +++ b/main.go @@ -24,6 +24,7 @@ import ( "github.com/sapcc/go-makefile-maker/internal/nix" "github.com/sapcc/go-makefile-maker/internal/renovate" "github.com/sapcc/go-makefile-maker/internal/reuse" + "github.com/sapcc/go-makefile-maker/internal/typos" ) func main() { @@ -130,4 +131,10 @@ func main() { logg.Debug("rendering REUSE configuration") reuse.RenderConfig(cfg, sr) } + + // Render typos config file + if cfg.Typos.IsEnabled() { + logg.Debug("rendering typos configuration") + typos.RenderConfig(cfg) + } } diff --git a/shell.nix b/shell.nix index f40e2f4..05de761 100644 --- a/shell.nix +++ b/shell.nix @@ -15,6 +15,7 @@ mkShell { renovate renovate reuse + typos # keep this line if you use bash bashInteractive ];