-
Notifications
You must be signed in to change notification settings - Fork 240
/
Copy pathlinter.go
154 lines (131 loc) · 4.36 KB
/
linter.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// Package linter analyses Jsonnet code for code "smells".
package linter
import (
"io"
jsonnet "github.com/google/go-jsonnet"
"github.com/google/go-jsonnet/ast"
"github.com/google/go-jsonnet/internal/errors"
"github.com/google/go-jsonnet/internal/parser"
"github.com/google/go-jsonnet/linter/internal/common"
"github.com/google/go-jsonnet/linter/internal/traversal"
"github.com/google/go-jsonnet/linter/internal/types"
"github.com/google/go-jsonnet/linter/internal/variables"
)
// ErrorWriter encapsulates a writer and an error state indicating when at least
// one error has been written to the writer.
type ErrorWriter struct {
ErrorsFound bool
Writer io.Writer
}
// Snippet represents a jsonnet file data that to be linted
type Snippet struct {
FileName string
Code string
}
func (e *ErrorWriter) writeError(vm *jsonnet.VM, err errors.StaticError) {
e.ErrorsFound = true
_, writeErr := e.Writer.Write([]byte(vm.ErrorFormatter.Format(err) + "\n"))
if writeErr != nil {
panic(writeErr)
}
}
// nodeWithLocation represents a Jsonnet program with its location
// for the importer.
type nodeWithLocation struct {
node ast.Node
path string
}
// Lint analyses a node and reports any issues it encounters to an error writer.
func lint(vm *jsonnet.VM, nodes []nodeWithLocation, errWriter *ErrorWriter) {
roots := make(map[string]ast.Node)
for _, node := range nodes {
roots[node.path] = node.node
}
for _, node := range nodes {
getImports(vm, node, roots, errWriter)
}
variablesInFile := make(map[string]common.VariableInfo)
std := common.Variable{
Name: "std",
Occurences: nil,
VariableKind: common.VarStdlib,
}
findVariables := func(node nodeWithLocation) *common.VariableInfo {
return variables.FindVariables(node.node, variables.Environment{"std": &std})
}
for importedPath, rootNode := range roots {
variablesInFile[importedPath] = *findVariables(nodeWithLocation{rootNode, importedPath})
}
vars := make(map[string]map[ast.Node]*common.Variable)
for importedPath, info := range variablesInFile {
vars[importedPath] = info.VarAt
}
for _, node := range nodes {
variableInfo := findVariables(node)
for _, v := range variableInfo.Variables {
if len(v.Occurences) == 0 && v.VariableKind == common.VarRegular && v.Name != "$" {
errWriter.writeError(vm, errors.MakeStaticError("Unused variable: "+string(v.Name), v.LocRange))
}
}
ec := common.ErrCollector{}
types.Check(node.node, roots, vars, func(currentPath, importedPath string) ast.Node {
node, _, err := vm.ImportAST(currentPath, importedPath)
if err != nil {
return nil
}
return node
}, &ec)
traversal.Traverse(node.node, &ec)
for _, err := range ec.Errs {
errWriter.writeError(vm, err)
}
}
}
func getImports(vm *jsonnet.VM, node nodeWithLocation, roots map[string]ast.Node, errWriter *ErrorWriter) {
// TODO(sbarzowski) consider providing some way to disable warnings about nonexistent imports
// At least for 3rd party code.
// Perhaps there may be some valid use cases for conditional imports where one of the imported
// files doesn't exist.
currentPath := node.path
switch node := node.node.(type) {
case *ast.Import:
p := node.File.Value
contents, foundAt, err := vm.ImportAST(currentPath, p)
if err != nil {
errWriter.writeError(vm, errors.MakeStaticError(err.Error(), *node.Loc()))
} else {
if _, visited := roots[foundAt]; !visited {
roots[foundAt] = contents
getImports(vm, nodeWithLocation{contents, foundAt}, roots, errWriter)
}
}
case *ast.ImportStr:
p := node.File.Value
_, err := vm.ResolveImport(currentPath, p)
if err != nil {
errWriter.writeError(vm, errors.MakeStaticError(err.Error(), *node.Loc()))
}
default:
for _, c := range parser.Children(node) {
getImports(vm, nodeWithLocation{c, currentPath}, roots, errWriter)
}
}
}
// LintSnippet checks for problems in code snippet(s).
func LintSnippet(vm *jsonnet.VM, output io.Writer, snippets []Snippet) bool {
errWriter := ErrorWriter{
Writer: output,
ErrorsFound: false,
}
var nodes []nodeWithLocation
for _, snippet := range snippets {
node, err := vm.SnippetToAST(snippet.FileName, snippet.Code)
if err != nil {
errWriter.writeError(vm, err.(errors.StaticError)) // ugly but true
} else {
nodes = append(nodes, nodeWithLocation{node, snippet.FileName})
}
}
lint(vm, nodes, &errWriter)
return errWriter.ErrorsFound
}