lint

package
v0.16.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 29, 2024 License: Apache-2.0 Imports: 21 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var AllRules = func(l *Linter) Rules {
	return Rules{
		{
			Name:        "forbidden-repository-used",
			Description: "do not specify a forbidden repository",
			Severity:    SeverityError,
			LintFunc: func(config config.Configuration) error {
				for _, repo := range config.Environment.Contents.Repositories {
					if slices.Contains(forbiddenRepositories, repo) {
						return fmt.Errorf("forbidden repository %s is used", repo)
					}
				}
				return nil
			},
		},
		{
			Name:        "forbidden-keyring-used",
			Description: "do not specify a forbidden keyring",
			Severity:    SeverityError,
			LintFunc: func(config config.Configuration) error {
				for _, keyring := range config.Environment.Contents.Keyring {
					if slices.Contains(forbiddenKeyrings, keyring) {
						return fmt.Errorf("forbidden keyring %s is used", keyring)
					}
				}
				return nil
			},
		},
		{
			Name:        "valid-copyright-header",
			Description: "every package should have a valid copyright header",
			Severity:    SeverityInfo,
			LintFunc: func(config config.Configuration) error {
				if len(config.Package.Copyright) == 0 {
					return fmt.Errorf("copyright header is missing")
				}
				for _, c := range config.Package.Copyright {
					if c.License == "" {
						return fmt.Errorf("license is missing")
					}
				}
				return nil
			},
		},
		{
			Name:        "contains-epoch",
			Description: "every package should have an epoch",
			Severity:    SeverityError,
			LintFunc: func(_ config.Configuration) error {
				var node yaml.Node
				fileInfo, err := os.Stat(l.options.Path)
				if err != nil {
					return err
				}

				if fileInfo.IsDir() {
					return nil
				}

				yamlData, err := os.ReadFile(l.options.Path)
				if err != nil {
					return err
				}

				err = yaml.Unmarshal(yamlData, &node)
				if err != nil {
					return err
				}

				if node.Content == nil {
					return fmt.Errorf("config %s has no yaml content", l.options.Path)
				}

				pkg, err := renovate.NodeFromMapping(node.Content[0], "package")
				if err != nil {
					return err
				}

				if pkg == nil {
					return fmt.Errorf("config %s has no package content", l.options.Path)
				}

				err = containsKey(pkg, "epoch")
				if err != nil {
					return fmt.Errorf("config %s has no package.epoch", l.options.Path)
				}

				return nil
			},
		},
		{
			Name:        "valid-pipeline-fetch-uri",
			Description: "every fetch pipeline should have a valid uri",
			Severity:    SeverityError,
			LintFunc: func(config config.Configuration) error {
				for _, p := range config.Pipeline {
					if p.Uses == "fetch" {
						uri, ok := p.With["uri"]
						if !ok {
							return fmt.Errorf("uri is missing in fetch pipeline")
						}
						u, err := url.ParseRequestURI(uri)
						if err != nil {
							return fmt.Errorf("uri is invalid URL structure")
						}
						if !reValidHostname.MatchString(u.Host) {
							return fmt.Errorf("uri hostname %q is invalid", u.Host)
						}

					}
				}
				return nil
			},
		},
		{
			Name:        "uri-mimic",
			Description: "every config should use a consistent hostname",
			Severity:    SeverityError,
			LintFunc: func(config config.Configuration) error {
				for _, p := range config.Pipeline {
					uri := p.With["uri"]
					if uri == "" {
						continue
					}
					u, err := url.ParseRequestURI(uri)
					if err != nil {

						return nil
					}
					host := u.Host
					if seenHosts[host] {
						continue
					}
					for k := range seenHosts {

						dist := levenshtein.DistanceForStrings([]rune(host), []rune(k), levenshtein.DefaultOptions)
						if hostEditDistanceExceptions[host] == k || hostEditDistanceExceptions[k] == host {
							continue
						}
						if dist <= minhostEditDistance {
							return fmt.Errorf("%q too similar to %q", host, k)
						}

						hostParts := strings.Split(host, ".")
						kParts := strings.Split(k, ".")
						if strings.Join(hostParts[:len(hostParts)-1], ".") == strings.Join(kParts[:len(kParts)-1], ".") {
							return fmt.Errorf("%q shares components with %q", host, k)
						}
					}
					seenHosts[host] = true
				}
				return nil
			},
		},

		{
			Name:        "valid-pipeline-fetch-digest",
			Description: "every fetch pipeline should have a valid digest",
			Severity:    SeverityError,
			LintFunc: func(config config.Configuration) error {
				for _, p := range config.Pipeline {
					if p.Uses == "fetch" {
						hashGiven := false
						if sha256, ok := p.With["expected-sha256"]; ok {
							if !reValidSHA256.MatchString(sha256) {
								return fmt.Errorf("expected-sha256 is not valid SHA256")
							}
							hashGiven = true
						}
						if sha512, ok := p.With["expected-sha512"]; ok {
							if !reValidSHA512.MatchString(sha512) {
								return fmt.Errorf("expected-sha512 is not valid SHA512")
							}
							hashGiven = true
						}
						if !hashGiven {
							return fmt.Errorf("expected-sha256 or expected-sha512 is missing")
						}
					}
				}
				return nil
			},
		},
		{
			Name:        "no-repeated-deps",
			Description: "no repeated dependencies",
			Severity:    SeverityError,
			LintFunc: func(config config.Configuration) error {
				seen := map[string]struct{}{}
				for _, p := range config.Environment.Contents.Packages {
					if _, ok := seen[p]; ok {
						return fmt.Errorf("package %s is duplicated in environment", p)
					}
					seen[p] = struct{}{}
				}
				return nil
			},
		},
		{
			Name:        "bad-template-var",
			Description: "bad template variable",
			Severity:    SeverityError,
			LintFunc: func(config config.Configuration) error {
				badTemplateVars := []string{
					"$pkgdir",
					"$pkgver",
					"$pkgname",
					"$srcdir",
				}

				hasBadVar := func(runs string) error {
					for _, badVar := range badTemplateVars {
						if strings.Contains(runs, badVar) {
							return fmt.Errorf("package contains likely incorrect template var %s", badVar)
						}
					}
					return nil
				}

				for _, s := range config.Pipeline {
					if err := hasBadVar(s.Runs); err != nil {
						return err
					}
				}

				for _, subPkg := range config.Subpackages {
					for _, subPipeline := range subPkg.Pipeline {
						if err := hasBadVar(subPipeline.Runs); err != nil {
							return err
						}
					}
				}
				return nil
			},
		},
		{
			Name:        "bad-version",
			Description: "version is malformed",
			Severity:    SeverityError,
			LintFunc: func(config config.Configuration) error {
				version := config.Package.Version
				if err := versions.ValidateWithoutEpoch(version); err != nil {
					return fmt.Errorf("invalid version %s, could not parse", version)
				}
				return nil
			},
		},
		{
			Name:        "valid-pipeline-git-checkout-commit",
			Description: "every git-checkout pipeline should have a valid expected-commit",
			Severity:    SeverityError,
			LintFunc: func(config config.Configuration) error {
				for _, p := range config.Pipeline {
					if p.Uses == gitCheckout {
						if commit, ok := p.With["expected-commit"]; ok {
							if !reValidSHA1.MatchString(commit) {
								return fmt.Errorf("expected-commit is not valid SHA1")
							}
						} else {
							return fmt.Errorf("expected-commit is missing")
						}
					}
				}
				return nil
			},
		},
		{
			Name:        "valid-pipeline-git-checkout-tag",
			Description: "every git-checkout pipeline should have a tag",
			Severity:    SeverityError,
			LintFunc: func(config config.Configuration) error {
				for _, p := range config.Pipeline {
					if p.Uses == gitCheckout {
						if _, ok := p.With["tag"]; !ok {
							return fmt.Errorf("tag is missing")
						}
					}
				}
				return nil
			},
		},
		{
			Name:        "check-when-version-changes",
			Description: "check comments to make sure they are updated when version changes",
			Severity:    SeverityError,
			LintFunc: func(config config.Configuration) error {
				re := regexp.MustCompile(`# CHECK-WHEN-VERSION-CHANGES: (.+)`)
				var checkString = func(s string) error {
					match := re.FindStringSubmatch(s)
					if len(match) == 0 {
						return nil
					}
					for _, m := range match[1:] {
						if m != config.Package.Version {
							return fmt.Errorf("version in comment: %s does not match version in package: %s, check that it can be updated and update the comment", m, config.Package.Version)
						}
					}
					return nil
				}
				for _, p := range config.Pipeline {
					if err := checkString(p.Runs); err != nil {
						return err
					}
				}
				for _, subPkg := range config.Subpackages {
					for _, subPipeline := range subPkg.Pipeline {
						if err := checkString(subPipeline.Runs); err != nil {
							return err
						}
					}
				}
				return nil
			},
		},
		{
			Name:        "tagged-repository-in-environment-repos",
			Description: "remove tagged repositories like @local from the repositories block",
			Severity:    SeverityError,
			LintFunc: func(config config.Configuration) error {
				for _, repo := range config.Environment.Contents.Repositories {
					if repo[0] == '@' {
						return fmt.Errorf("repository %q is tagged", repo)
					}
				}
				return nil
			},
		},
		{
			Name:        "git-checkout-must-use-github-updates",
			Description: "when using git-checkout, must use github updates so we can get the expected-commit",
			Severity:    SeverityError,
			LintFunc: func(config config.Configuration) error {
				for _, p := range config.Pipeline {
					if p.Uses == gitCheckout {
						if config.Update.Enabled && config.Update.GitHubMonitor == nil {
							return fmt.Errorf("configure update.github when using git-checkout")
						}
					}
				}
				return nil
			},
		},
		{
			Name:        "valid-spdx-license",
			Description: "every package should have a valid SPDX license",
			Severity:    SeverityError,
			LintFunc: func(config config.Configuration) error {
				for _, c := range config.Package.Copyright {

					if c.License == "" {
						log.Println("license is missing")
						return nil
					}
					if valid, _ := spdxexp.ValidateLicenses([]string{c.License}); !valid {
						log.Printf("license %q is not valid SPDX license", c.License)
						return nil
					}
				}
				return nil
			},
		},
	}
}

