2fe3b9fc38
Changes since 19.4.0: The notes for inserting an empty line have been changed from "insert after this line" to "insert before this line" to make the line numbers in the diagnostics contiguous. There had been several places where the diagnostics went from line 1 to line 2 and then back to line 1, which was confusing. The lines in ALTERNATIVES files are checked for trailing whitespace. This is only for consistency with the other checks. In the whole pkgsrc tree all ALTERNATIVES files are already fine. The diagnostics for comments in .endif/.endfor lines that don't correspond to their .if/.elif/.for counterparts now includes the exact line number of the corresponding condition, to make the warning easier to fix. The diagnostics for wrong variable value alignment now mention the current column in addition to the desired column, to make it easier to see by how much and in which direction the indentation should be fixed. Variables that are used in conditions before they are actually defined need the :U modifier.
332 lines
7.6 KiB
Go
332 lines
7.6 KiB
Go
package pkglint
|
|
|
|
type MkShWalker struct {
|
|
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)
|
|
Case func(caseClause *MkShCase)
|
|
CaseItem func(caseItem *MkShCaseItem)
|
|
FunctionDefinition func(funcdef *MkShFunctionDefinition)
|
|
If func(ifClause *MkShIf)
|
|
Loop func(loop *MkShLoop)
|
|
Words func(words []*ShToken)
|
|
Word func(word *ShToken)
|
|
Redirects func(redirects []*MkShRedirection)
|
|
Redirect func(redirect *MkShRedirection)
|
|
For func(forClause *MkShFor)
|
|
|
|
// For variable definition in a for loop.
|
|
Varname func(varname string)
|
|
}
|
|
|
|
// 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.
|
|
Context []MkShWalkerPathElement
|
|
}
|
|
|
|
type MkShWalkerPathElement struct {
|
|
|
|
// 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
|
|
// to the fields "Cond", "Action", "Else", etc.
|
|
Index int
|
|
|
|
Element interface{}
|
|
}
|
|
|
|
func NewMkShWalker() *MkShWalker {
|
|
return &MkShWalker{}
|
|
}
|
|
|
|
// Walk calls the given callback for each node of the parsed shell program,
|
|
// in visiting order from large to small.
|
|
func (w *MkShWalker) Walk(list *MkShList) {
|
|
w.walkList(-1, list)
|
|
|
|
// The calls to w.push and w.pop must be balanced.
|
|
assert(len(w.Context) == 0)
|
|
}
|
|
|
|
func (w *MkShWalker) walkList(index int, list *MkShList) {
|
|
w.push(index, list)
|
|
|
|
if callback := w.Callback.List; callback != nil {
|
|
callback(list)
|
|
}
|
|
|
|
for i, andor := range list.AndOrs {
|
|
w.walkAndOr(i, andor)
|
|
}
|
|
|
|
w.pop()
|
|
}
|
|
|
|
func (w *MkShWalker) walkAndOr(index int, andor *MkShAndOr) {
|
|
w.push(index, andor)
|
|
|
|
if callback := w.Callback.AndOr; callback != nil {
|
|
callback(andor)
|
|
}
|
|
|
|
for i, pipeline := range andor.Pipes {
|
|
w.walkPipeline(i, pipeline)
|
|
}
|
|
|
|
w.pop()
|
|
}
|
|
|
|
func (w *MkShWalker) walkPipeline(index int, pipeline *MkShPipeline) {
|
|
w.push(index, pipeline)
|
|
|
|
if callback := w.Callback.Pipeline; callback != nil {
|
|
callback(pipeline)
|
|
}
|
|
|
|
for i, command := range pipeline.Cmds {
|
|
w.walkCommand(i, command)
|
|
}
|
|
|
|
w.pop()
|
|
}
|
|
|
|
func (w *MkShWalker) walkCommand(index int, command *MkShCommand) {
|
|
w.push(index, command)
|
|
|
|
if callback := w.Callback.Command; callback != nil {
|
|
callback(command)
|
|
}
|
|
|
|
switch {
|
|
case command.Simple != nil:
|
|
w.walkSimpleCommand(-1, command.Simple)
|
|
case command.Compound != nil:
|
|
w.walkCompoundCommand(-1, command.Compound)
|
|
w.walkRedirects(command.Redirects)
|
|
case command.FuncDef != nil:
|
|
w.walkFunctionDefinition(-1, command.FuncDef)
|
|
w.walkRedirects(command.Redirects)
|
|
}
|
|
|
|
w.pop()
|
|
}
|
|
|
|
func (w *MkShWalker) walkSimpleCommand(index int, command *MkShSimpleCommand) {
|
|
w.push(index, command)
|
|
|
|
if callback := w.Callback.SimpleCommand; callback != nil {
|
|
callback(command)
|
|
}
|
|
|
|
w.walkWords(0, command.Assignments)
|
|
if command.Name != nil {
|
|
w.walkWord(-1, command.Name)
|
|
}
|
|
w.walkWords(1, command.Args)
|
|
w.walkRedirects(command.Redirections)
|
|
|
|
w.pop()
|
|
}
|
|
|
|
func (w *MkShWalker) walkCompoundCommand(index int, command *MkShCompoundCommand) {
|
|
w.push(index, command)
|
|
|
|
if callback := w.Callback.CompoundCommand; callback != nil {
|
|
callback(command)
|
|
}
|
|
|
|
switch {
|
|
case command.Brace != nil:
|
|
w.walkList(-1, command.Brace)
|
|
case command.Case != nil:
|
|
w.walkCase(command.Case)
|
|
case command.For != nil:
|
|
w.walkFor(command.For)
|
|
case command.If != nil:
|
|
w.walkIf(command.If)
|
|
case command.Loop != nil:
|
|
w.walkLoop(command.Loop)
|
|
case command.Subshell != nil:
|
|
w.walkList(-1, command.Subshell)
|
|
}
|
|
|
|
w.pop()
|
|
}
|
|
|
|
func (w *MkShWalker) walkCase(caseClause *MkShCase) {
|
|
w.push(-1, caseClause)
|
|
|
|
if callback := w.Callback.Case; callback != nil {
|
|
callback(caseClause)
|
|
}
|
|
|
|
w.walkWord(-1, caseClause.Word)
|
|
for i, caseItem := range caseClause.Cases {
|
|
w.push(i, caseItem)
|
|
if callback := w.Callback.CaseItem; callback != nil {
|
|
callback(caseItem)
|
|
}
|
|
w.walkWords(-1, caseItem.Patterns)
|
|
if caseItem.Action != nil {
|
|
w.walkList(-1, caseItem.Action)
|
|
}
|
|
w.pop()
|
|
}
|
|
|
|
w.pop()
|
|
}
|
|
|
|
func (w *MkShWalker) walkFunctionDefinition(index int, funcdef *MkShFunctionDefinition) {
|
|
w.push(index, funcdef)
|
|
|
|
if callback := w.Callback.FunctionDefinition; callback != nil {
|
|
callback(funcdef)
|
|
}
|
|
|
|
w.walkCompoundCommand(-1, funcdef.Body)
|
|
|
|
w.pop()
|
|
}
|
|
|
|
func (w *MkShWalker) walkIf(ifClause *MkShIf) {
|
|
w.push(-1, ifClause)
|
|
|
|
if callback := w.Callback.If; callback != nil {
|
|
callback(ifClause)
|
|
}
|
|
|
|
// TODO: Replace these indices with proper field names; see MkShWalkerPathElement.Index.
|
|
for i, cond := range ifClause.Conds {
|
|
w.walkList(2*i, cond)
|
|
w.walkList(2*i+1, ifClause.Actions[i])
|
|
}
|
|
if ifClause.Else != nil {
|
|
w.walkList(2*len(ifClause.Conds), ifClause.Else)
|
|
}
|
|
|
|
w.pop()
|
|
}
|
|
|
|
func (w *MkShWalker) walkLoop(loop *MkShLoop) {
|
|
w.push(-1, loop)
|
|
|
|
if callback := w.Callback.Loop; callback != nil {
|
|
callback(loop)
|
|
}
|
|
|
|
w.walkList(0, loop.Cond)
|
|
w.walkList(1, loop.Action)
|
|
|
|
w.pop()
|
|
}
|
|
|
|
func (w *MkShWalker) walkWords(index int, words []*ShToken) {
|
|
if len(words) == 0 {
|
|
return
|
|
}
|
|
|
|
w.push(index, words)
|
|
|
|
if callback := w.Callback.Words; callback != nil {
|
|
callback(words)
|
|
}
|
|
|
|
for i, word := range words {
|
|
w.walkWord(i, word)
|
|
}
|
|
|
|
w.pop()
|
|
}
|
|
|
|
func (w *MkShWalker) walkWord(index int, word *ShToken) {
|
|
w.push(index, word)
|
|
|
|
if callback := w.Callback.Word; callback != nil {
|
|
callback(word)
|
|
}
|
|
|
|
w.pop()
|
|
}
|
|
|
|
func (w *MkShWalker) walkRedirects(redirects []*MkShRedirection) {
|
|
if len(redirects) == 0 {
|
|
return
|
|
}
|
|
|
|
w.push(-1, redirects)
|
|
|
|
if callback := w.Callback.Redirects; callback != nil {
|
|
callback(redirects)
|
|
}
|
|
|
|
for i, redirect := range redirects {
|
|
w.push(i, redirect)
|
|
if callback := w.Callback.Redirect; callback != nil {
|
|
callback(redirect)
|
|
}
|
|
|
|
w.walkWord(i, redirect.Target)
|
|
w.pop()
|
|
}
|
|
|
|
w.pop()
|
|
}
|
|
|
|
func (w *MkShWalker) walkFor(forClause *MkShFor) {
|
|
w.push(-1, forClause)
|
|
|
|
if callback := w.Callback.For; callback != nil {
|
|
callback(forClause)
|
|
}
|
|
if callback := w.Callback.Varname; callback != nil {
|
|
callback(forClause.Varname)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func (w *MkShWalker) push(index int, element interface{}) {
|
|
w.Context = append(w.Context, MkShWalkerPathElement{index, element})
|
|
}
|
|
|
|
func (w *MkShWalker) pop() {
|
|
w.Context = w.Context[:len(w.Context)-1]
|
|
}
|