Skip to content

Commit 4af5997

Browse files
authored
refactor: refactored lint command (#1604)
* refactor: refactored lint command * style: fixed function name based on go conventions
1 parent 75480d6 commit 4af5997

File tree

5 files changed

+192
-147
lines changed

5 files changed

+192
-147
lines changed

cmd/file_lint.go

Lines changed: 6 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,11 @@ import (
44
"errors"
55
"fmt"
66
"log"
7-
"strings"
87

9-
"github.com/daveshanley/vacuum/motor"
10-
"github.com/daveshanley/vacuum/rulesets"
8+
"github.com/kong/deck/lint"
119
"github.com/kong/go-apiops/filebasics"
1210
"github.com/kong/go-apiops/logbasics"
1311
"github.com/spf13/cobra"
14-
"sigs.k8s.io/yaml"
1512
)
1613

1714
var (
@@ -24,156 +21,24 @@ var (
2421

2522
const plainTextFormat = "plain"
2623

27-
type Severity int
28-
29-
const (
30-
SeverityHint Severity = iota
31-
SeverityInfo
32-
SeverityWarn
33-
SeverityError
34-
)
35-
36-
var severityStrings = [...]string{
37-
"hint",
38-
"info",
39-
"warn",
40-
"error",
41-
}
42-
43-
type LintResult struct {
44-
Message string
45-
Severity string
46-
Line int
47-
Column int
48-
Character int
49-
Path string
50-
}
51-
52-
func ParseSeverity(s string) Severity {
53-
for i, str := range severityStrings {
54-
if s == str {
55-
return Severity(i)
56-
}
57-
}
58-
return SeverityWarn
59-
}
60-
61-
func isOpenAPISpec(fileBytes []byte) bool {
62-
var contents map[string]interface{}
63-
64-
// This marshalling is redundant with what happens
65-
// in the linting command. There is likely an algorithm
66-
// we could use to determine JSON vs YAML and pull out the
67-
// openapi key without unmarshalling the entire file.
68-
err := yaml.Unmarshal(fileBytes, &contents)
69-
if err != nil {
70-
return false
71-
}
72-
73-
return contents["openapi"] != nil
74-
}
75-
76-
// getRuleSet reads the ruleset file by the provided name and returns a RuleSet object.
77-
func getRuleSet(ruleSetFile string) (*rulesets.RuleSet, error) {
78-
ruleSetBytes, err := filebasics.ReadFile(ruleSetFile)
79-
if err != nil {
80-
return nil, fmt.Errorf("error reading ruleset file: %w", err)
81-
}
82-
83-
customRuleSet, err := rulesets.CreateRuleSetFromData(ruleSetBytes)
84-
if err != nil {
85-
return nil, fmt.Errorf("error creating ruleset: %w", err)
86-
}
87-
88-
extends := customRuleSet.GetExtendsValue()
89-
if len(extends) > 0 {
90-
defaultRuleSet := rulesets.BuildDefaultRuleSets()
91-
return defaultRuleSet.GenerateRuleSetFromSuppliedRuleSet(customRuleSet), nil
92-
}
93-
94-
return customRuleSet, nil
95-
}
96-
9724
// Executes the CLI command "lint"
9825
func executeLint(cmd *cobra.Command, args []string) error {
9926
verbosity, _ := cmd.Flags().GetInt("verbose")
10027
logbasics.Initialize(log.LstdFlags, verbosity)
10128
_ = sendAnalytics("file-lint", "", modeLocal)
10229

103-
customRuleSet, err := getRuleSet(args[0])
30+
lintErrs, err := lint.Lint(cmdLintInputFilename, args[0],
31+
cmdLintFailSeverity, cmdLintOnlyFailures)
10432
if err != nil {
10533
return err
10634
}
10735

108-
stateFileBytes, err := filebasics.ReadFile(cmdLintInputFilename)
36+
silenceErrors, err := lint.GetLintOutput(lintErrs, cmdLintFormat, cmdLintOutputFilename)
10937
if err != nil {
110-
return fmt.Errorf("failed to read input file '%s'; %w", cmdLintInputFilename, err)
111-
}
112-
113-
ruleSetResults := motor.ApplyRulesToRuleSet(&motor.RuleSetExecution{
114-
RuleSet: customRuleSet,
115-
Spec: stateFileBytes,
116-
SkipDocumentCheck: !isOpenAPISpec(stateFileBytes),
117-
AllowLookup: true,
118-
})
119-
120-
var (
121-
failingCount int
122-
totalCount int
123-
lintResults = make([]LintResult, 0)
124-
)
125-
for _, x := range ruleSetResults.Results {
126-
if cmdLintOnlyFailures && ParseSeverity(x.Rule.Severity) < ParseSeverity(cmdLintFailSeverity) {
127-
continue
128-
}
129-
if ParseSeverity(x.Rule.Severity) >= ParseSeverity(cmdLintFailSeverity) {
130-
failingCount++
131-
}
132-
totalCount++
133-
lintResults = append(lintResults, LintResult{
134-
Message: x.Message,
135-
Path: func() string {
136-
if path, ok := x.Rule.Given.(string); ok {
137-
return path
138-
}
139-
return ""
140-
}(),
141-
Line: x.StartNode.Line,
142-
Column: x.StartNode.Column,
143-
Severity: x.Rule.Severity,
144-
})
145-
}
146-
147-
lintErrs := map[string]interface{}{
148-
"total_count": totalCount,
149-
"fail_count": failingCount,
150-
"results": lintResults,
38+
return err
15139
}
15240

153-
outputFormat := strings.ToUpper(cmdLintFormat)
154-
switch outputFormat {
155-
case strings.ToUpper(string(filebasics.OutputFormatJSON)):
156-
fallthrough
157-
case strings.ToUpper(string(filebasics.OutputFormatYaml)):
158-
if err = filebasics.WriteSerializedFile(
159-
cmdLintOutputFilename, lintErrs, filebasics.OutputFormat(outputFormat),
160-
); err != nil {
161-
return fmt.Errorf("error writing lint results: %w", err)
162-
}
163-
case strings.ToUpper(plainTextFormat):
164-
if totalCount > 0 {
165-
fmt.Printf("Linting Violations: %d\n", totalCount)
166-
fmt.Printf("Failures: %d\n\n", failingCount)
167-
for _, violation := range lintErrs["results"].([]LintResult) {
168-
fmt.Printf("[%s][%d:%d] %s\n",
169-
violation.Severity, violation.Line, violation.Column, violation.Message,
170-
)
171-
}
172-
}
173-
default:
174-
return fmt.Errorf("invalid output format: %s", cmdLintFormat)
175-
}
176-
if failingCount > 0 {
41+
if silenceErrors {
17742
// We don't want to print the error here as they're already output above
17843
// But we _do_ want to set an exit code of failure.
17944
//

lint/lint.go

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package lint
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/daveshanley/vacuum/motor"
8+
"github.com/daveshanley/vacuum/rulesets"
9+
"github.com/kong/go-apiops/filebasics"
10+
)
11+
12+
const plainTextFormat = "plain"
13+
14+
type Severity int
15+
16+
const (
17+
SeverityHint Severity = iota
18+
SeverityInfo
19+
SeverityWarn
20+
SeverityError
21+
)
22+
23+
var severityStrings = [...]string{
24+
"hint",
25+
"info",
26+
"warn",
27+
"error",
28+
}
29+
30+
type Result struct {
31+
Message string
32+
Severity string
33+
Line int
34+
Column int
35+
Character int
36+
Path string
37+
}
38+
39+
// getRuleSet reads the ruleset file by the provided name and returns a RuleSet object.
40+
func getRuleSet(ruleSetFile string) (*rulesets.RuleSet, error) {
41+
ruleSetBytes, err := filebasics.ReadFile(ruleSetFile)
42+
if err != nil {
43+
return nil, fmt.Errorf("error reading ruleset file: %w", err)
44+
}
45+
46+
customRuleSet, err := rulesets.CreateRuleSetFromData(ruleSetBytes)
47+
if err != nil {
48+
return nil, fmt.Errorf("error creating ruleset: %w", err)
49+
}
50+
51+
extends := customRuleSet.GetExtendsValue()
52+
if len(extends) > 0 {
53+
defaultRuleSet := rulesets.BuildDefaultRuleSets()
54+
return defaultRuleSet.GenerateRuleSetFromSuppliedRuleSet(customRuleSet), nil
55+
}
56+
57+
return customRuleSet, nil
58+
}
59+
60+
func Lint(
61+
cmdLintInputFilename string,
62+
ruleSetFileName string,
63+
cmdLintFailSeverity string,
64+
cmdLintOnlyFailures bool,
65+
) (map[string]interface{}, error) {
66+
customRuleSet, err := getRuleSet(ruleSetFileName)
67+
if err != nil {
68+
return nil, err
69+
}
70+
71+
stateFileBytes, err := filebasics.ReadFile(cmdLintInputFilename)
72+
if err != nil {
73+
return nil, fmt.Errorf("failed to read input file '%s'; %w", cmdLintInputFilename, err)
74+
}
75+
76+
ruleSetResults := motor.ApplyRulesToRuleSet(&motor.RuleSetExecution{
77+
RuleSet: customRuleSet,
78+
Spec: stateFileBytes,
79+
SkipDocumentCheck: !isOpenAPISpec(stateFileBytes),
80+
AllowLookup: true,
81+
})
82+
83+
var (
84+
failingCount int
85+
totalCount int
86+
lintResults = make([]Result, 0)
87+
)
88+
for _, x := range ruleSetResults.Results {
89+
if cmdLintOnlyFailures && ParseSeverity(x.Rule.Severity) < ParseSeverity(cmdLintFailSeverity) {
90+
continue
91+
}
92+
if ParseSeverity(x.Rule.Severity) >= ParseSeverity(cmdLintFailSeverity) {
93+
failingCount++
94+
}
95+
totalCount++
96+
lintResults = append(lintResults, Result{
97+
Message: x.Message,
98+
Path: func() string {
99+
if path, ok := x.Rule.Given.(string); ok {
100+
return path
101+
}
102+
return ""
103+
}(),
104+
Line: x.StartNode.Line,
105+
Column: x.StartNode.Column,
106+
Severity: x.Rule.Severity,
107+
})
108+
}
109+
110+
lintErrs := map[string]interface{}{
111+
"total_count": totalCount,
112+
"fail_count": failingCount,
113+
"results": lintResults,
114+
}
115+
116+
return lintErrs, nil
117+
}
118+
119+
func GetLintOutput(lintErrs map[string]interface{}, cmdLintFormat, cmdLintOutputFilename string) (bool, error) {
120+
outputFormat := strings.ToUpper(cmdLintFormat)
121+
totalCount := lintErrs["total_count"].(int)
122+
failingCount := lintErrs["fail_count"].(int)
123+
silenceErrors := false
124+
125+
switch outputFormat {
126+
case strings.ToUpper(string(filebasics.OutputFormatJSON)):
127+
fallthrough
128+
case strings.ToUpper(string(filebasics.OutputFormatYaml)):
129+
if err := filebasics.WriteSerializedFile(
130+
cmdLintOutputFilename, lintErrs, filebasics.OutputFormat(outputFormat),
131+
); err != nil {
132+
return silenceErrors, fmt.Errorf("error writing lint results: %w", err)
133+
}
134+
case strings.ToUpper(plainTextFormat):
135+
if totalCount > 0 {
136+
fmt.Printf("Linting Violations: %d\n", totalCount)
137+
fmt.Printf("Failures: %d\n\n", failingCount)
138+
for _, violation := range lintErrs["results"].([]Result) {
139+
fmt.Printf("[%s][%d:%d] %s\n",
140+
violation.Severity, violation.Line, violation.Column, violation.Message,
141+
)
142+
}
143+
}
144+
default:
145+
return silenceErrors, fmt.Errorf("invalid output format: %s", cmdLintFormat)
146+
}
147+
148+
if failingCount > 0 {
149+
silenceErrors = true
150+
}
151+
152+
return silenceErrors, nil
153+
}

lint/utils.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package lint
2+
3+
import "sigs.k8s.io/yaml"
4+
5+
func ParseSeverity(s string) Severity {
6+
for i, str := range severityStrings {
7+
if s == str {
8+
return Severity(i)
9+
}
10+
}
11+
return SeverityWarn
12+
}
13+
14+
func isOpenAPISpec(fileBytes []byte) bool {
15+
var contents map[string]interface{}
16+
17+
// This marshalling is redundant with what happens
18+
// in the linting command. There is likely an algorithm
19+
// we could use to determine JSON vs YAML and pull out the
20+
// openapi key without unmarshalling the entire file.
21+
err := yaml.Unmarshal(fileBytes, &contents)
22+
if err != nil {
23+
return false
24+
}
25+
26+
return contents["openapi"] != nil
27+
}

0 commit comments

Comments
 (0)