AllRules is a list of all available rules to evaluate.

Functions

This section is empty.

Types

type ConditionFunc

type ConditionFunc func() bool

ConditionFunc is a function that checks if a rule should be executed.

type EvalResult

type EvalResult struct {
	// File is the name of the file that was evaluated against.
	File string

	// Errors is a list of validation errors for each rule.
	Errors EvalRuleErrors
}

EvalResult represents the result of an evaluation for a single configuration.

type EvalRuleError

type EvalRuleError struct {
	// Rule is the rule that caused the error.
	Rule Rule

	// Error is the error that occurred.
	Error error
}

EvalRuleError represents an error that occurred during single rule evaluation.

type EvalRuleErrors

type EvalRuleErrors []EvalRuleError

EvalRuleErrors returns a list of EvalError.

func (EvalRuleErrors) WrapErrors

func (e EvalRuleErrors) WrapErrors() error

WrapErrors wraps multiple errors into a single error.

type Function

type Function func(config.Configuration) error

Function is a function that lints a single configuration.

type Linter

type Linter struct {
	// contains filtered or unexported fields
}

Linter represents a linter instance.

func New

func New(opts ...Option) *Linter

New initializes a new instance of Linter.

func (*Linter) Lint

func (l *Linter) Lint(ctx context.Context) (Result, error)

