From 49d63eeaab3afc30781fdc8a562395f065c7d55f Mon Sep 17 00:00:00 2001 From: Ruben Hoenle Date: Fri, 15 May 2026 12:42:52 +0200 Subject: [PATCH] feat(ci): add custom linter to prevent stuttering in pkg paths --- .custom-gcl.yml | 5 +++ .gitignore | 1 + Makefile | 1 + golang-ci.yaml | 5 +++ scripts/lint-golangci-lint.sh | 8 ++-- tools/go.mod | 8 ++++ tools/go.sum | 4 ++ tools/linters/pkgstutter/pkgstutter.go | 56 ++++++++++++++++++++++++++ 8 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 .custom-gcl.yml create mode 100644 tools/go.mod create mode 100644 tools/go.sum create mode 100644 tools/linters/pkgstutter/pkgstutter.go diff --git a/.custom-gcl.yml b/.custom-gcl.yml new file mode 100644 index 000000000..5ab7a548c --- /dev/null +++ b/.custom-gcl.yml @@ -0,0 +1,5 @@ +version: v1.62.0 +plugins: + - module: 'github.com/stackitcloud/stackit-sdk-go/tools' + import: 'github.com/stackitcloud/stackit-sdk-go/tools/linters/pkgstutter' + path: ./tools diff --git a/.gitignore b/.gitignore index cd0231d2e..14e046c3f 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ bin/ *.vscode/ go.work.sum +custom-gcl diff --git a/Makefile b/Makefile index b01244db3..53388bb78 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,7 @@ project-tools: ## Install project tools # LINT lint-golangci-lint: ## Lint Go code + @golangci-lint custom @echo ">> Linting with golangci-lint" @$(SCRIPTS_BASE)/lint-golangci-lint.sh "${skip-non-generated-files}" "${service}" diff --git a/golang-ci.yaml b/golang-ci.yaml index 09c28a019..53fbad6bb 100644 --- a/golang-ci.yaml +++ b/golang-ci.yaml @@ -9,6 +9,10 @@ run: # timeout for analysis, e.g. 30s, 5m, default is 1m timeout: 5m linters-settings: + custom: + pkgstutter: + type: module + description: "A custom local linter" goimports: # put imports beginning with prefix after 3rd-party packages; # it's a comma-separated list of prefixes @@ -64,6 +68,7 @@ linters-settings: - dupImport # https://github.com/go-critic/go-critic/issues/845 linters: enable: + - pkgstutter # custom local linter # https://golangci-lint.run/usage/linters/ # default linters - gosimple diff --git a/scripts/lint-golangci-lint.sh b/scripts/lint-golangci-lint.sh index 4be1f46de..6c0c70659 100755 --- a/scripts/lint-golangci-lint.sh +++ b/scripts/lint-golangci-lint.sh @@ -34,9 +34,9 @@ lint_service() { echo ">> Linting service ${service}" cd ${SERVICES_PATH}/${service} if [ "${SKIP_NON_GENERATED_FILES}" = true ]; then - golangci-lint run ${GOLANG_CI_ARGS} --skip-dirs wait # All manually maintained files are in subfolders + ${ROOT_DIR}/custom-gcl run ${GOLANG_CI_ARGS} --skip-dirs wait # All manually maintained files are in subfolders else - golangci-lint run ${GOLANG_CI_ARGS} + ${ROOT_DIR}/custom-gcl run ${GOLANG_CI_ARGS} fi } @@ -48,7 +48,7 @@ else if [ "${SKIP_NON_GENERATED_FILES}" = false ]; then echo ">> Linting core" cd ${CORE_PATH} - golangci-lint run ${GOLANG_CI_ARGS} + ${ROOT_DIR}/custom-gcl run ${GOLANG_CI_ARGS} fi for service_dir in ${SERVICES_PATH}/*; do @@ -61,7 +61,7 @@ else example=$(basename ${example_dir}) echo ">> Linting example ${example}" cd ${example_dir} - golangci-lint run ${GOLANG_CI_ARGS} + ${ROOT_DIR}/custom-gcl run ${GOLANG_CI_ARGS} done fi fi diff --git a/tools/go.mod b/tools/go.mod new file mode 100644 index 000000000..47aebbc60 --- /dev/null +++ b/tools/go.mod @@ -0,0 +1,8 @@ +module github.com/stackitcloud/stackit-sdk-go/tools + +go 1.25.9 + +require ( + github.com/golangci/plugin-module-register v0.1.2 + golang.org/x/tools v0.45.0 +) diff --git a/tools/go.sum b/tools/go.sum new file mode 100644 index 000000000..bdfff8dcb --- /dev/null +++ b/tools/go.sum @@ -0,0 +1,4 @@ +github.com/golangci/plugin-module-register v0.1.2 h1:e5WM6PO6NIAEcij3B053CohVp3HIYbzSuP53UAYgOpg= +github.com/golangci/plugin-module-register v0.1.2/go.mod h1:1+QGTsKBvAIvPvoY/os+G5eoqxWn70HYDm2uvUyGuVw= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= diff --git a/tools/linters/pkgstutter/pkgstutter.go b/tools/linters/pkgstutter/pkgstutter.go new file mode 100644 index 000000000..aaaa1673d --- /dev/null +++ b/tools/linters/pkgstutter/pkgstutter.go @@ -0,0 +1,56 @@ +package pkgstutter + +import ( + "strings" + + "github.com/golangci/plugin-module-register/register" + "golang.org/x/tools/go/analysis" +) + +var Analyzer = &analysis.Analyzer{ + Name: "pkgstutter", + Doc: "Prevents stuttering package names, e.g. github.com/foo/bar/wait/wait", + Run: run, +} + +func run(pass *analysis.Pass) (any, error) { + pkgPath := pass.Pkg.Path() + parts := strings.Split(pkgPath, "/") + + // Check for adjacent identical parts in the path + for i := 1; i < len(parts); i++ { + if parts[i] == parts[i-1] { + // If a stutter is found, report it at the package declaration + // of the first file in the package to pinpoint the error. + if len(pass.Files) > 0 { + pass.Reportf( + pass.Files[0].Package, + "package path %q contains stuttering (%s/%s)", + pkgPath, parts[i-1], parts[i], + ) + } + // Break after the first finding to avoid spamming multiple errors for the same package + break + } + } + + return nil, nil +} + +func init() { + register.Plugin("pkgstutter", New) +} + +func New(settings any) (register.LinterPlugin, error) { + return &plugin{}, nil +} + +type plugin struct{} + +func (p *plugin) BuildAnalyzers() ([]*analysis.Analyzer, error) { + return []*analysis.Analyzer{Analyzer}, nil +} + +func (p *plugin) GetLoadMode() string { + return register.LoadModeTypesInfo +}