2018-12-17 01:15:39 +01:00
|
|
|
package pkglint
|
2016-07-07 14:09:26 +02:00
|
|
|
|
|
|
|
type MkShWalker struct {
|
2018-10-04 00:27:53 +02:00
|
|
|
Callback struct {
|
|
|
|
List func(list *MkShList)
|
|
|
|
AndOr func(andor *MkShAndOr)
|
|
|
|
Pipeline func(pipeline *MkShPipeline)
|
|
|
|
Command func(command *MkShCommand)
|
|
|
|
SimpleCommand func(command *MkShSimpleCommand)
|
|
|
|
CompoundCommand func(command *MkShCompoundCommand)
|
2018-12-17 01:15:39 +01:00
|
|
|
Case func(caseClause *MkShCase)
|
2018-10-04 00:27:53 +02:00
|
|
|
CaseItem func(caseItem *MkShCaseItem)
|
|
|
|
FunctionDefinition func(funcdef *MkShFunctionDefinition)
|
2018-12-17 01:15:39 +01:00
|
|
|
If func(ifClause *MkShIf)
|
|
|
|
Loop func(loop *MkShLoop)
|
2018-10-04 00:27:53 +02:00
|
|
|
Words func(words []*ShToken)
|
|
|
|
Word func(word *ShToken)
|
|
|
|
Redirects func(redirects []*MkShRedirection)
|
|
|
|
Redirect func(redirect *MkShRedirection)
|
2018-12-17 01:15:39 +01:00
|
|
|
For func(forClause *MkShFor)
|
|
|
|
|
|
|
|
// For variable definition in a for loop.
|
|
|
|
Varname func(varname string)
|
2018-10-04 00:27:53 +02:00
|
|
|
}
|
2018-12-17 01:15:39 +01:00
|
|
|
|
|
|
|
// Context[0] is the currently visited element,
|
|
|
|
// Context[1] is its immediate parent element, and so on.
|
|
|
|
// This is useful when the check for a CaseItem needs to look at the enclosing Case.
|
2018-10-04 00:27:53 +02:00
|
|
|
Context []MkShWalkerPathElement
|
|
|
|
}
|
|
|
|
|
|
|
|
type MkShWalkerPathElement struct {
|
2018-12-17 01:15:39 +01:00
|
|
|
|
|
|
|
// For fields that can be repeated, this is the index as seen from the parent element.
|
|
|
|
// For fields that cannot be repeated, it is -1.
|
|
|
|
//
|
|
|
|
// For example, in the SimpleCommand "var=value cmd arg1 arg2",
|
|
|
|
// there are multiple child elements of type Words.
|
|
|
|
//
|
|
|
|
// The first Words are the variable assignments, which have index 0.
|
|
|
|
//
|
|
|
|
// The command "cmd" has type Word, therefore it cannot be confused
|
|
|
|
// with either of the Words lists and has index -1.
|
|
|
|
//
|
|
|
|
// The second Words are the arguments, which have index 1.
|
|
|
|
// In this example, there are two arguments, so when visiting the
|
|
|
|
// arguments individually, arg1 will have index 0 and arg2 will have index 1.
|
|
|
|
//
|
|
|
|
// TODO: It might be worth defining negative indexes to correspond
|
2019-02-22 00:44:55 +01:00
|
|
|
// to the fields "Cond", "Action", "Else", etc.
|
2018-12-17 01:15:39 +01:00
|
|
|
Index int
|
|
|
|
|
2018-10-04 00:27:53 +02:00
|
|
|
Element interface{}
|
2016-07-07 14:09:26 +02:00
|
|
|
}
|
|
|
|
|
2018-08-12 18:31:56 +02:00
|
|
|
func NewMkShWalker() *MkShWalker {
|
|
|
|
return &MkShWalker{}
|
2016-07-07 14:09:26 +02:00
|
|
|
}
|
|
|
|
|
2018-08-12 18:31:56 +02:00
|
|
|
// Walk calls the given callback for each node of the parsed shell program,
|
|
|
|
// in visiting order from large to small.
|
2018-10-04 00:27:53 +02:00
|
|
|
func (w *MkShWalker) Walk(list *MkShList) {
|
|
|
|
w.walkList(-1, list)
|
|
|
|
|
2019-06-30 22:56:18 +02:00
|
|
|
// The calls to w.push and w.pop must be balanced.
|
|
|
|
assert(len(w.Context) == 0)
|
2016-07-07 14:09:26 +02:00
|
|
|
}
|
|
|
|
|
2018-10-04 00:27:53 +02:00
|
|
|
func (w *MkShWalker) walkList(index int, list *MkShList) {
|
|
|
|
w.push(index, list)
|
|
|
|
|
|
|
|
if callback := w.Callback.List; callback != nil {
|
|
|
|
callback(list)
|
2018-08-12 18:31:56 +02:00
|
|
|
}
|
2016-07-07 14:09:26 +02:00
|
|
|
|
2018-10-04 00:27:53 +02:00
|
|
|
for i, andor := range list.AndOrs {
|
|
|
|
w.walkAndOr(i, andor)
|
2016-07-07 14:09:26 +02:00
|
|
|
}
|
2018-10-04 00:27:53 +02:00
|
|
|
|
|
|
|
w.pop()
|
2016-07-07 14:09:26 +02:00
|
|
|
}
|
|
|
|
|
2018-10-04 00:27:53 +02:00
|
|
|
func (w *MkShWalker) walkAndOr(index int, andor *MkShAndOr) {
|
|
|
|
w.push(index, andor)
|
|
|
|
|
|
|
|
if callback := w.Callback.AndOr; callback != nil {
|
|
|
|
callback(andor)
|
2018-08-12 18:31:56 +02:00
|
|
|
}
|
2016-07-07 14:09:26 +02:00
|
|
|
|
2018-10-04 00:27:53 +02:00
|
|
|
for i, pipeline := range andor.Pipes {
|
|
|
|
w.walkPipeline(i, pipeline)
|
2016-07-07 14:09:26 +02:00
|
|
|
}
|
2018-10-04 00:27:53 +02:00
|
|
|
|
|
|
|
w.pop()
|
2016-07-07 14:09:26 +02:00
|
|
|
}
|
|
|
|
|
2018-10-04 00:27:53 +02:00
|
|
|
func (w *MkShWalker) walkPipeline(index int, pipeline *MkShPipeline) {
|
|
|
|
w.push(index, pipeline)
|
|
|
|
|
|
|
|
if callback := w.Callback.Pipeline; callback != nil {
|
|
|
|
callback(pipeline)
|
2018-08-12 18:31:56 +02:00
|
|
|
}
|
2016-07-07 14:09:26 +02:00
|
|
|
|
2018-10-04 00:27:53 +02:00
|
|
|
for i, command := range pipeline.Cmds {
|
|
|
|
w.walkCommand(i, command)
|
2016-07-07 14:09:26 +02:00
|
|
|
}
|
2018-10-04 00:27:53 +02:00
|
|
|
|
|
|
|
w.pop()
|
2016-07-07 14:09:26 +02:00
|
|
|
}
|
|
|
|
|
2018-10-04 00:27:53 +02:00
|
|
|
func (w *MkShWalker) walkCommand(index int, command *MkShCommand) {
|
|
|
|
w.push(index, command)
|
|
|
|
|
|
|
|
if callback := w.Callback.Command; callback != nil {
|
|
|
|
callback(command)
|
2018-08-12 18:31:56 +02:00
|
|
|
}
|
2016-07-07 14:09:26 +02:00
|
|
|
|
|
|
|
switch {
|
|
|
|
case command.Simple != nil:
|
2018-10-04 00:27:53 +02:00
|
|
|
w.walkSimpleCommand(-1, command.Simple)
|
2016-07-07 14:09:26 +02:00
|
|
|
case command.Compound != nil:
|
2018-10-04 00:27:53 +02:00
|
|
|
w.walkCompoundCommand(-1, command.Compound)
|
2019-04-20 19:43:24 +02:00
|
|
|
w.walkRedirects(command.Redirects)
|
2016-07-07 14:09:26 +02:00
|
|
|
case command.FuncDef != nil:
|
2018-10-04 00:27:53 +02:00
|
|
|
w.walkFunctionDefinition(-1, command.FuncDef)
|
2019-04-20 19:43:24 +02:00
|
|
|
w.walkRedirects(command.Redirects)
|
2016-07-07 14:09:26 +02:00
|
|
|
}
|
2018-10-04 00:27:53 +02:00
|
|
|
|
|
|
|
w.pop()
|
2016-07-07 14:09:26 +02:00
|
|
|
}
|
|
|
|
|
2018-10-04 00:27:53 +02:00
|
|
|
func (w *MkShWalker) walkSimpleCommand(index int, command *MkShSimpleCommand) {
|
|
|
|
w.push(index, command)
|
|
|
|
|
|
|
|
if callback := w.Callback.SimpleCommand; callback != nil {
|
|
|
|
callback(command)
|
2018-08-12 18:31:56 +02:00
|
|
|
}
|
2016-07-07 14:09:26 +02:00
|
|
|
|
2018-10-04 00:27:53 +02:00
|
|
|
w.walkWords(0, command.Assignments)
|
2016-07-07 14:09:26 +02:00
|
|
|
if command.Name != nil {
|
2018-10-04 00:27:53 +02:00
|
|
|
w.walkWord(-1, command.Name)
|
2016-07-07 14:09:26 +02:00
|
|
|
}
|
2018-12-17 01:15:39 +01:00
|
|
|
w.walkWords(1, command.Args)
|
2019-04-20 19:43:24 +02:00
|
|
|
w.walkRedirects(command.Redirections)
|
2018-10-04 00:27:53 +02:00
|
|
|
|
|
|
|
w.pop()
|
2016-07-07 14:09:26 +02:00
|
|
|
}
|
|
|
|
|
2018-10-04 00:27:53 +02:00
|
|
|
func (w *MkShWalker) walkCompoundCommand(index int, command *MkShCompoundCommand) {
|
|
|
|
w.push(index, command)
|
|
|
|
|
|
|
|
if callback := w.Callback.CompoundCommand; callback != nil {
|
|
|
|
callback(command)
|
2018-08-12 18:31:56 +02:00
|
|
|
}
|
2016-07-07 14:09:26 +02:00
|
|
|
|
|
|
|
switch {
|
|
|
|
case command.Brace != nil:
|
2018-10-04 00:27:53 +02:00
|
|
|
w.walkList(-1, command.Brace)
|
2016-07-07 14:09:26 +02:00
|
|
|
case command.Case != nil:
|
2018-10-04 00:27:53 +02:00
|
|
|
w.walkCase(command.Case)
|
2016-07-07 14:09:26 +02:00
|
|
|
case command.For != nil:
|
2018-10-04 00:27:53 +02:00
|
|
|
w.walkFor(command.For)
|
2016-07-07 14:09:26 +02:00
|
|
|
case command.If != nil:
|
2018-10-04 00:27:53 +02:00
|
|
|
w.walkIf(command.If)
|
2016-07-07 14:09:26 +02:00
|
|
|
case command.Loop != nil:
|
2018-10-04 00:27:53 +02:00
|
|
|
w.walkLoop(command.Loop)
|
2016-07-07 14:09:26 +02:00
|
|
|
case command.Subshell != nil:
|
2018-10-04 00:27:53 +02:00
|
|
|
w.walkList(-1, command.Subshell)
|
2016-07-07 14:09:26 +02:00
|
|
|
}
|
2018-10-04 00:27:53 +02:00
|
|
|
|
|
|
|
w.pop()
|
2016-07-07 14:09:26 +02:00
|
|
|
}
|
|
|
|
|
2018-12-17 01:15:39 +01:00
|
|
|
func (w *MkShWalker) walkCase(caseClause *MkShCase) {
|
2018-10-04 00:27:53 +02:00
|
|
|
w.push(-1, caseClause)
|
|
|
|
|
|
|
|
if callback := w.Callback.Case; callback != nil {
|
|
|
|
callback(caseClause)
|
2018-08-12 18:31:56 +02:00
|
|
|
}
|
2016-07-07 14:09:26 +02:00
|
|
|
|
2018-12-17 01:15:39 +01:00
|
|
|
w.walkWord(-1, caseClause.Word)
|
2018-10-04 00:27:53 +02:00
|
|
|
for i, caseItem := range caseClause.Cases {
|
|
|
|
w.push(i, caseItem)
|
|
|
|
if callback := w.Callback.CaseItem; callback != nil {
|
|
|
|
callback(caseItem)
|
2018-08-12 18:31:56 +02:00
|
|
|
}
|
2018-12-17 01:15:39 +01:00
|
|
|
w.walkWords(-1, caseItem.Patterns)
|
2019-08-02 20:55:07 +02:00
|
|
|
if caseItem.Action != nil {
|
|
|
|
w.walkList(-1, caseItem.Action)
|
|
|
|
}
|
2018-10-04 00:27:53 +02:00
|
|
|
w.pop()
|
2016-07-07 14:09:26 +02:00
|
|
|
}
|
2018-10-04 00:27:53 +02:00
|
|
|
|
|
|
|
w.pop()
|
2016-07-07 14:09:26 +02:00
|
|
|
}
|
|
|
|
|
2018-10-04 00:27:53 +02:00
|
|
|
func (w *MkShWalker) walkFunctionDefinition(index int, funcdef *MkShFunctionDefinition) {
|
|
|
|
w.push(index, funcdef)
|
|
|
|
|
|
|
|
if callback := w.Callback.FunctionDefinition; callback != nil {
|
|
|
|
callback(funcdef)
|
2018-08-12 18:31:56 +02:00
|
|
|
}
|
2016-07-07 14:09:26 +02:00
|
|
|
|
2018-10-04 00:27:53 +02:00
|
|
|
w.walkCompoundCommand(-1, funcdef.Body)
|
|
|
|
|
|
|
|
w.pop()
|
2016-07-07 14:09:26 +02:00
|
|
|
}
|
|
|
|
|
2018-12-17 01:15:39 +01:00
|
|
|
func (w *MkShWalker) walkIf(ifClause *MkShIf) {
|
2018-10-04 00:27:53 +02:00
|
|
|
w.push(-1, ifClause)
|
|
|
|
|
|
|
|
if callback := w.Callback.If; callback != nil {
|
|
|
|
callback(ifClause)
|
2018-08-12 18:31:56 +02:00
|
|
|
}
|
|
|
|
|
2018-12-17 01:15:39 +01:00
|
|
|
// TODO: Replace these indices with proper field names; see MkShWalkerPathElement.Index.
|
2016-07-07 14:09:26 +02:00
|
|
|
for i, cond := range ifClause.Conds {
|
2018-10-04 00:27:53 +02:00
|
|
|
w.walkList(2*i, cond)
|
|
|
|
w.walkList(2*i+1, ifClause.Actions[i])
|
2016-07-07 14:09:26 +02:00
|
|
|
}
|
|
|
|
if ifClause.Else != nil {
|
2018-10-04 00:27:53 +02:00
|
|
|
w.walkList(2*len(ifClause.Conds), ifClause.Else)
|
2016-07-07 14:09:26 +02:00
|
|
|
}
|
2018-10-04 00:27:53 +02:00
|
|
|
|
|
|
|
w.pop()
|
2016-07-07 14:09:26 +02:00
|
|
|
}
|
|
|
|
|
2018-12-17 01:15:39 +01:00
|
|
|
func (w *MkShWalker) walkLoop(loop *MkShLoop) {
|
2018-10-04 00:27:53 +02:00
|
|
|
w.push(-1, loop)
|
|
|
|
|
|
|
|
if callback := w.Callback.Loop; callback != nil {
|
|
|
|
callback(loop)
|
2018-08-12 18:31:56 +02:00
|
|
|
}
|
|
|
|
|
2018-10-04 00:27:53 +02:00
|
|
|
w.walkList(0, loop.Cond)
|
|
|
|
w.walkList(1, loop.Action)
|
|
|
|
|
|
|
|
w.pop()
|
2016-07-07 14:09:26 +02:00
|
|
|
}
|
|
|
|
|
2018-10-04 00:27:53 +02:00
|
|
|
func (w *MkShWalker) walkWords(index int, words []*ShToken) {
|
|
|
|
if len(words) == 0 {
|
|
|
|
return
|
|
|
|
}
|
2016-07-07 14:09:26 +02:00
|
|
|
|
2018-10-04 00:27:53 +02:00
|
|
|
w.push(index, words)
|
|
|
|
|
|
|
|
if callback := w.Callback.Words; callback != nil {
|
|
|
|
callback(words)
|
2016-07-07 14:09:26 +02:00
|
|
|
}
|
2018-10-04 00:27:53 +02:00
|
|
|
|
|
|
|
for i, word := range words {
|
|
|
|
w.walkWord(i, word)
|
|
|
|
}
|
|
|
|
|
|
|
|
w.pop()
|
2016-07-07 14:09:26 +02:00
|
|
|
}
|
|
|
|
|
2018-10-04 00:27:53 +02:00
|
|
|
func (w *MkShWalker) walkWord(index int, word *ShToken) {
|
|
|
|
w.push(index, word)
|
|
|
|
|
|
|
|
if callback := w.Callback.Word; callback != nil {
|
|
|
|
callback(word)
|
2018-08-12 18:31:56 +02:00
|
|
|
}
|
2018-10-04 00:27:53 +02:00
|
|
|
|
|
|
|
w.pop()
|
2016-07-07 14:09:26 +02:00
|
|
|
}
|
|
|
|
|
2019-04-20 19:43:24 +02:00
|
|
|
func (w *MkShWalker) walkRedirects(redirects []*MkShRedirection) {
|
2018-10-04 00:27:53 +02:00
|
|
|
if len(redirects) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-04-20 19:43:24 +02:00
|
|
|
w.push(-1, redirects)
|
2018-08-12 18:31:56 +02:00
|
|
|
|
2018-10-04 00:27:53 +02:00
|
|
|
if callback := w.Callback.Redirects; callback != nil {
|
|
|
|
callback(redirects)
|
|
|
|
}
|
2016-07-07 14:09:26 +02:00
|
|
|
|
2018-10-04 00:27:53 +02:00
|
|
|
for i, redirect := range redirects {
|
2019-04-20 19:43:24 +02:00
|
|
|
w.push(i, redirect)
|
2018-10-04 00:27:53 +02:00
|
|
|
if callback := w.Callback.Redirect; callback != nil {
|
|
|
|
callback(redirect)
|
2018-08-12 18:31:56 +02:00
|
|
|
}
|
2018-10-04 00:27:53 +02:00
|
|
|
|
|
|
|
w.walkWord(i, redirect.Target)
|
2019-04-20 19:43:24 +02:00
|
|
|
w.pop()
|
2016-07-07 14:09:26 +02:00
|
|
|
}
|
2018-10-04 00:27:53 +02:00
|
|
|
|
|
|
|
w.pop()
|
2016-07-07 14:09:26 +02:00
|
|
|
}
|
|
|
|
|
2018-12-17 01:15:39 +01:00
|
|
|
func (w *MkShWalker) walkFor(forClause *MkShFor) {
|
2018-10-04 00:27:53 +02:00
|
|
|
w.push(-1, forClause)
|
|
|
|
|
|
|
|
if callback := w.Callback.For; callback != nil {
|
|
|
|
callback(forClause)
|
2018-08-12 18:31:56 +02:00
|
|
|
}
|
2018-10-04 00:27:53 +02:00
|
|
|
if callback := w.Callback.Varname; callback != nil {
|
|
|
|
callback(forClause.Varname)
|
2018-08-12 18:31:56 +02:00
|
|
|
}
|
2016-07-07 14:09:26 +02:00
|
|
|
|
2018-10-04 00:27:53 +02:00
|
|
|
w.walkWords(-1, forClause.Values)
|
|
|
|
w.walkList(-1, forClause.Body)
|
|
|
|
|
|
|
|
w.pop()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Current provides access to the element that the walker is currently
|
|
|
|
// processing, especially its index as seen from its parent element.
|
|
|
|
func (w *MkShWalker) Current() MkShWalkerPathElement {
|
|
|
|
return w.Context[len(w.Context)-1]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parent returns an ancestor element from the currently visited path.
|
|
|
|
// Parent(0) is the element that is currently visited,
|
|
|
|
// Parent(1) is its direct parent, and so on.
|
|
|
|
func (w *MkShWalker) Parent(steps int) interface{} {
|
|
|
|
index := len(w.Context) - 1 - steps
|
|
|
|
if index >= 0 {
|
|
|
|
return w.Context[index].Element
|
|
|
|
}
|
|
|
|
return nil
|
2018-08-12 18:31:56 +02:00
|
|
|
}
|
|
|
|
|
2018-10-04 00:27:53 +02:00
|
|
|
func (w *MkShWalker) push(index int, element interface{}) {
|
|
|
|
w.Context = append(w.Context, MkShWalkerPathElement{index, element})
|
2018-08-12 18:31:56 +02:00
|
|
|
}
|
|
|
|
|
2018-10-04 00:27:53 +02:00
|
|
|
func (w *MkShWalker) pop() {
|
|
|
|
w.Context = w.Context[:len(w.Context)-1]
|
2016-07-07 14:09:26 +02:00
|
|
|
}
|