Lint evaluates all rules and returns the result.

func (*Linter) Print

func (l *Linter) Print(ctx context.Context, result Result)

Print prints the result to stdout.

func (*Linter) PrintRules

func (l *Linter) PrintRules(ctx context.Context)

PrintRules prints the rules to stdout.

type Option

type Option func(*Options)

Option represents a linter option.

func WithPath

func WithPath(path string) Option

WithPath sets the path to the file or directory to lint.

func WithSkipRules

func WithSkipRules(skipRules []string) Option

WithSkipRules sets the skip rules option.

type Options

type Options struct {
	// Path is the path to the file or directory to lint
	Path string

	// Skip rules removes the given slice of rules to be checked
	SkipRules []string
}

Options represents the options to configure the linter.

type Result

type Result []EvalResult

Result is a list of RuleResult.

func (Result) HasErrors

func (r Result) HasErrors() bool

HasErrors returns true if any of the EvalResult has an error.

type Rule

type Rule struct {
	// Name is the name of the rule.
	Name string

	// Description is the description of the rule.
	Description string

	// Severity is the severity of the rule.
	Severity Severity

	// LintFunc is the function that lints a single configuration.
	LintFunc Function

	// ConditionFuncs is a list of and-conditioned functions that check if the rule should be executed.
	ConditionFuncs []ConditionFunc
}

Rule represents a linter rule.

type Rules

type Rules []Rule

Rules is a list of Rule.

type Severity

type Severity string

Severity is the severity of a rule.

const (
	SeverityError   Severity = "ERROR"
	SeverityWarning Severity = "WARNING"
	SeverityInfo    Severity = "INFO"
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL