-
Notifications
You must be signed in to change notification settings - Fork 0
/
check.go
120 lines (104 loc) · 3.25 KB
/
check.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
// Package check package contains the parsing and ruling utilities to report problems.
package check
import (
"github.com/89luca89/shell-funcheck/pkg/types"
"github.com/89luca89/shell-funcheck/pkg/util"
"mvdan.cc/sh/v3/syntax"
)
// listParsedTokens will walk trough the input function and store
//
// comments
// arguments
// assignments
// iter variables (for i in ..., for x in ...)
// variables
//
// these will then be stored in a ParsedTokens struct that will later
// be used to apply the rules.
func listParsedTokens(funct *syntax.Stmt) types.ParsedTokens {
comments := []string{}
arguments := map[string]*syntax.Assign{}
assignments := map[string]*syntax.Assign{}
iters := map[string]*syntax.WordIter{}
variables := []*syntax.Lit{}
for _, comment := range funct.Comments {
comments = append(comments, comment.Text)
}
fun, ok := funct.Cmd.(*syntax.FuncDecl)
if !ok {
return types.ParsedTokens{}
}
syntax.Walk(fun, func(node syntax.Node) bool {
switch token := node.(type) {
case *syntax.ParamExp:
if util.StringIsArgument(token.Param.Value) {
variables = append(variables, token.Param)
}
case *syntax.WordIter:
if util.StringIsArgument(token.Name.Value) {
iters[token.Name.Value] = token
}
case *syntax.CallExpr:
for _, assign := range token.Assigns {
assignments[assign.Name.Value] = assign
if arg, ok := assign.Value.Parts[0].(*syntax.ParamExp); ok {
if util.StringIsArgument(arg.Param.Value) {
arguments[assign.Name.Value] = assign
delete(assignments, assign.Name.Value)
}
} else if arg, ok := assign.Value.Parts[0].(*syntax.DblQuoted); ok {
if len(arg.Parts) > 0 {
if nestArg, ok := arg.Parts[0].(*syntax.ParamExp); ok {
if util.StringIsArgument(nestArg.Param.Value) {
arguments[assign.Name.Value] = assign
delete(assignments, assign.Name.Value)
}
}
}
}
}
}
return true
})
return types.ParsedTokens{
Arguments: arguments,
Assignments: assignments,
Comments: comments,
Iters: iters,
Variables: variables,
Function: fun,
}
}
// RunCheck will run checks on an input file.
// this will report:
//
// uncommented and undocumented functions
// missing document sections in the comment
// missing arguments declaration in the comment
// missing global variables declaration in the comment
// missing env variables declaration in the comment
// missing output explanation in the comment
// warn about incomplete documentation of the previous sections.
func RunCheck(filepath string, options *types.CLIOptions) ([]types.ReportedVar, error) {
var result []types.ReportedVar
functions, err := listFileFunctions(filepath, options)
if err != nil {
return nil, err
}
if len(functions) == 0 {
return append(result, types.ReportedVar{
Reason: "no functions found in file",
Level: types.Note,
}), nil
}
parsedTokens := []types.ParsedTokens{}
for _, function := range functions {
result = append(result, validateFunctionComments(function)...)
parsedTokens = append(parsedTokens, listParsedTokens(function))
}
for _, parsedTokens := range parsedTokens {
result = append(result, listUndeclaredArguments(parsedTokens)...)
result = append(result, listUndeclaredVariables(parsedTokens)...)
}
return result, nil
}