From 1ddaea84fb9bf40ade260577728c360caa61b973 Mon Sep 17 00:00:00 2001 From: rillig Date: Fri, 2 Aug 2019 18:55:07 +0000 Subject: pkgtools/pkglint: update to 5.7.19 Changes since 5.7.18: * The tricky construct for generating case-items from a Make variable no longer produces parse errors. Example: case $$expr in ${PATTERNS:@p@ (${p}) action ;; @} esac --- pkgtools/pkglint/files/mkshparser.go | 15 +++++++++ pkgtools/pkglint/files/mkshparser_test.go | 56 +++++++++++++++++++++++++++++-- pkgtools/pkglint/files/mkshtypes.go | 1 + pkgtools/pkglint/files/mkshwalker.go | 4 ++- pkgtools/pkglint/files/shell.y | 13 ++++--- pkgtools/pkglint/files/shell_test.go | 9 ++--- 6 files changed, 85 insertions(+), 13 deletions(-) (limited to 'pkgtools/pkglint/files') diff --git a/pkgtools/pkglint/files/mkshparser.go b/pkgtools/pkglint/files/mkshparser.go index fd0297f2faf..b248c6b9dc9 100644 --- a/pkgtools/pkglint/files/mkshparser.go +++ b/pkgtools/pkglint/files/mkshparser.go @@ -243,6 +243,21 @@ func (lex *ShellLexer) Lex(lval *shyySymType) (ttype int) { p := NewShTokenizer(dummyLine, token, false) // Just for converting the string to a ShToken lval.Word = p.ShToken() lex.atCommandStart = false + + // Inside of a case statement, ${PATTERNS:@p@ (${p}) action ;; @} expands to + // a list of case-items, and after this list a new command starts. + // This is necessary to return a following "esac" as tkESAC instead of a + // simple word. + if lex.sinceCase >= 0 && len(lval.Word.Atoms) == 1 { + if varUse := lval.Word.Atoms[0].VarUse(); varUse != nil { + if len(varUse.modifiers) > 0 { + lastModifier := varUse.modifiers[len(varUse.modifiers)-1].Text + if hasPrefix(lastModifier, "@") { + lex.atCommandStart = true + } + } + } + } } return ttype diff --git a/pkgtools/pkglint/files/mkshparser_test.go b/pkgtools/pkglint/files/mkshparser_test.go index 0eab6b40489..31dc889e350 100644 --- a/pkgtools/pkglint/files/mkshparser_test.go +++ b/pkgtools/pkglint/files/mkshparser_test.go @@ -384,6 +384,23 @@ func (s *ShSuite) Test_ShellParser__case_clause(c *check.C) { b.CaseItem( b.Words("if", "then", "else"), b.List(), sepNone)))) + + // This could be regarded an evil preprocessor hack, but it's used + // in practice and is somewhat established, even though it is + // difficult to parse and understand, even for humans. + s.test("case $$expr in ${PATTERNS:@p@ (${p}) action ;; @} (*) ;; esac", + b.List().AddCommand(b.Case( + b.Token("$$expr"), + b.CaseItemVar("${PATTERNS:@p@ (${p}) action ;; @}"), + b.CaseItem( + b.Words("*"), + b.List(), sepNone)))) + + // The default case may be omitted if PATTERNS can never be empty. + s.test("case $$expr in ${PATTERNS:@p@ (${p}) action ;; @} esac", + b.List().AddCommand(b.Case( + b.Token("$$expr"), + b.CaseItemVar("${PATTERNS:@p@ (${p}) action ;; @}")))) } func (s *ShSuite) Test_ShellParser__if_clause(c *check.C) { @@ -555,11 +572,11 @@ func (s *ShSuite) test(program string, expected *MkShList) { error: ""} parser := shyyParserImpl{} - succeeded := parser.Parse(&lexer) + zeroMeansSuccess := parser.Parse(&lexer) c := s.c - if t.CheckEquals(succeeded, 0) && t.CheckEquals(lexer.error, "") { + if t.CheckEquals(zeroMeansSuccess, 0) && t.CheckEquals(lexer.error, "") { if !t.CheckDeepEquals(lexer.result, expected) { actualJSON, actualErr := json.MarshalIndent(lexer.result, "", " ") expectedJSON, expectedErr := json.MarshalIndent(expected, "", " ") @@ -664,6 +681,35 @@ func (s *ShSuite) Test_ShellLexer_Lex__keywords(c *check.C) { "if cond ; then : ; fi") } +func (s *Suite) Test_ShellLexer_Lex__case_patterns(c *check.C) { + t := s.Init(c) + + test := func(shellProgram string, expectedTokens ...int) { + tokens, rest := splitIntoShellTokens(nil, shellProgram) + lexer := NewShellLexer(tokens, rest) + + var actualTokens []int + for { + var token shyySymType + tokenType := lexer.Lex(&token) + if tokenType <= 0 { + break + } + actualTokens = append(actualTokens, tokenType) + } + t.CheckDeepEquals(actualTokens, expectedTokens) + } + + test( + "case $$expr in ${PATTERNS:@p@(${p}) action ;; @} esac", + + tkCASE, + tkWORD, + tkIN, + tkWORD, + tkESAC) +} + type MkShBuilder struct { } @@ -728,7 +774,11 @@ func (b *MkShBuilder) Case(selector *ShToken, items ...*MkShCaseItem) *MkShComma } func (b *MkShBuilder) CaseItem(patterns []*ShToken, action *MkShList, separator MkShSeparator) *MkShCaseItem { - return &MkShCaseItem{patterns, action, separator} + return &MkShCaseItem{patterns, action, separator, nil} +} + +func (b *MkShBuilder) CaseItemVar(varUseText string) *MkShCaseItem { + return &MkShCaseItem{nil, nil, sepNone, b.Token(varUseText)} } func (b *MkShBuilder) While(cond, action *MkShList, redirects ...*MkShRedirection) *MkShCommand { diff --git a/pkgtools/pkglint/files/mkshtypes.go b/pkgtools/pkglint/files/mkshtypes.go index ea778fe3986..1f3e5f74f8c 100644 --- a/pkgtools/pkglint/files/mkshtypes.go +++ b/pkgtools/pkglint/files/mkshtypes.go @@ -127,6 +127,7 @@ type MkShCaseItem struct { Patterns []*ShToken Action *MkShList Separator MkShSeparator + Var *ShToken // ${PATTERNS:@p@ (${p}) action ;; @} } // MkShIf is a conditional statement, possibly having diff --git a/pkgtools/pkglint/files/mkshwalker.go b/pkgtools/pkglint/files/mkshwalker.go index 012b88dcbd6..eb96104ff70 100644 --- a/pkgtools/pkglint/files/mkshwalker.go +++ b/pkgtools/pkglint/files/mkshwalker.go @@ -215,7 +215,9 @@ func (w *MkShWalker) walkCase(caseClause *MkShCase) { callback(caseItem) } w.walkWords(-1, caseItem.Patterns) - w.walkList(-1, caseItem.Action) + if caseItem.Action != nil { + w.walkList(-1, caseItem.Action) + } w.pop() } diff --git a/pkgtools/pkglint/files/shell.y b/pkgtools/pkglint/files/shell.y index 3c4f6cc92b5..b10fa5e7a33 100644 --- a/pkgtools/pkglint/files/shell.y +++ b/pkgtools/pkglint/files/shell.y @@ -208,20 +208,23 @@ case_selector : pattern tkRPAREN { } case_item_ns : case_selector linebreak { - $$ = &MkShCaseItem{$1, &MkShList{}, sepNone} + $$ = &MkShCaseItem{$1, &MkShList{}, sepNone, nil} } case_item_ns : case_selector linebreak term linebreak { - $$ = &MkShCaseItem{$1, $3, sepNone} + $$ = &MkShCaseItem{$1, $3, sepNone, nil} } case_item_ns : case_selector linebreak term separator_op linebreak { - $$ = &MkShCaseItem{$1, $3, $4} + $$ = &MkShCaseItem{$1, $3, $4, nil} } case_item : case_selector linebreak tkSEMISEMI linebreak { - $$ = &MkShCaseItem{$1, &MkShList{}, sepNone} + $$ = &MkShCaseItem{$1, &MkShList{}, sepNone, nil} } case_item : case_selector compound_list tkSEMISEMI linebreak { - $$ = &MkShCaseItem{$1, $2, sepNone} + $$ = &MkShCaseItem{$1, $2, sepNone, nil} +} +case_item : tkWORD { + $$ = &MkShCaseItem{Var: $1} } pattern : tkWORD { diff --git a/pkgtools/pkglint/files/shell_test.go b/pkgtools/pkglint/files/shell_test.go index 5a4b4fd53ba..081c1fa68fc 100644 --- a/pkgtools/pkglint/files/shell_test.go +++ b/pkgtools/pkglint/files/shell_test.go @@ -1099,10 +1099,11 @@ func (s *Suite) Test_ShellLineChecker_CheckShellCommand__case_patterns_from_vari mklines.Check() - // FIXME: Support the above variable expansion. - t.CheckOutputLines( - "WARN: Makefile:4: Pkglint ShellLine.CheckShellCommand: " + - "parse error at []string{\"*\", \")\", \"continue\", \";\", \"esac\"}") + // TODO: Ensure that the shell word is really only one variable use. + // TODO: Ensure that the last modifier is :@@@. + // TODO: Ensure that the replacement is a well-formed case-item. + // TODO: Ensure that the replacement contains ";;" as the last shell token. + t.CheckOutputEmpty() } func (s *Suite) Test_ShellLineChecker_checkHiddenAndSuppress(c *check.C) { -- cgit v1.2.3