feat: add verify command (#4527)
This commit is contained in:
parent
6709c974a4
commit
eaafdf3623
1
.github/workflows/pr.yml
vendored
1
.github/workflows/pr.yml
vendored
@ -166,6 +166,7 @@ jobs:
|
|||||||
|
|
||||||
- run: ./golangci-lint config
|
- run: ./golangci-lint config
|
||||||
- run: ./golangci-lint config path
|
- run: ./golangci-lint config path
|
||||||
|
- run: ./golangci-lint config verify --schema jsonschema/golangci.jsonschema.json
|
||||||
|
|
||||||
- run: ./golangci-lint help
|
- run: ./golangci-lint help
|
||||||
- run: ./golangci-lint help linters
|
- run: ./golangci-lint help linters
|
||||||
|
@ -163,6 +163,8 @@ issues:
|
|||||||
text: "SA1019: c.cfg.Run.ShowStats is deprecated: use Output.ShowStats instead."
|
text: "SA1019: c.cfg.Run.ShowStats is deprecated: use Output.ShowStats instead."
|
||||||
- path: pkg/golinters/govet.go
|
- path: pkg/golinters/govet.go
|
||||||
text: "SA1019: cfg.CheckShadowing is deprecated: the linter should be enabled inside `Enable`."
|
text: "SA1019: cfg.CheckShadowing is deprecated: the linter should be enabled inside `Enable`."
|
||||||
|
- path: pkg/commands/config.go
|
||||||
|
text: "SA1019: cfg.Run.UseDefaultSkipDirs is deprecated: use Issues.UseDefaultExcludeDirs instead."
|
||||||
|
|
||||||
- path: pkg/golinters
|
- path: pkg/golinters
|
||||||
linters:
|
linters:
|
||||||
|
2
Makefile
2
Makefile
@ -89,7 +89,7 @@ go.mod: FORCE
|
|||||||
go.sum: go.mod
|
go.sum: go.mod
|
||||||
|
|
||||||
website_copy_jsonschema:
|
website_copy_jsonschema:
|
||||||
cp -r ./jsonschema ./docs/static
|
go run ./scripts/website/copy_jsonschema/
|
||||||
.PHONY: website_copy_jsonschema
|
.PHONY: website_copy_jsonschema
|
||||||
|
|
||||||
website_expand_templates:
|
website_expand_templates:
|
||||||
|
2
go.mod
2
go.mod
@ -80,6 +80,7 @@ require (
|
|||||||
github.com/nishanths/exhaustive v0.12.0
|
github.com/nishanths/exhaustive v0.12.0
|
||||||
github.com/nishanths/predeclared v0.2.2
|
github.com/nishanths/predeclared v0.2.2
|
||||||
github.com/nunnatsa/ginkgolinter v0.16.1
|
github.com/nunnatsa/ginkgolinter v0.16.1
|
||||||
|
github.com/pelletier/go-toml/v2 v2.1.1
|
||||||
github.com/polyfloyd/go-errorlint v1.4.8
|
github.com/polyfloyd/go-errorlint v1.4.8
|
||||||
github.com/quasilyte/go-ruleguard/dsl v0.3.22
|
github.com/quasilyte/go-ruleguard/dsl v0.3.22
|
||||||
github.com/ryancurrah/gomodguard v1.3.1
|
github.com/ryancurrah/gomodguard v1.3.1
|
||||||
@ -161,7 +162,6 @@ require (
|
|||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||||
github.com/prometheus/client_golang v1.12.1 // indirect
|
github.com/prometheus/client_golang v1.12.1 // indirect
|
||||||
|
4
go.sum
generated
4
go.sum
generated
@ -410,8 +410,8 @@ github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT9
|
|||||||
github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
|
github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
|
||||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
|
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
|
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
@ -17,13 +18,19 @@ type configCommand struct {
|
|||||||
viper *viper.Viper
|
viper *viper.Viper
|
||||||
cmd *cobra.Command
|
cmd *cobra.Command
|
||||||
|
|
||||||
|
opts config.LoaderOptions
|
||||||
|
verifyOpts verifyOptions
|
||||||
|
|
||||||
|
buildInfo BuildInfo
|
||||||
|
|
||||||
log logutils.Log
|
log logutils.Log
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConfigCommand(log logutils.Log) *configCommand {
|
func newConfigCommand(log logutils.Log, info BuildInfo) *configCommand {
|
||||||
c := &configCommand{
|
c := &configCommand{
|
||||||
viper: viper.New(),
|
viper: viper.New(),
|
||||||
log: log,
|
log: log,
|
||||||
|
buildInfo: info,
|
||||||
}
|
}
|
||||||
|
|
||||||
configCmd := &cobra.Command{
|
configCmd := &cobra.Command{
|
||||||
@ -33,6 +40,15 @@ func newConfigCommand(log logutils.Log) *configCommand {
|
|||||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||||
return cmd.Help()
|
return cmd.Help()
|
||||||
},
|
},
|
||||||
|
PersistentPreRunE: c.preRunE,
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyCommand := &cobra.Command{
|
||||||
|
Use: "verify",
|
||||||
|
Short: "Verify configuration against JSON schema",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
ValidArgsFunction: cobra.NoFileCompletions,
|
||||||
|
RunE: c.executeVerify,
|
||||||
}
|
}
|
||||||
|
|
||||||
configCmd.AddCommand(
|
configCmd.AddCommand(
|
||||||
@ -41,11 +57,21 @@ func newConfigCommand(log logutils.Log) *configCommand {
|
|||||||
Short: "Print used config path",
|
Short: "Print used config path",
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
ValidArgsFunction: cobra.NoFileCompletions,
|
ValidArgsFunction: cobra.NoFileCompletions,
|
||||||
Run: c.execute,
|
Run: c.executePath,
|
||||||
PreRunE: c.preRunE,
|
|
||||||
},
|
},
|
||||||
|
verifyCommand,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
flagSet := configCmd.PersistentFlags()
|
||||||
|
flagSet.SortFlags = false // sort them as they are defined here
|
||||||
|
|
||||||
|
setupConfigFileFlagSet(flagSet, &c.opts)
|
||||||
|
|
||||||
|
// ex: --schema jsonschema/golangci.next.jsonschema.json
|
||||||
|
verifyFlagSet := verifyCommand.Flags()
|
||||||
|
verifyFlagSet.StringVar(&c.verifyOpts.schemaURL, "schema", "", color.GreenString("JSON schema URL"))
|
||||||
|
_ = verifyFlagSet.MarkHidden("schema")
|
||||||
|
|
||||||
c.cmd = configCmd
|
c.cmd = configCmd
|
||||||
|
|
||||||
return c
|
return c
|
||||||
@ -54,7 +80,16 @@ func newConfigCommand(log logutils.Log) *configCommand {
|
|||||||
func (c *configCommand) preRunE(cmd *cobra.Command, _ []string) error {
|
func (c *configCommand) preRunE(cmd *cobra.Command, _ []string) error {
|
||||||
// The command doesn't depend on the real configuration.
|
// The command doesn't depend on the real configuration.
|
||||||
// It only needs to know the path of the configuration file.
|
// It only needs to know the path of the configuration file.
|
||||||
loader := config.NewLoader(c.log.Child(logutils.DebugKeyConfigReader), c.viper, cmd.Flags(), config.LoaderOptions{}, config.NewDefault())
|
cfg := config.NewDefault()
|
||||||
|
|
||||||
|
// Hack to hide deprecation messages related to `--skip-dirs-use-default`:
|
||||||
|
// Flags are not bound then the default values, defined only through flags, are not applied.
|
||||||
|
// In this command, file path and file information are the only requirements, i.e. it don't need flag values.
|
||||||
|
//
|
||||||
|
// TODO(ldez) add an option (check deprecation) to `Loader.Load()` but this require a dedicated PR.
|
||||||
|
cfg.Run.UseDefaultSkipDirs = true
|
||||||
|
|
||||||
|
loader := config.NewLoader(c.log.Child(logutils.DebugKeyConfigReader), c.viper, cmd.Flags(), c.opts, cfg)
|
||||||
|
|
||||||
if err := loader.Load(); err != nil {
|
if err := loader.Load(); err != nil {
|
||||||
return fmt.Errorf("can't load config: %w", err)
|
return fmt.Errorf("can't load config: %w", err)
|
||||||
@ -63,14 +98,14 @@ func (c *configCommand) preRunE(cmd *cobra.Command, _ []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *configCommand) execute(_ *cobra.Command, _ []string) {
|
func (c *configCommand) executePath(cmd *cobra.Command, _ []string) {
|
||||||
usedConfigFile := c.getUsedConfig()
|
usedConfigFile := c.getUsedConfig()
|
||||||
if usedConfigFile == "" {
|
if usedConfigFile == "" {
|
||||||
c.log.Warnf("No config file detected")
|
c.log.Warnf("No config file detected")
|
||||||
os.Exit(exitcodes.NoConfigFileDetected)
|
os.Exit(exitcodes.NoConfigFileDetected)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(usedConfigFile)
|
cmd.Println(usedConfigFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getUsedConfig returns the resolved path to the golangci config file,
|
// getUsedConfig returns the resolved path to the golangci config file,
|
||||||
|
176
pkg/commands/config_verify.go
Normal file
176
pkg/commands/config_verify.go
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
hcversion "github.com/hashicorp/go-version"
|
||||||
|
"github.com/pelletier/go-toml/v2"
|
||||||
|
"github.com/santhosh-tekuri/jsonschema/v5"
|
||||||
|
_ "github.com/santhosh-tekuri/jsonschema/v5/httploader"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
"github.com/golangci/golangci-lint/pkg/exitcodes"
|
||||||
|
)
|
||||||
|
|
||||||
|
type verifyOptions struct {
|
||||||
|
schemaURL string // For debugging purpose only (Flag only).
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *configCommand) executeVerify(cmd *cobra.Command, _ []string) error {
|
||||||
|
usedConfigFile := c.getUsedConfig()
|
||||||
|
if usedConfigFile == "" {
|
||||||
|
c.log.Warnf("No config file detected")
|
||||||
|
os.Exit(exitcodes.NoConfigFileDetected)
|
||||||
|
}
|
||||||
|
|
||||||
|
schemaURL, err := createSchemaURL(cmd.Flags(), c.buildInfo)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("get JSON schema: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateConfiguration(schemaURL, usedConfigFile)
|
||||||
|
if err != nil {
|
||||||
|
var v *jsonschema.ValidationError
|
||||||
|
if !errors.As(err, &v) {
|
||||||
|
return fmt.Errorf("[%s] validate: %w", usedConfigFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
detail := v.DetailedOutput()
|
||||||
|
|
||||||
|
printValidationDetail(cmd, &detail)
|
||||||
|
|
||||||
|
return fmt.Errorf("the configuration contains invalid elements")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSchemaURL(flags *pflag.FlagSet, buildInfo BuildInfo) (string, error) {
|
||||||
|
schemaURL, err := flags.GetString("schema")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("get schema flag: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if schemaURL != "" {
|
||||||
|
return schemaURL, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case buildInfo.Version != "" && buildInfo.Version != "(devel)":
|
||||||
|
version, err := hcversion.NewVersion(buildInfo.Version)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("parse version: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
schemaURL = fmt.Sprintf("https://golangci-lint.run/jsonschema/golangci.v%d.%d.jsonschema.json",
|
||||||
|
version.Segments()[0], version.Segments()[1])
|
||||||
|
|
||||||
|
case buildInfo.Commit != "" && buildInfo.Commit != "?":
|
||||||
|
if buildInfo.Commit == "unknown" {
|
||||||
|
return "", errors.New("unknown commit information")
|
||||||
|
}
|
||||||
|
|
||||||
|
commit := buildInfo.Commit
|
||||||
|
|
||||||
|
if strings.HasPrefix(commit, "(") {
|
||||||
|
c, _, ok := strings.Cut(strings.TrimPrefix(commit, "("), ",")
|
||||||
|
if !ok {
|
||||||
|
return "", errors.New("commit information not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
commit = c
|
||||||
|
}
|
||||||
|
|
||||||
|
schemaURL = fmt.Sprintf("https://raw.githubusercontent.com/golangci/golangci-lint/%s/jsonschema/golangci.next.jsonschema.json",
|
||||||
|
commit)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "", errors.New("version not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return schemaURL, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateConfiguration(schemaPath, targetFile string) error {
|
||||||
|
compiler := jsonschema.NewCompiler()
|
||||||
|
compiler.Draft = jsonschema.Draft7
|
||||||
|
|
||||||
|
schema, err := compiler.Compile(schemaPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("compile schema: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var m any
|
||||||
|
|
||||||
|
switch strings.ToLower(filepath.Ext(targetFile)) {
|
||||||
|
case ".yaml", ".yml", ".json":
|
||||||
|
m, err = decodeYamlFile(targetFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case ".toml":
|
||||||
|
m, err = decodeTomlFile(targetFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
// unsupported
|
||||||
|
return errors.New("unsupported configuration format")
|
||||||
|
}
|
||||||
|
|
||||||
|
return schema.Validate(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printValidationDetail(cmd *cobra.Command, detail *jsonschema.Detailed) {
|
||||||
|
if detail.Error != "" {
|
||||||
|
cmd.PrintErrf("jsonschema: %q does not validate with %q: %s\n",
|
||||||
|
strings.ReplaceAll(strings.TrimPrefix(detail.InstanceLocation, "/"), "/", "."), detail.KeywordLocation, detail.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, d := range detail.Errors {
|
||||||
|
d := d
|
||||||
|
printValidationDetail(cmd, &d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeYamlFile(filename string) (any, error) {
|
||||||
|
file, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("[%s] file open: %w", filename, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() { _ = file.Close() }()
|
||||||
|
|
||||||
|
var m any
|
||||||
|
err = yaml.NewDecoder(file).Decode(&m)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("[%s] YAML decode: %w", filename, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeTomlFile(filename string) (any, error) {
|
||||||
|
file, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("[%s] file open: %w", filename, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() { _ = file.Close() }()
|
||||||
|
|
||||||
|
var m any
|
||||||
|
err = toml.NewDecoder(file).Decode(&m)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("[%s] TOML decode: %w", filename, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
140
pkg/commands/config_verify_test.go
Normal file
140
pkg/commands/config_verify_test.go
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_createSchemaURL(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
flag string
|
||||||
|
info BuildInfo
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "schema flag only",
|
||||||
|
flag: "https://example.com",
|
||||||
|
expected: "https://example.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "schema flag and build info",
|
||||||
|
flag: "https://example.com",
|
||||||
|
info: BuildInfo{
|
||||||
|
Version: "v1.0.0",
|
||||||
|
Commit: "cd8b11773c6c1f595e8eb98c0d4310af20ae20df",
|
||||||
|
},
|
||||||
|
expected: "https://example.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "version and commit",
|
||||||
|
info: BuildInfo{
|
||||||
|
Version: "v1.0.0",
|
||||||
|
Commit: "cd8b11773c6c1f595e8eb98c0d4310af20ae20df",
|
||||||
|
},
|
||||||
|
expected: "https://golangci-lint.run/jsonschema/golangci.v1.0.jsonschema.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "commit only",
|
||||||
|
info: BuildInfo{
|
||||||
|
Commit: "cd8b11773c6c1f595e8eb98c0d4310af20ae20df",
|
||||||
|
},
|
||||||
|
expected: "https://raw.githubusercontent.com/golangci/golangci-lint/cd8b11773c6c1f595e8eb98c0d4310af20ae20df/jsonschema/golangci.next.jsonschema.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "version devel and commit",
|
||||||
|
info: BuildInfo{
|
||||||
|
Version: "(devel)",
|
||||||
|
Commit: "cd8b11773c6c1f595e8eb98c0d4310af20ae20df",
|
||||||
|
},
|
||||||
|
expected: "https://raw.githubusercontent.com/golangci/golangci-lint/cd8b11773c6c1f595e8eb98c0d4310af20ae20df/jsonschema/golangci.next.jsonschema.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "composite commit info",
|
||||||
|
info: BuildInfo{
|
||||||
|
Version: "",
|
||||||
|
Commit: `(cd8b11773c6c1f595e8eb98c0d4310af20ae20df, modified: "false", mod sum: "123")`,
|
||||||
|
},
|
||||||
|
expected: "https://raw.githubusercontent.com/golangci/golangci-lint/cd8b11773c6c1f595e8eb98c0d4310af20ae20df/jsonschema/golangci.next.jsonschema.json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
||||||
|
flags.String("schema", "", "")
|
||||||
|
if test.flag != "" {
|
||||||
|
_ = flags.Set("schema", test.flag)
|
||||||
|
}
|
||||||
|
|
||||||
|
schemaURL, err := createSchemaURL(flags, test.info)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expected, schemaURL)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_createSchemaURL_error(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
info BuildInfo
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "commit unknown",
|
||||||
|
info: BuildInfo{
|
||||||
|
Commit: "unknown",
|
||||||
|
},
|
||||||
|
expected: "unknown commit information",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "commit ?",
|
||||||
|
info: BuildInfo{
|
||||||
|
Commit: "?",
|
||||||
|
},
|
||||||
|
expected: "version not found",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "version devel only",
|
||||||
|
info: BuildInfo{
|
||||||
|
Version: "(devel)",
|
||||||
|
},
|
||||||
|
expected: "version not found",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "invalid version",
|
||||||
|
info: BuildInfo{
|
||||||
|
Version: "example",
|
||||||
|
},
|
||||||
|
expected: "parse version: Malformed version: example",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "invalid composite commit info",
|
||||||
|
info: BuildInfo{
|
||||||
|
Version: "",
|
||||||
|
Commit: `(cd8b11773c6c1f595e8eb98c0d4310af20ae20df)`,
|
||||||
|
},
|
||||||
|
expected: "commit information not found",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
||||||
|
flags.String("schema", "", "")
|
||||||
|
|
||||||
|
_, err := createSchemaURL(flags, test.info)
|
||||||
|
require.EqualError(t, err, test.expected)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -61,7 +61,7 @@ func newRootCommand(info BuildInfo) *rootCommand {
|
|||||||
newLintersCommand(log).cmd,
|
newLintersCommand(log).cmd,
|
||||||
newRunCommand(log, info).cmd,
|
newRunCommand(log, info).cmd,
|
||||||
newCacheCommand().cmd,
|
newCacheCommand().cmd,
|
||||||
newConfigCommand(log).cmd,
|
newConfigCommand(log, info).cmd,
|
||||||
newVersionCommand(info).cmd,
|
newVersionCommand(info).cmd,
|
||||||
newCustomCommand(log).cmd,
|
newCustomCommand(log).cmd,
|
||||||
)
|
)
|
||||||
|
100
scripts/website/copy_jsonschema/main.go
Normal file
100
scripts/website/copy_jsonschema/main.go
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
hcversion "github.com/hashicorp/go-version"
|
||||||
|
|
||||||
|
"github.com/golangci/golangci-lint/scripts/website/github"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
err := copySchemas()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func copySchemas() error {
|
||||||
|
dstDir := filepath.FromSlash("docs/static/jsonschema/")
|
||||||
|
|
||||||
|
err := os.RemoveAll(dstDir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("remove dir: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.MkdirAll(dstDir, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("make dir: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The key is the destination file.
|
||||||
|
// The value is the source file.
|
||||||
|
files := map[string]string{}
|
||||||
|
|
||||||
|
entries, err := os.ReadDir("jsonschema")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("read dir: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
if strings.HasSuffix(entry.Name(), ".jsonschema.json") {
|
||||||
|
files[entry.Name()] = entry.Name()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
latest, err := github.GetLatestVersion()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("get latest release version: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
version, err := hcversion.NewVersion(latest)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("parse version: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
versioned := fmt.Sprintf("golangci.v%d.%d.jsonschema.json", version.Segments()[0], version.Segments()[1])
|
||||||
|
files[versioned] = "golangci.jsonschema.json"
|
||||||
|
|
||||||
|
for dst, src := range files {
|
||||||
|
err := copyFile(filepath.Join("jsonschema", src), filepath.Join(dstDir, dst))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("copy files: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFile(src, dst string) error {
|
||||||
|
source, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("open file %s: %w", src, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() { _ = source.Close() }()
|
||||||
|
|
||||||
|
info, err := source.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("file %s not found: %w", src, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
destination, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, info.Mode())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create file %s: %w", dst, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() { _ = destination.Close() }()
|
||||||
|
|
||||||
|
_, err = io.Copy(destination, source)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("copy file %s to %s: %w", src, dst, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -3,15 +3,13 @@ package main
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/golangci/golangci-lint/internal/renameio"
|
"github.com/golangci/golangci-lint/internal/renameio"
|
||||||
|
"github.com/golangci/golangci-lint/scripts/website/github"
|
||||||
"github.com/golangci/golangci-lint/scripts/website/types"
|
"github.com/golangci/golangci-lint/scripts/website/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -89,46 +87,6 @@ func processDoc(path string, replacements map[string]string, madeReplacements ma
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type latestRelease struct {
|
|
||||||
TagName string `json:"tag_name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func getLatestVersion() (string, error) {
|
|
||||||
endpoint := "https://api.github.com/repos/golangci/golangci-lint/releases/latest"
|
|
||||||
|
|
||||||
//nolint:noctx // request timeout handled by the client
|
|
||||||
req, err := http.NewRequest(http.MethodGet, endpoint, http.NoBody)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("prepare a HTTP request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Set("Accept", "application/vnd.github.v3+json")
|
|
||||||
|
|
||||||
client := &http.Client{Timeout: 2 * time.Second}
|
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("get HTTP response for the latest tag: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("read a body for the latest tag: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
release := latestRelease{}
|
|
||||||
|
|
||||||
err = json.Unmarshal(body, &release)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("unmarshal the body for the latest tag: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return release.TagName, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildTemplateContext() (map[string]string, error) {
|
func buildTemplateContext() (map[string]string, error) {
|
||||||
snippets, err := getExampleSnippets()
|
snippets, err := getExampleSnippets()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -150,7 +108,7 @@ func buildTemplateContext() (map[string]string, error) {
|
|||||||
return nil, fmt.Errorf("read CHANGELOG.md: %w", err)
|
return nil, fmt.Errorf("read CHANGELOG.md: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
latestVersion, err := getLatestVersion()
|
latestVersion, err := github.GetLatestVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("get the latest version: %w", err)
|
return nil, fmt.Errorf("get the latest version: %w", err)
|
||||||
}
|
}
|
||||||
|
49
scripts/website/github/github.go
Normal file
49
scripts/website/github/github.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package github
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const endpoint = "https://api.github.com/repos/golangci/golangci-lint/releases/latest"
|
||||||
|
|
||||||
|
type releaseInfo struct {
|
||||||
|
TagName string `json:"tag_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLatestVersion gets latest release information.
|
||||||
|
func GetLatestVersion() (string, error) {
|
||||||
|
//nolint:noctx // request timeout handled by the client
|
||||||
|
req, err := http.NewRequest(http.MethodGet, endpoint, http.NoBody)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("prepare a HTTP request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Accept", "application/vnd.github.v3+json")
|
||||||
|
|
||||||
|
client := &http.Client{Timeout: 2 * time.Second}
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("get HTTP response for the latest tag: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("read a body for the latest tag: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
release := releaseInfo{}
|
||||||
|
|
||||||
|
err = json.Unmarshal(body, &release)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("unmarshal the body for the latest tag: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return release.TagName, nil
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user