Skip to content

Commit e589139

Browse files
authored
feat: add config path placeholder (#5650)
1 parent d0b46eb commit e589139

File tree

10 files changed

+319
-258
lines changed

10 files changed

+319
-258
lines changed

.golangci.next.reference.yml

+3
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,7 @@ linters:
335335
# List of file globs that will match this list of settings to compare against.
336336
# By default, if a path is relative, it is relative to the directory where the golangci-lint command is executed.
337337
# The placeholder '${base-path}' is substituted with a path relative to the mode defined with `run.relative-path-mode`.
338+
# The placeholder '${config-path}' is substituted with a path relative to the configuration file.
338339
# Default: $all
339340
files:
340341
- "!**/*_a _file.go"
@@ -1161,6 +1162,7 @@ linters:
11611162
# Comma-separated list of file paths containing ruleguard rules.
11621163
# By default, if a path is relative, it is relative to the directory where the golangci-lint command is executed.
11631164
# The placeholder '${base-path}' is substituted with a path relative to the mode defined with `run.relative-path-mode`.
1165+
# The placeholder '${config-path}' is substituted with a path relative to the configuration file.
11641166
# Glob patterns such as 'rules-*.go' may be specified.
11651167
# Default: ""
11661168
rules: '${base-path}/ruleguard/rules-*.go,${base-path}/myrule1.go'
@@ -1256,6 +1258,7 @@ linters:
12561258
# Useful if you need to load the template from a specific file.
12571259
# By default, if a path is relative, it is relative to the directory where the golangci-lint command is executed.
12581260
# The placeholder '${base-path}' is substituted with a path relative to the mode defined with `run.relative-path-mode`.
1261+
# The placeholder '${config-path}' is substituted with a path relative to the configuration file.
12591262
# Default: ""
12601263
template-path: /path/to/my/template.tmpl
12611264

pkg/config/placeholders.go

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package config
2+
3+
import "strings"
4+
5+
const (
6+
// placeholderBasePath used inside linters to evaluate relative paths.
7+
placeholderBasePath = "${base-path}"
8+
9+
// placeholderConfigPath used inside linters to paths relative to configuration.
10+
placeholderConfigPath = "${config-path}"
11+
)
12+
13+
func NewPlaceholderReplacer(cfg *Config) *strings.Replacer {
14+
return strings.NewReplacer(
15+
placeholderBasePath, cfg.GetBasePath(),
16+
placeholderConfigPath, cfg.GetConfigDir(),
17+
)
18+
}

pkg/golinters/depguard/depguard.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,17 @@ import (
88

99
"github.com/golangci/golangci-lint/v2/pkg/config"
1010
"github.com/golangci/golangci-lint/v2/pkg/goanalysis"
11-
"github.com/golangci/golangci-lint/v2/pkg/golinters/internal"
1211
"github.com/golangci/golangci-lint/v2/pkg/lint/linter"
1312
)
1413

15-
func New(settings *config.DepGuardSettings, basePath string) *goanalysis.Linter {
14+
func New(settings *config.DepGuardSettings, replacer *strings.Replacer) *goanalysis.Linter {
1615
conf := depguard.LinterSettings{}
1716

1817
if settings != nil {
1918
for s, rule := range settings.Rules {
2019
var extendedPatterns []string
2120
for _, file := range rule.Files {
22-
extendedPatterns = append(extendedPatterns, strings.ReplaceAll(file, internal.PlaceholderBasePath, basePath))
21+
extendedPatterns = append(extendedPatterns, replacer.Replace(file))
2322
}
2423

2524
list := &depguard.List{

pkg/golinters/gocritic/gocritic.go

+7-86
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ import (
55
"fmt"
66
"go/ast"
77
"go/types"
8-
"maps"
9-
"reflect"
108
"runtime"
119
"slices"
1210
"strings"
@@ -19,7 +17,6 @@ import (
1917

2018
"github.com/golangci/golangci-lint/v2/pkg/config"
2119
"github.com/golangci/golangci-lint/v2/pkg/goanalysis"
22-
"github.com/golangci/golangci-lint/v2/pkg/golinters/internal"
2320
"github.com/golangci/golangci-lint/v2/pkg/lint/linter"
2421
"github.com/golangci/golangci-lint/v2/pkg/logutils"
2522
)
@@ -31,7 +28,7 @@ var (
3128
isDebug = logutils.HaveDebugTag(logutils.DebugKeyGoCritic)
3229
)
3330

34-
func New(settings *config.GoCriticSettings) *goanalysis.Linter {
31+
func New(settings *config.GoCriticSettings, replacer *strings.Replacer) *goanalysis.Linter {
3532
wrapper := &goCriticWrapper{
3633
sizes: types.SizesFor("gc", runtime.GOARCH),
3734
}
@@ -58,23 +55,18 @@ Dynamic rules are written declaratively with AST patterns, filters, report messa
5855
nil,
5956
).
6057
WithContextSetter(func(context *linter.Context) {
61-
wrapper.replacer = strings.NewReplacer(
62-
internal.PlaceholderBasePath, context.Cfg.GetBasePath(),
63-
)
64-
65-
wrapper.init(context.Log, settings)
58+
wrapper.init(context.Log, settings, replacer)
6659
}).
6760
WithLoadMode(goanalysis.LoadModeTypesInfo)
6861
}
6962

7063
type goCriticWrapper struct {
7164
settingsWrapper *settingsWrapper
72-
replacer *strings.Replacer
7365
sizes types.Sizes
7466
once sync.Once
7567
}
7668

77-
func (w *goCriticWrapper) init(logger logutils.Log, settings *config.GoCriticSettings) {
69+
func (w *goCriticWrapper) init(logger logutils.Log, settings *config.GoCriticSettings, replacer *strings.Replacer) {
7870
if settings == nil {
7971
return
8072
}
@@ -86,12 +78,9 @@ func (w *goCriticWrapper) init(logger logutils.Log, settings *config.GoCriticSet
8678
}
8779
})
8880

89-
settingsWrapper := newSettingsWrapper(settings, logger)
90-
settingsWrapper.InferEnabledChecks()
81+
settingsWrapper := newSettingsWrapper(logger, settings, replacer)
9182

92-
// Validate must be after InferEnabledChecks, not before.
93-
// Because it uses gathered information about tags set and finally enabled checks.
94-
if err := settingsWrapper.Validate(); err != nil {
83+
if err := settingsWrapper.Load(); err != nil {
9584
logger.Fatalf("%s: invalid settings: %s", linterName, err)
9685
}
9786

@@ -140,7 +129,8 @@ func (w *goCriticWrapper) buildEnabledCheckers(linterCtx *gocriticlinter.Context
140129
continue
141130
}
142131

143-
if err := w.configureCheckerInfo(info, allLowerCasedParams); err != nil {
132+
err := w.settingsWrapper.setCheckerParams(info, allLowerCasedParams)
133+
if err != nil {
144134
return nil, err
145135
}
146136

@@ -155,59 +145,6 @@ func (w *goCriticWrapper) buildEnabledCheckers(linterCtx *gocriticlinter.Context
155145
return enabledCheckers, nil
156146
}
157147

158-
func (w *goCriticWrapper) configureCheckerInfo(
159-
info *gocriticlinter.CheckerInfo,
160-
allLowerCasedParams map[string]config.GoCriticCheckSettings,
161-
) error {
162-
params := allLowerCasedParams[strings.ToLower(info.Name)]
163-
if params == nil { // no config for this checker
164-
return nil
165-
}
166-
167-
// To lowercase info param keys here because golangci-lint's config parser lowercases all strings.
168-
infoParams := normalizeMap(info.Params)
169-
for k, p := range params {
170-
v, ok := infoParams[k]
171-
if ok {
172-
v.Value = w.normalizeCheckerParamsValue(p)
173-
continue
174-
}
175-
176-
// param `k` isn't supported
177-
if len(info.Params) == 0 {
178-
return fmt.Errorf("checker %s config param %s doesn't exist: checker doesn't have params",
179-
info.Name, k)
180-
}
181-
182-
supportedKeys := slices.Sorted(maps.Keys(info.Params))
183-
184-
return fmt.Errorf("checker %s config param %s doesn't exist, all existing: %s",
185-
info.Name, k, supportedKeys)
186-
}
187-
188-
return nil
189-
}
190-
191-
// normalizeCheckerParamsValue normalizes value types.
192-
// go-critic asserts that CheckerParam.Value has some specific types,
193-
// but the file parsers (TOML, YAML, JSON) don't create the same representation for raw type.
194-
// then we have to convert value types into the expected value types.
195-
// Maybe in the future, this kind of conversion will be done in go-critic itself.
196-
func (w *goCriticWrapper) normalizeCheckerParamsValue(p any) any {
197-
rv := reflect.ValueOf(p)
198-
switch rv.Type().Kind() {
199-
case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int:
200-
return int(rv.Int())
201-
case reflect.Bool:
202-
return rv.Bool()
203-
case reflect.String:
204-
// Perform variable substitution.
205-
return w.replacer.Replace(rv.String())
206-
default:
207-
return p
208-
}
209-
}
210-
211148
func runOnFile(pass *analysis.Pass, f *ast.File, checks []*gocriticlinter.Checker) {
212149
for _, c := range checks {
213150
// All checkers are expected to use *lint.Context
@@ -233,19 +170,3 @@ func runOnFile(pass *analysis.Pass, f *ast.File, checks []*gocriticlinter.Checke
233170
}
234171
}
235172
}
236-
237-
func normalizeMap[ValueT any](in map[string]ValueT) map[string]ValueT {
238-
ret := make(map[string]ValueT, len(in))
239-
for k, v := range in {
240-
ret[strings.ToLower(k)] = v
241-
}
242-
return ret
243-
}
244-
245-
func isEnabledByDefaultGoCriticChecker(info *gocriticlinter.CheckerInfo) bool {
246-
// https://github.com/go-critic/go-critic/blob/5b67cfd487ae9fe058b4b19321901b3131810f65/cmd/gocritic/check.go#L342-L345
247-
return !info.HasTag(gocriticlinter.ExperimentalTag) &&
248-
!info.HasTag(gocriticlinter.OpinionatedTag) &&
249-
!info.HasTag(gocriticlinter.PerformanceTag) &&
250-
!info.HasTag(gocriticlinter.SecurityTag)
251-
}

0 commit comments

Comments
 (0)