Skip to content

Commit a6a2cfb

Browse files
committed
Minor cleanup.
1 parent c17d094 commit a6a2cfb

37 files changed

+145
-93
lines changed

cmd/jsonnet-lint/cmd.go

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"io/ioutil"
66
"os"
77

8-
"github.com/fatih/color"
98
"github.com/google/go-jsonnet/linter"
109

1110
jsonnet "github.com/google/go-jsonnet"
@@ -48,19 +47,13 @@ func main() {
4847
if err != nil {
4948
die(err)
5049
}
51-
errWriter := &linter.ErrorWriter{
52-
Writer: os.Stderr,
53-
Formatter: jsonnet.LinterFormatter(),
54-
}
5550

5651
vm := jsonnet.MakeVM()
5752

58-
errWriter.Formatter.SetColorFormatter(color.New(color.FgRed).Fprintf)
59-
60-
l := linter.NewLinter(vm, errWriter)
53+
l := linter.NewLinter(vm, os.Stderr)
6154
l.AddFile(p, string(data))
62-
l.Check()
63-
if errWriter.ErrorsFound {
55+
errorsFound := l.Check()
56+
if errorsFound {
6457
fmt.Fprintf(os.Stderr, "Problems found!\n")
6558
ExitProblemsFound()
6659
}

linter/internal/common/common.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,19 @@ import (
44
"github.com/google/go-jsonnet/ast"
55
)
66

7+
// VariableKind allows distinguishing various kinds of variables.
78
type VariableKind int
89

910
const (
11+
// VarRegular is a "normal" variable with a definition in the code.
1012
VarRegular VariableKind = iota
13+
// VarParam is a function parameter.
1114
VarParam
15+
// VarStdlib is a special `std` variable.
1216
VarStdlib
1317
)
1418

19+
// Variable is a representation of a variable somewhere in the code.
1520
type Variable struct {
1621
Name ast.Identifier
1722
BindNode ast.Node

linter/internal/traversal/traversal.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
package traversal
22

3+
// Package traversal provides relatively lightweight checks
4+
// which can all fit within one traversal of the AST.
5+
// Currently available checks:
6+
// * Loop detection
7+
// TODO(sbarzowski) add more
8+
39
import (
410
"github.com/google/go-jsonnet/ast"
511
"github.com/google/go-jsonnet/linter/internal/utils"
@@ -54,6 +60,8 @@ func findLoopingInLocal(node *ast.Local, ec *utils.ErrCollector) {
5460
}
5561
}
5662

63+
// Traverse visits all nodes in the AST and runs appropriate
64+
// checks.
5765
func Traverse(node ast.Node, ec *utils.ErrCollector) {
5866
switch node := node.(type) {
5967
case *ast.Local:

linter/internal/types/build_graph.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ import (
1111
"github.com/google/go-jsonnet/linter/internal/common"
1212
)
1313

14+
// Maximum number of array elements for which we track
15+
// the values individually. We do that, because in Jsonnet
16+
// there is no separate tuple type, so we treat arrays as
17+
// potential tuples.
1418
const maxKnownCount = 5
1519

1620
func (g *typeGraph) getExprPlaceholder(node ast.Node) placeholderID {
@@ -85,13 +89,11 @@ func (g *typeGraph) AddRoots(roots map[string]ast.Node, vars map[string]map[ast.
8589
}
8690
}
8791

88-
type ImportFunc func(currentPath, importedPath string) ast.Node
89-
9092
func countRequiredParameters(params []ast.Parameter) int {
9193
count := 0
9294
for _, p := range params {
9395
if p.DefaultArg != nil {
94-
count += 1
96+
count++
9597
}
9698
}
9799
return count

linter/internal/types/check.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ func checkSubexpr(node ast.Node, typeOf ExprTypes, ec *utils.ErrCollector) {
1414
}
1515
}
1616

17+
// Check verifies that the types are valid for a given program, given
18+
// the previously resolved types.
1719
func Check(node ast.Node, typeOf ExprTypes, ec *utils.ErrCollector) {
1820
checkSubexpr(node, typeOf, ec)
1921
switch node := node.(type) {

linter/internal/types/desc.go

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ func (f *functionDesc) normalize() {
114114
f.resultContains = normalizePlaceholders(f.resultContains)
115115
}
116116

117+
// TypeDesc is a representation of a type.
118+
// This is (way) richer than the basic Jsonnet type system
119+
// with seven types.
117120
type TypeDesc struct {
118121
Bool bool
119122
Number bool
@@ -124,23 +127,28 @@ type TypeDesc struct {
124127
ArrayDesc *arrayDesc
125128
}
126129

130+
// Any returns whether all values are allowed (i.e. we know nothing about it).
127131
func (t *TypeDesc) Any() bool {
128132
// TODO(sbarzowski) BUG - must check that function, object and array allow any values inside
129133
return t.Bool && t.Number && t.String && t.Null && t.Function() && t.Object() && t.Array()
130134
}
131135

136+
// Void returns whether the type is empty (no values are possible).
132137
func (t *TypeDesc) Void() bool {
133138
return !t.Bool && !t.Number && !t.String && !t.Null && !t.Function() && !t.Object() && !t.Array()
134139
}
135140

141+
// Function returns whether the types contains a function.
136142
func (t *TypeDesc) Function() bool {
137143
return t.FunctionDesc != nil
138144
}
139145

146+
// Object returns whether the types contains an object.
140147
func (t *TypeDesc) Object() bool {
141148
return t.ObjectDesc != nil
142149
}
143150

151+
// Array returns whether the types contains an array.
144152
func (t *TypeDesc) Array() bool {
145153
return t.ArrayDesc != nil
146154
}
@@ -149,6 +157,8 @@ func voidTypeDesc() TypeDesc {
149157
return TypeDesc{}
150158
}
151159

160+
// Describe provides incomplete, but human-readable
161+
// representation of a type.
152162
func Describe(t *TypeDesc) string {
153163
if t.Any() {
154164
return "any"
@@ -158,25 +168,25 @@ func Describe(t *TypeDesc) string {
158168
}
159169
parts := []string{}
160170
if t.Bool {
161-
parts = append(parts, "bool")
171+
parts = append(parts, "a bool")
162172
}
163173
if t.Number {
164-
parts = append(parts, "number")
174+
parts = append(parts, "a number")
165175
}
166176
if t.String {
167-
parts = append(parts, "string")
177+
parts = append(parts, "a string")
168178
}
169179
if t.Null {
170-
parts = append(parts, "null")
180+
parts = append(parts, "a null")
171181
}
172182
if t.Function() {
173-
parts = append(parts, "function")
183+
parts = append(parts, "a function")
174184
}
175185
if t.Object() {
176-
parts = append(parts, "object")
186+
parts = append(parts, "an object")
177187
}
178188
if t.Array() {
179-
parts = append(parts, "array")
189+
parts = append(parts, "an array")
180190
}
181191
return strings.Join(parts, " or ")
182192
}
@@ -339,6 +349,7 @@ func plusArrays(left, right *arrayDesc) *arrayDesc {
339349

340350
func builtinPlus(concreteArgs []*TypeDesc, pArgs []placeholderID) builtinOpResult {
341351
if concreteArgs[0] != nil && concreteArgs[1] != nil {
352+
// We have concrete arguments available - we can provide a concrete result.
342353
left := concreteArgs[0]
343354
right := concreteArgs[1]
344355
return builtinOpResult{
@@ -353,9 +364,10 @@ func builtinPlus(concreteArgs []*TypeDesc, pArgs []placeholderID) builtinOpResul
353364
},
354365
contained: pArgs,
355366
}
356-
} else {
357-
return builtinOpResult{
358-
contained: pArgs,
359-
}
367+
}
368+
// We do now know what the arguments are yet, so we cannot provide any concrete
369+
// result without more context.
370+
return builtinOpResult{
371+
contained: pArgs,
360372
}
361373
}

linter/internal/types/graph.go

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,8 @@ func (g *typeGraph) getOrCreateElementType(target placeholderID, index *indexSpe
7373
elID := g.newPlaceholder()
7474
elementType.stringIndex[index.stringIndex] = elID
7575
return created, elID
76-
} else {
77-
return created, elementType.stringIndex[index.stringIndex]
7876
}
77+
return created, elementType.stringIndex[index.stringIndex]
7978
} else if index.indexType == knownIntIndex {
8079
if elementType.intIndex == nil {
8180
elementType.intIndex = make([]placeholderID, maxKnownCount)
@@ -85,9 +84,8 @@ func (g *typeGraph) getOrCreateElementType(target placeholderID, index *indexSpe
8584
elID := g.newPlaceholder()
8685
elementType.intIndex[index.intIndex] = elID
8786
return created, elID
88-
} else {
89-
return created, elementType.intIndex[index.intIndex]
9087
}
88+
return created, elementType.intIndex[index.intIndex]
9189
} else if index.indexType == functionIndex {
9290
if elementType.callIndex == noType {
9391
created = true
@@ -128,6 +126,10 @@ type typePlaceholder struct {
128126
builtinOp *builtinOpDesc
129127
}
130128

129+
// ImportFunc should provide an AST node from a given location.
130+
// If a node is not available it should return nil.
131+
type ImportFunc func(currentPath, importedPath string) ast.Node
132+
131133
type typeGraph struct {
132134
_placeholders []typePlaceholder
133135
exprPlaceholder map[ast.Node]placeholderID
@@ -441,6 +443,7 @@ func tpRef(p placeholderID) typePlaceholder {
441443
}
442444
}
443445

446+
// ExprTypes is a map containing a type of each expression.
444447
type ExprTypes map[ast.Node]TypeDesc
445448

446449
func (g *typeGraph) newSimpleFuncType(returnType placeholderID, argNames ...ast.Identifier) placeholderID {
@@ -639,6 +642,11 @@ func prepareStdlib(g *typeGraph) {
639642
})
640643
}
641644

645+
// NewTypeGraph creates a new type graph, with the basic types and stdlib ready.
646+
// It does not contain any representation based on user-provided code yet.
647+
//
648+
// It requires varAt - the information about the variables in the problem (indexed
649+
// by the location of used) and importFunc for importing the code from other files.
642650
func NewTypeGraph(varAt map[ast.Node]*common.Variable, importFunc ImportFunc) *typeGraph {
643651
g := typeGraph{
644652
exprPlaceholder: make(map[ast.Node]placeholderID),
@@ -718,11 +726,15 @@ func NewTypeGraph(varAt map[ast.Node]*common.Variable, importFunc ImportFunc) *t
718726
return &g
719727
}
720728

729+
// AddToGraph adds the code represented by node and all its available
730+
// (importable) transitive dependencies to the type graph.
721731
func (g *typeGraph) AddToGraph(node ast.Node, varAt map[ast.Node]*common.Variable) {
722732
g.varAt = varAt // TODO(sbarzowski) hacky! If it changes, we shouldn't put it in typeGraph at all
723733
prepareTP(node, g)
724734
}
725735

736+
// PrepareTypes produces a final type for each expression in the graph.
737+
// No further operations on the graph are valid after this is called.
726738
func (g *typeGraph) PrepareTypes(node ast.Node, typeOf ExprTypes, varAt map[ast.Node]*common.Variable) {
727739
g.AddToGraph(node, varAt)
728740
g.simplifyReferences()
@@ -734,3 +746,7 @@ func (g *typeGraph) PrepareTypes(node ast.Node, typeOf ExprTypes, varAt map[ast.
734746
typeOf[e] = g.upperBound[p]
735747
}
736748
}
749+
750+
// TODO(sbarzowski) consider refactoring this to a safer interface, where a bunch of root nodes are passed
751+
// at once, with their varAt.
752+
// Or just unexport this stuff?

linter/linter.go

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,11 @@ import (
2323
type ErrorWriter struct {
2424
ErrorsFound bool
2525
Writer io.Writer
26-
Formatter jsonnet.ErrorFormatter
2726
}
2827

29-
func (e *ErrorWriter) writeError(err errors.StaticError) {
28+
func (e *ErrorWriter) writeError(vm *jsonnet.VM, err errors.StaticError) {
3029
e.ErrorsFound = true
31-
_, writeErr := e.Writer.Write([]byte(e.Formatter.Format(err) + "\n"))
30+
_, writeErr := e.Writer.Write([]byte(vm.ErrorFormatter.Format(err) + "\n"))
3231
if writeErr != nil {
3332
panic(writeErr)
3433
}
@@ -69,8 +68,7 @@ func lint(vm *jsonnet.VM, node ast.Node, currentPath string, errWriter *ErrorWri
6968

7069
for _, v := range variableInfo.Variables {
7170
if len(v.Occurences) == 0 && v.VariableKind == common.VarRegular && v.Name != "$" {
72-
// TODO(sbarzowski) re-enable
73-
errWriter.writeError(errors.MakeStaticError("Unused variable: "+string(v.Name), v.LocRange))
71+
errWriter.writeError(vm, errors.MakeStaticError("Unused variable: "+string(v.Name), v.LocRange))
7472
}
7573
}
7674
et := make(types.ExprTypes)
@@ -97,7 +95,7 @@ func lint(vm *jsonnet.VM, node ast.Node, currentPath string, errWriter *ErrorWri
9795
traversal.Traverse(node, &ec)
9896

9997
for _, err := range ec.Errs {
100-
errWriter.writeError(err)
98+
errWriter.writeError(vm, err)
10199
}
102100
}
103101

@@ -111,7 +109,7 @@ func getImports(vm *jsonnet.VM, node ast.Node, roots map[string]ast.Node, curren
111109
p := node.File.Value
112110
contents, foundAt, err := vm.ImportAST(currentPath, p)
113111
if err != nil {
114-
errWriter.writeError(errors.MakeStaticError(err.Error(), *node.Loc()))
112+
errWriter.writeError(vm, errors.MakeStaticError(err.Error(), *node.Loc()))
115113
} else {
116114
if _, visited := roots[foundAt]; !visited {
117115
roots[foundAt] = contents
@@ -122,7 +120,7 @@ func getImports(vm *jsonnet.VM, node ast.Node, roots map[string]ast.Node, curren
122120
p := node.File.Value
123121
_, err := vm.ResolveImport(currentPath, p)
124122
if err != nil {
125-
errWriter.writeError(errors.MakeStaticError(err.Error(), *node.Loc()))
123+
errWriter.writeError(vm, errors.MakeStaticError(err.Error(), *node.Loc()))
126124
}
127125
default:
128126
for _, c := range parser.Children(node) {
@@ -135,17 +133,13 @@ type Linter struct {
135133
vm *jsonnet.VM
136134
// TODO(sbarzowski) implement observer in VM, so that linter cache is automatically flushed when
137135
// the sources are invalidated. This is unnecessary if we implement one-go linter.
138-
errWriter *ErrorWriter
136+
errWriter io.Writer
139137

140138
// TODO(sbarzowski) allow more files
141139
filename string
142140
code string
143141
}
144142

145-
func (linter *Linter) flushCache() {
146-
147-
}
148-
149143
func (linter *Linter) AddFile(filename, code string) {
150144
// TODO(sbarzowski) create addImported - a variant which imports it instead of getting code as string
151145
// This allows avoiding processing it twice if it's both imported and accessed directly
@@ -159,16 +153,19 @@ func (linter *Linter) AddFile(filename, code string) {
159153

160154
func (linter *Linter) Check() bool {
161155
node, err := jsonnet.SnippetToAST(linter.filename, linter.code)
156+
errWriter := ErrorWriter{
157+
Writer: linter.errWriter,
158+
ErrorsFound: false,
159+
}
162160
if err != nil {
163-
linter.errWriter.writeError(err.(errors.StaticError)) // ugly but true
161+
errWriter.writeError(linter.vm, err.(errors.StaticError)) // ugly but true
164162
return true
165163
}
166-
lint(linter.vm, node, linter.filename, linter.errWriter)
167-
// TODO(sbarzowski) There is something fishy about this usage errWriter, since it may be reused multiple times
168-
return linter.errWriter.ErrorsFound
164+
lint(linter.vm, node, linter.filename, &errWriter)
165+
return errWriter.ErrorsFound
169166
}
170167

171-
func NewLinter(vm *jsonnet.VM, errWriter *ErrorWriter) *Linter {
168+
func NewLinter(vm *jsonnet.VM, errWriter io.Writer) *Linter {
172169
return &Linter{
173170
vm: vm,
174171
errWriter: errWriter,

0 commit comments

Comments
 (0)