pkgsrc-Changes archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

CVS commit: pkgsrc/pkgtools/pkglint



Module Name:    pkgsrc
Committed By:   rillig
Date:           Sat Jun 20 07:00:44 UTC 2020

Modified Files:
        pkgsrc/pkgtools/pkglint: Makefile
        pkgsrc/pkgtools/pkglint/files: check_test.go scope.go scope_test.go
            substcontext.go substcontext_test.go vardefs.go
        pkgsrc/pkgtools/pkglint/files/makepat: pat.go pat_test.go

Log Message:
pkgtools/pkglint: update to 20.1.18

Changes since 20.1.17:

Fixed the algorithm for checking whether two patterns overlap, such as
MACHINE_PLATFORM:MNetBSD-[0-9].*-*.

Fixed wrong warning about foreign variable in SUBST block, as seen in
geography/qgis.


To generate a diff of this commit:
cvs rdiff -u -r1.657 -r1.658 pkgsrc/pkgtools/pkglint/Makefile
cvs rdiff -u -r1.73 -r1.74 pkgsrc/pkgtools/pkglint/files/check_test.go
cvs rdiff -u -r1.1 -r1.2 pkgsrc/pkgtools/pkglint/files/scope.go \
    pkgsrc/pkgtools/pkglint/files/scope_test.go
cvs rdiff -u -r1.38 -r1.39 pkgsrc/pkgtools/pkglint/files/substcontext.go
cvs rdiff -u -r1.36 -r1.37 pkgsrc/pkgtools/pkglint/files/substcontext_test.go
cvs rdiff -u -r1.99 -r1.100 pkgsrc/pkgtools/pkglint/files/vardefs.go
cvs rdiff -u -r1.1 -r1.2 pkgsrc/pkgtools/pkglint/files/makepat/pat.go \
    pkgsrc/pkgtools/pkglint/files/makepat/pat_test.go

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: pkgsrc/pkgtools/pkglint/Makefile
diff -u pkgsrc/pkgtools/pkglint/Makefile:1.657 pkgsrc/pkgtools/pkglint/Makefile:1.658
--- pkgsrc/pkgtools/pkglint/Makefile:1.657      Wed Jun 17 09:54:12 2020
+++ pkgsrc/pkgtools/pkglint/Makefile    Sat Jun 20 07:00:44 2020
@@ -1,7 +1,6 @@
-# $NetBSD: Makefile,v 1.657 2020/06/17 09:54:12 bsiegert Exp $
+# $NetBSD: Makefile,v 1.658 2020/06/20 07:00:44 rillig Exp $
 
-PKGNAME=       pkglint-20.1.17
-PKGREVISION=   1
+PKGNAME=       pkglint-20.1.18
 CATEGORIES=    pkgtools
 DISTNAME=      tools
 MASTER_SITES=  ${MASTER_SITE_GITHUB:=golang/}

Index: pkgsrc/pkgtools/pkglint/files/check_test.go
diff -u pkgsrc/pkgtools/pkglint/files/check_test.go:1.73 pkgsrc/pkgtools/pkglint/files/check_test.go:1.74
--- pkgsrc/pkgtools/pkglint/files/check_test.go:1.73    Sun Jun 14 11:35:54 2020
+++ pkgsrc/pkgtools/pkglint/files/check_test.go Sat Jun 20 07:00:44 2020
@@ -128,7 +128,6 @@ func Test__qa(t *testing.T) {
        ck.Configure("pkglint.go", "*", "*", -intqa.EMissingTest)        // TODO
        ck.Configure("pkgsrc.go", "*", "*", -intqa.EMissingTest)         // TODO
        ck.Configure("redundantscope.go", "*", "*", -intqa.EMissingTest) // TODO
-       ck.Configure("scope.go", "*", "*", -intqa.EMissingTest)          // TODO
        ck.Configure("shell.go", "*", "*", -intqa.EMissingTest)          // TODO
        ck.Configure("shtokenizer.go", "*", "*", -intqa.EMissingTest)    // TODO
        ck.Configure("shtypes.go", "*", "*", -intqa.EMissingTest)        // TODO

Index: pkgsrc/pkgtools/pkglint/files/scope.go
diff -u pkgsrc/pkgtools/pkglint/files/scope.go:1.1 pkgsrc/pkgtools/pkglint/files/scope.go:1.2
--- pkgsrc/pkgtools/pkglint/files/scope.go:1.1  Sun Jun 14 11:35:54 2020
+++ pkgsrc/pkgtools/pkglint/files/scope.go      Sat Jun 20 07:00:44 2020
@@ -114,13 +114,13 @@ func (s *Scope) Fallback(varname string,
 }
 
 // Use marks the variable and its canonicalized form as used.
-func (s *Scope) Use(varname string, line *MkLine, time VucTime) {
+func (s *Scope) Use(varname string, mkline *MkLine, time VucTime) {
        use := func(name string) {
                v := s.create(name)
                if v.used == nil {
-                       v.used = line
+                       v.used = mkline
                        if trace.Tracing {
-                               trace.Step2("Using %q in %s", name, line.String())
+                               trace.Step2("Using %q in %s", name, mkline.String())
                        }
                }
                if time == VucLoadTime {
Index: pkgsrc/pkgtools/pkglint/files/scope_test.go
diff -u pkgsrc/pkgtools/pkglint/files/scope_test.go:1.1 pkgsrc/pkgtools/pkglint/files/scope_test.go:1.2
--- pkgsrc/pkgtools/pkglint/files/scope_test.go:1.1     Sun Jun 14 11:35:54 2020
+++ pkgsrc/pkgtools/pkglint/files/scope_test.go Sat Jun 20 07:00:44 2020
@@ -34,6 +34,43 @@ func (s *Suite) Test_Scope__commented_va
        t.CheckEquals(indeterminate, false)
 }
 
+func (s *Suite) Test_NewScope(c *check.C) {
+       t := s.Init(c)
+
+       scope := NewScope()
+
+       t.Check(scope.names, check.IsNil)
+       t.Check(scope.vs, check.NotNil)
+       t.Check(scope.vs, check.HasLen, 0)
+}
+
+func (s *Suite) Test_Scope_varnames(c *check.C) {
+       t := s.Init(c)
+
+       scope := NewScope()
+       mkline := t.NewMkLine("filename.mk", 3, "DEFINED=\t${USED}")
+
+       t.Check(scope.varnames(), check.IsNil)
+
+       scope.Define("DEFINED", mkline)
+       scope.Use("USED", mkline, VucRunTime)
+
+       t.CheckDeepEquals(scope.varnames(), []string{"DEFINED", "USED"})
+       scope.varnames()[0] = "modified" // just to demonstrate the caching
+       t.CheckDeepEquals(scope.varnames(), []string{"modified", "USED"})
+}
+
+func (s *Suite) Test_Scope_create(c *check.C) {
+       t := s.Init(c)
+
+       scope := NewScope()
+
+       v1 := scope.create("VAR")
+       v2 := scope.create("VAR")
+
+       t.CheckEquals(v1, v2)
+}
+
 func (s *Suite) Test_Scope_Define(c *check.C) {
        t := s.Init(c)
 
@@ -75,6 +112,45 @@ func (s *Suite) Test_Scope_Define(c *che
                true, true, "")
 }
 
+func (s *Suite) Test_Scope_def(c *check.C) {
+       t := s.Init(c)
+
+       mkline := t.NewMkLine("filename.mk", 3, "VAR=\tvalue")
+       scope := NewScope()
+
+       scope.def("VAR.param", mkline)
+
+       t.Check(scope.FirstDefinition("VAR"), check.IsNil)
+       t.CheckEquals(scope.FirstDefinition("VAR.param"), mkline)
+       t.Check(scope.FirstDefinition("VAR.*"), check.IsNil)
+}
+
+func (s *Suite) Test_Scope_Fallback(c *check.C) {
+       t := s.Init(c)
+
+       mkline := t.NewMkLine("filename.mk", 3, "VAR=\tvalue")
+       scope := NewScope()
+       scope.def("VAR.param", mkline)
+
+       scope.Fallback("FALLBACK", "fallback")
+
+       t.CheckEquals(scope.LastValue("VAR.param"), "value")
+       t.CheckEquals(scope.LastValue("FALLBACK"), "fallback")
+       t.CheckEquals(scope.LastValue("UNDEFINED"), "")
+}
+
+func (s *Suite) Test_Scope_Use(c *check.C) {
+       t := s.Init(c)
+
+       mkline := t.NewMkLine("filename.mk", 3, "VAR=\t${USED}")
+       scope := NewScope()
+       scope.Define("VAR", mkline)
+       scope.Use("USED", mkline, VucRunTime)
+
+       t.CheckEquals(scope.LastValue("VAR"), "${USED}")
+       t.CheckEquals(scope.LastValue("USED"), "")
+}
+
 func (s *Suite) Test_Scope_Mentioned(c *check.C) {
        t := s.Init(c)
 
@@ -102,6 +178,13 @@ func (s *Suite) Test_Scope_IsDefined(c *
        t.CheckEquals(scope.IsDefined("VAR.param"), true)
        t.CheckEquals(scope.IsDefined("VAR.other"), false)
        t.CheckEquals(scope.IsDefined("VARIABLE.*"), false)
+}
+
+func (s *Suite) Test_Scope_IsDefinedSimilar(c *check.C) {
+       t := s.Init(c)
+
+       scope := NewScope()
+       scope.Define("VAR.param", t.NewMkLine("file.mk", 1, "VAR.param=value"))
 
        t.CheckEquals(scope.IsDefinedSimilar("VAR.param"), true)
        t.CheckEquals(scope.IsDefinedSimilar("VAR.other"), true)
@@ -118,23 +201,47 @@ func (s *Suite) Test_Scope_IsUsed(c *che
        t.CheckEquals(scope.IsUsed("VAR.param"), true)
        t.CheckEquals(scope.IsUsed("VAR.other"), false)
        t.CheckEquals(scope.IsUsed("VARIABLE.*"), false)
+}
+
+func (s *Suite) Test_Scope_IsUsedSimilar(c *check.C) {
+       t := s.Init(c)
+
+       scope := NewScope()
+       mkline := t.NewMkLine("file.mk", 1, "\techo ${VAR.param}")
+       scope.Use("VAR.param", mkline, VucRunTime)
 
        t.CheckEquals(scope.IsUsedSimilar("VAR.param"), true)
        t.CheckEquals(scope.IsUsedSimilar("VAR.other"), true)
        t.CheckEquals(scope.IsUsedSimilar("VARIABLE.*"), false)
 }
 
+func (s *Suite) Test_Scope_IsUsedAtLoadTime(c *check.C) {
+       t := s.Init(c)
+
+       scope := NewScope()
+       mkline := t.NewMkLine("file.mk", 1, "\techo ${VAR.param}")
+
+       scope.Use("LOAD_TIME", mkline, VucLoadTime)
+       scope.Use("RUN_TIME", mkline, VucRunTime)
+
+       t.CheckEquals(scope.IsUsedAtLoadTime("LOAD_TIME"), true)
+       t.CheckEquals(scope.IsUsedAtLoadTime("RUN_TIME"), false)
+       t.CheckEquals(scope.IsUsedAtLoadTime("UNDEFINED"), false)
+}
+
 func (s *Suite) Test_Scope_FirstDefinition(c *check.C) {
        t := s.Init(c)
 
-       mkline1 := t.NewMkLine("fname.mk", 3, "VAR=\tvalue")
-       mkline2 := t.NewMkLine("fname.mk", 3, ".if ${SNEAKY::=value}")
+       mkline3 := t.NewMkLine("fname.mk", 3, "VAR=\tvalue")
+       mkline4 := t.NewMkLine("fname.mk", 4, ".if ${SNEAKY::=value}")
+       mkline5 := t.NewMkLine("fname.mk", 5, ".if ${USED}")
 
        scope := NewScope()
-       scope.Define("VAR", mkline1)
-       scope.Define("SNEAKY", mkline2)
+       scope.Define("VAR", mkline3)
+       scope.Define("SNEAKY", mkline4)
+       scope.Use("USED", mkline5, VucLoadTime)
 
-       t.CheckEquals(scope.FirstDefinition("VAR"), mkline1)
+       t.CheckEquals(scope.FirstDefinition("VAR"), mkline3)
 
        // This call returns nil because it's not a variable assignment
        // and the calling code typically assumes a variable definition.
@@ -142,27 +249,68 @@ func (s *Suite) Test_Scope_FirstDefiniti
        // case that only few people actually know. It's better that way.
        t.Check(scope.FirstDefinition("SNEAKY"), check.IsNil)
 
+       t.Check(scope.FirstDefinition("USED"), check.IsNil)
+
        t.CheckOutputLines(
-               "ERROR: fname.mk:3: Assignment modifiers like \":=\" " +
+               "ERROR: fname.mk:4: Assignment modifiers like \":=\" " +
                        "must not be used at all.")
 }
 
+func (s *Suite) Test_Scope_LastDefinition(c *check.C) {
+       t := s.Init(c)
+
+       mkline3 := t.NewMkLine("fname.mk", 3, "VAR=\tfirst")
+       mkline4 := t.NewMkLine("fname.mk", 4, "VAR=\t${USED}")
+
+       scope := NewScope()
+       scope.Define("VAR", mkline3)
+       scope.Define("VAR", mkline4)
+       scope.Use("USED", mkline4, VucRunTime)
+
+       t.CheckEquals(scope.LastDefinition("VAR"), mkline4)
+       t.Check(scope.LastDefinition("UNDEFINED"), check.IsNil)
+       t.Check(scope.LastDefinition("USED"), check.IsNil)
+}
+
 func (s *Suite) Test_Scope_Commented(c *check.C) {
        t := s.Init(c)
 
        assigned := t.NewMkLine("filename.mk", 3, "VAR=\tvalue")
        commented := t.NewMkLine("filename.mk", 4, "#COMMENTED=\tvalue")
        documented := t.NewMkLine("filename.mk", 5, "# DOCUMENTED is a variable.")
+       used := t.NewMkLine("filename.mk", 6, "ANY=\t${USED}")
 
        scope := NewScope()
        scope.Define("VAR", assigned)
        scope.Define("COMMENTED", commented)
        scope.Define("DOCUMENTED", documented)
+       scope.Use("USED", used, VucRunTime)
 
        t.Check(scope.Commented("VAR"), check.IsNil)
        t.CheckEquals(scope.Commented("COMMENTED"), commented)
        t.Check(scope.Commented("DOCUMENTED"), check.IsNil)
        t.Check(scope.Commented("UNKNOWN"), check.IsNil)
+       t.Check(scope.Commented("USED"), check.IsNil)
+}
+
+func (s *Suite) Test_Scope_FirstUse(c *check.C) {
+       t := s.Init(c)
+
+       mklines := t.NewMkLines("file.mk",
+               MkCvsID,
+               "VAR1=\t${USED}",
+               "VAR2=\t${USED}")
+
+       mklines.Check()
+
+       scope := mklines.allVars
+       t.CheckEquals(scope.FirstUse("USED"), mklines.mklines[1])
+       t.Check(scope.FirstUse("UNUSED"), check.IsNil)
+
+       t.CheckOutputLines(
+               "WARN: file.mk:2: VAR1 is defined but not used.",
+               "WARN: file.mk:2: USED is used but not defined.",
+               "WARN: file.mk:3: VAR2 is defined but not used.")
 }
 
 func (s *Suite) Test_Scope_LastValue(c *check.C) {
@@ -210,10 +358,37 @@ func (s *Suite) Test_Scope_LastValue__ap
        t.CheckEquals(pkg.vars.LastValue("PLIST_VARS"), "one two")
 }
 
+func (s *Suite) Test_Scope_LastValueFound(c *check.C) {
+       t := s.Init(c)
+
+       mklines := t.NewMkLines("file.mk",
+               MkCvsID,
+               "VAR=\tfirst",
+               "VAR=\tsecond",
+               ".if 1",
+               "VAR=\tthird (conditional)",
+               ".endif")
+
+       mklines.Check()
+
+       value, found, indeterminate := mklines.allVars.LastValueFound("VAR")
+       t.CheckEquals(value, "third (conditional)")
+       t.CheckEquals(found, true)
+       t.CheckEquals(indeterminate, false) // TODO: why?
+
+       t.CheckOutputLines(
+               "WARN: file.mk:2: VAR is defined but not used.")
+}
+
+// Scope.DefineAll copies only the variable definitions,
+// but not the uses of variables.
 func (s *Suite) Test_Scope_DefineAll(c *check.C) {
        t := s.Init(c)
 
+       mkline := t.NewMkLine("filename.mk", 123, "VAR=\t${USED}")
+
        src := NewScope()
+       src.Use("USED", mkline, VucRunTime)
 
        dst := NewScope()
        dst.DefineAll(&src)
@@ -225,3 +400,22 @@ func (s *Suite) Test_Scope_DefineAll(c *
 
        t.CheckEquals(dst.IsDefined("VAR"), true)
 }
+
+func (s *Suite) Test_Scope_forEach(c *check.C) {
+       t := s.Init(c)
+
+       mkline := t.NewMkLine("filename.mk", 123, "VAR=\t${USED}")
+
+       scope := NewScope()
+       scope.Define("VAR", mkline)
+       scope.Use("USED", mkline, VucRunTime)
+
+       var result []string
+       scope.forEach(func(varname string, data *scopeVar) {
+               result = append(result, varname+"="+data.value)
+       })
+
+       t.CheckDeepEquals(result, []string{
+               "USED=",
+               "VAR=${USED}"})
+}

Index: pkgsrc/pkgtools/pkglint/files/substcontext.go
diff -u pkgsrc/pkgtools/pkglint/files/substcontext.go:1.38 pkgsrc/pkgtools/pkglint/files/substcontext.go:1.39
--- pkgsrc/pkgtools/pkglint/files/substcontext.go:1.38  Sun May 24 19:12:29 2020
+++ pkgsrc/pkgtools/pkglint/files/substcontext.go       Sat Jun 20 07:00:44 2020
@@ -420,6 +420,8 @@ func (b *substBlock) varassign(mkline *M
        case "SUBST_FILTER_CMD.*":
                b.varassignFilterCmd(mkline)
        }
+
+       b.varassignAllowForeign(mkline)
 }
 
 func (b *substBlock) varassignStage(mkline *MkLine, pkg *Package) {
@@ -491,6 +493,12 @@ func (b *substBlock) varassignFilterCmd(
        b.addSeen(ssTransform)
 }
 
+func (b *substBlock) varassignAllowForeign(mkline *MkLine) {
+       mkline.ForEachUsed(func(varUse *MkVarUse, time VucTime) {
+               b.allowVar(varUse.varname)
+       })
+}
+
 func (b *substBlock) suggestSubstVars(mkline *MkLine) {
 
        tokens, _ := splitIntoShellTokens(mkline.Line, mkline.Value())

Index: pkgsrc/pkgtools/pkglint/files/substcontext_test.go
diff -u pkgsrc/pkgtools/pkglint/files/substcontext_test.go:1.36 pkgsrc/pkgtools/pkglint/files/substcontext_test.go:1.37
--- pkgsrc/pkgtools/pkglint/files/substcontext_test.go:1.36     Sun May 24 19:12:29 2020
+++ pkgsrc/pkgtools/pkglint/files/substcontext_test.go  Sat Jun 20 07:00:44 2020
@@ -978,6 +978,22 @@ func (s *Suite) Test_substScope_finish__
                        "Please add only one class at a time to SUBST_CLASSES.")
 }
 
+func (s *Suite) Test_substScope_finish__indirect_SUBST_FILES(c *check.C) {
+       t := s.Init(c)
+
+       t.RunSubst(
+               "SUBST_CLASSES+= 1",
+               "SUBST_STAGE.1=  pre-configure",
+               "S1_CMD=         echo file",
+               "SUBST_FILES.1=  ${S1_CMD:sh}",
+               "SUBST_VARS.1=   PREFIX")
+
+       // Since S1_CMD is used in SUBST_FILES, it is not foreign
+       // but obviously belongs to the SUBST block.
+       // Before 2020-06-20, pkglint had warned about this.
+       t.CheckOutputEmpty()
+}
+
 func (s *Suite) Test_substScope_prepareSubstClasses(c *check.C) {
        t := s.Init(c)
 

Index: pkgsrc/pkgtools/pkglint/files/vardefs.go
diff -u pkgsrc/pkgtools/pkglint/files/vardefs.go:1.99 pkgsrc/pkgtools/pkglint/files/vardefs.go:1.100
--- pkgsrc/pkgtools/pkglint/files/vardefs.go:1.99       Sun Jun  7 15:49:23 2020
+++ pkgsrc/pkgtools/pkglint/files/vardefs.go    Sat Jun 20 07:00:44 2020
@@ -1155,7 +1155,7 @@ func (reg *VarTypeRegistry) Init(src *Pk
        reg.sys("EXTRACT_CMD", BtShellCommand)
        reg.pkg("EXTRACT_DIR", BtPathname)
        reg.pkg("EXTRACT_DIR.*", BtPathname)
-       reg.pkglist("EXTRACT_ELEMENTS", BtPathPattern) // TODO: No slashes allowed
+       reg.pkglist("EXTRACT_ELEMENTS", BtPathPattern)
        reg.pkglist("EXTRACT_ENV", BtShellWord)
        reg.pkglist("EXTRACT_ONLY", BtPathname)
        reg.pkglist("EXTRACT_OPTS", BtShellWord)

Index: pkgsrc/pkgtools/pkglint/files/makepat/pat.go
diff -u pkgsrc/pkgtools/pkglint/files/makepat/pat.go:1.1 pkgsrc/pkgtools/pkglint/files/makepat/pat.go:1.2
--- pkgsrc/pkgtools/pkglint/files/makepat/pat.go:1.1    Sun Jun 14 11:35:54 2020
+++ pkgsrc/pkgtools/pkglint/files/makepat/pat.go        Sat Jun 20 07:00:44 2020
@@ -27,22 +27,20 @@ type StateID uint16
 // Compile parses a pattern, including the error checking that is missing
 // from bmake.
 func Compile(pattern string) (*Pattern, error) {
-       var a Pattern
-       s := a.AddState(false)
-
-       var deadEnd StateID
+       var p Pattern
+       s := p.AddState(false)
 
        lex := textproc.NewLexer(pattern)
        for !lex.EOF() {
 
                if lex.SkipByte('*') {
-                       a.AddTransition(s, 0, 255, s)
+                       p.AddTransition(s, 0, 255, s)
                        continue
                }
 
                if lex.SkipByte('?') {
-                       next := a.AddState(false)
-                       a.AddTransition(s, 0, 255, next)
+                       next := p.AddState(false)
+                       p.AddTransition(s, 0, 255, next)
                        s = next
                        continue
                }
@@ -52,112 +50,194 @@ func Compile(pattern string) (*Pattern, 
                                return nil, errors.New("unfinished escape sequence")
                        }
                        ch := lex.NextByte()
-                       next := a.AddState(false)
-                       a.AddTransition(s, ch, ch, next)
+                       next := p.AddState(false)
+                       p.AddTransition(s, ch, ch, next)
                        s = next
                        continue
                }
 
                ch := lex.NextByte()
                if ch != '[' {
-                       next := a.AddState(false)
-                       a.AddTransition(s, ch, ch, next)
+                       next := p.AddState(false)
+                       p.AddTransition(s, ch, ch, next)
                        s = next
                        continue
                }
 
-               negate := lex.SkipByte('^')
-               if negate && deadEnd == 0 {
-                       deadEnd = a.AddState(false)
+               next, err := compileCharClass(&p, lex, ch, s)
+               if err != nil {
+                       return nil, err
+               }
+
+               s = next
+       }
+
+       p.states[s].end = true
+       return &p, nil
+}
+
+func compileCharClass(p *Pattern, lex *textproc.Lexer, ch byte, s StateID) (StateID, error) {
+       negate := lex.SkipByte('^')
+       chars := make([]bool, 256)
+       next := p.AddState(false)
+       for {
+               if lex.EOF() {
+                       return 0, errors.New("unfinished character class")
+               }
+               ch = lex.NextByte()
+               if ch == ']' {
+                       break
                }
-               next := a.AddState(false)
-               for {
+               if lex.SkipByte('-') {
                        if lex.EOF() {
-                               return nil, errors.New("unfinished character class")
+                               return 0, errors.New("unfinished character range")
                        }
-                       ch = lex.NextByte()
-                       if ch == ']' {
-                               break
-                       }
-                       max := ch
-                       if lex.SkipByte('-') {
-                               if lex.EOF() {
-                                       return nil, errors.New("unfinished character range")
-                               }
-                               max = lex.NextByte()
+                       max := lex.NextByte()
+                       if ch > max {
+                               ch, max = max, ch
                        }
-
-                       to := next
-                       if negate {
-                               to = deadEnd
+                       for i := int(ch); i <= int(max); i++ {
+                               chars[i] = true
                        }
-                       a.AddTransition(s, bmin(ch, max), bmax(ch, max), to)
+               } else {
+                       chars[ch] = true
                }
-               if negate {
-                       a.AddTransition(s, 0, 255, next)
+       }
+       if negate {
+               for i, b := range chars {
+                       chars[i] = !b
                }
-               s = next
        }
 
-       a.states[s].end = true
-       return &a, nil
+       start := 0
+       for start < len(chars) && !chars[start] {
+               start++
+       }
+
+       for start < len(chars) {
+               end := start
+               for end < len(chars) && chars[end] {
+                       end++
+               }
+
+               if start < end {
+                       p.AddTransition(s, byte(start), byte(end-1), next)
+               }
+
+               start = end
+               for start < len(chars) && !chars[start] {
+                       start++
+               }
+       }
+       return next, nil
 }
 
-func (a *Pattern) AddState(end bool) StateID {
-       a.states = append(a.states, state{nil, end})
-       return StateID(len(a.states) - 1)
+func (p *Pattern) AddState(end bool) StateID {
+       p.states = append(p.states, state{nil, end})
+       return StateID(len(p.states) - 1)
 }
 
-func (a *Pattern) AddTransition(from StateID, min, max byte, to StateID) {
-       state := &a.states[from]
+func (p *Pattern) AddTransition(from StateID, min, max byte, to StateID) {
+       state := &p.states[from]
        state.transitions = append(state.transitions, transition{min, max, to})
 }
 
 // Match tests whether a pattern matches the given string.
-func (a *Pattern) Match(s string) bool {
-       state := StateID(0)
+func (p *Pattern) Match(s string) bool {
+       curr := make([]bool, len(p.states))
+       next := make([]bool, len(p.states))
+
+       curr[0] = true
        for _, ch := range []byte(s) {
-               for _, tr := range a.states[state].transitions {
-                       if tr.min <= ch && ch <= tr.max {
-                               state = tr.to
-                               goto nextByte
+               ok := false
+               for i, _ := range next {
+                       next[i] = false
+               }
+
+               for si := range curr {
+                       if !curr[si] {
+                               continue
+                       }
+                       for _, tr := range p.states[si].transitions {
+                               if tr.min <= ch && ch <= tr.max {
+                                       next[tr.to] = true
+                                       ok = true
+                               }
                        }
                }
-               return false
-       nextByte:
+               if !ok {
+                       return false
+               }
+               curr, next = next, curr
        }
-       return a.states[state].end
+
+       for i, curr := range curr {
+               if curr && p.states[i].end {
+                       return true
+               }
+       }
+       return false
 }
 
 // Intersect computes a pattern that only matches if both given patterns
 // match at the same time.
-func Intersect(a, b *Pattern) *Pattern {
-       var is Pattern
-       for i := 0; i < len(a.states); i++ {
-               for j := 0; j < len(b.states); j++ {
-                       is.AddState(a.states[i].end && b.states[j].end)
+func Intersect(p1, p2 *Pattern) *Pattern {
+       var res Pattern
+       for i1 := 0; i1 < len(p1.states); i1++ {
+               for i2 := 0; i2 < len(p2.states); i2++ {
+                       res.AddState(p1.states[i1].end && p2.states[i2].end)
                }
        }
 
-       for i := 0; i < len(a.states); i++ {
-               for j := 0; j < len(b.states); j++ {
-                       for _, at := range a.states[i].transitions {
-                               for _, bt := range b.states[j].transitions {
-                                       min := bmax(at.min, bt.min)
-                                       max := bmin(at.max, bt.max)
+       for i1 := 0; i1 < len(p1.states); i1++ {
+               for i2 := 0; i2 < len(p2.states); i2++ {
+                       for _, t1 := range p1.states[i1].transitions {
+                               for _, t2 := range p2.states[i2].transitions {
+                                       min := bmax(t1.min, t2.min)
+                                       max := bmin(t1.max, t2.max)
                                        if min <= max {
-                                               from := StateID(i*len(b.states) + j)
-                                               to := at.to*StateID(len(b.states)) + bt.to
-                                               is.AddTransition(from, min, max, to)
+                                               from := StateID(i1*len(p2.states) + i2)
+                                               to := t1.to*StateID(len(p2.states)) + t2.to
+                                               res.AddTransition(from, min, max, to)
                                        }
                                }
                        }
                }
        }
 
-       // TODO: optimize: remove transitions that point to a dead end
+       return res.optimized()
+}
+
+func (p *Pattern) optimized() *Pattern {
+       var opt Pattern
+
+       var todo []StateID
+       hasNewID := make([]bool, len(p.states))
+       newIDs := make([]StateID, len(p.states))
+
+       todo = append(todo, 0)
+       newIDs[0] = opt.AddState(p.states[0].end)
+       hasNewID[0] = true
+
+       for len(todo) > 0 {
+               oldStateID := todo[len(todo)-1]
+               todo = todo[:len(todo)-1]
+
+               oldState := p.states[oldStateID]
+
+               for _, t := range oldState.transitions {
+                       if !hasNewID[t.to] {
+                               hasNewID[t.to] = true
+                               newIDs[t.to] = opt.AddState(p.states[t.to].end)
+                               todo = append(todo, t.to)
+                       }
+                       opt.AddTransition(newIDs[oldStateID], t.min, t.max, newIDs[t.to])
+               }
+       }
+
+       // TODO: remove transitions that point to a dead end
 
-       return &is
+       return &opt
 }
 
 // CanMatch tests whether the pattern can match some string.
@@ -165,13 +245,13 @@ func Intersect(a, b *Pattern) *Pattern {
 // Typical counterexamples are:
 //  [^]
 //  Intersect("*.c", "*.h")
-func (a *Pattern) CanMatch() bool {
-       reachable := make([]bool, len(a.states))
+func (p *Pattern) CanMatch() bool {
+       reachable := make([]bool, len(p.states))
        reachable[0] = true
 
 again:
        changed := false
-       for i, s := range a.states {
+       for i, s := range p.states {
                if reachable[i] {
                        for _, t := range s.transitions {
                                if !reachable[t.to] {
@@ -185,7 +265,7 @@ again:
                goto again
        }
 
-       for i, s := range a.states {
+       for i, s := range p.states {
                if reachable[i] && s.end {
                        return true
                }
Index: pkgsrc/pkgtools/pkglint/files/makepat/pat_test.go
diff -u pkgsrc/pkgtools/pkglint/files/makepat/pat_test.go:1.1 pkgsrc/pkgtools/pkglint/files/makepat/pat_test.go:1.2
--- pkgsrc/pkgtools/pkglint/files/makepat/pat_test.go:1.1       Sun Jun 14 11:35:54 2020
+++ pkgsrc/pkgtools/pkglint/files/makepat/pat_test.go   Sat Jun 20 07:00:44 2020
@@ -1,10 +1,87 @@
 package makepat
 
 import (
+       "netbsd.org/pkglint/intqa"
+       "reflect"
        "testing"
 )
 
-func Test_Automaton_Match(t *testing.T) {
+func Test_Compile__errors(t *testing.T) {
+       tests := []struct {
+               pattern string
+               msg     string
+       }{
+               {"\\", "unfinished escape sequence"},
+               {"[", "unfinished character class"},
+               {"[a-", "unfinished character range"},
+               {"[\\", "unfinished character class"},
+       }
+       for _, tt := range tests {
+               t.Run(tt.pattern, func(t *testing.T) {
+                       _, err := Compile(tt.pattern)
+                       if err == nil {
+                               t.Fail()
+                       } else if err.Error() != tt.msg {
+                               t.Errorf("err = %v, want %v", err, tt.msg)
+                       }
+               })
+       }
+}
+
+func Test_compileCharClass(t *testing.T) {
+       tests := []struct {
+               pattern string
+               str     string
+               want    bool
+       }{
+               {"[0-9]", "/", false},
+               {"[0-9]", "0", true},
+               {"[0-9]", "9", true},
+               {"[0-9]", ":", false},
+       }
+       for _, tt := range tests {
+               t.Run(tt.pattern, func(t *testing.T) {
+                       p, err := Compile(tt.pattern)
+                       if err != nil {
+                               t.Fail()
+                       }
+                       got := p.Match(tt.str)
+                       if got != tt.want {
+                               t.Errorf("got %v, want %v", got, tt.want)
+                       }
+               })
+       }
+}
+
+func Test_Pattern_AddState(t *testing.T) {
+       var p Pattern
+
+       p.AddState(false)
+       p.AddState(true)
+
+       expected := Pattern{states: []state{{nil, false}, {nil, true}}}
+       if !reflect.DeepEqual(p, expected) {
+               t.Errorf("%#v", p)
+       }
+}
+
+func Test_Pattern_AddTransition(t *testing.T) {
+       var p Pattern
+
+       p.AddState(false)
+       p.AddState(true)
+       p.AddTransition(0, '0', '9', 1)
+       p.AddTransition(1, '0', '9', 0)
+
+       expected := Pattern{states: []state{
+               {[]transition{{'0', '9', 1}}, false},
+               {[]transition{{'0', '9', 0}}, true}}}
+       if !reflect.DeepEqual(p, expected) {
+               t.Errorf("%#v", p)
+       }
+}
+
+func Test_Pattern_Match(t *testing.T) {
        tests := []struct {
                pattern string
                str     string
@@ -67,6 +144,12 @@ func Test_Automaton_Match(t *testing.T) 
                {"[9-0]", "5", true},
                {"[9-0]", "9", true},
                {"[9-0]", ":", false},
+               {"*.c", ".c", true},
+               {"*.c", "a.c", true},
+               {"*.c", "c.c", true},
+               {"*.c", "..c", true},
+               {"*.c", ".c.c", true},
+               {"*.c", "a.c.c", true},
        }
        for _, tt := range tests {
                t.Run(tt.pattern+" "+tt.str, func(t *testing.T) {
@@ -81,28 +164,6 @@ func Test_Automaton_Match(t *testing.T) 
        }
 }
 
-func Test_Automaton_Compile__errors(t *testing.T) {
-       tests := []struct {
-               pattern string
-               msg     string
-       }{
-               {"\\", "unfinished escape sequence"},
-               {"[", "unfinished character class"},
-               {"[a-", "unfinished character range"},
-               {"[\\", "unfinished character class"},
-       }
-       for _, tt := range tests {
-               t.Run(tt.pattern, func(t *testing.T) {
-                       _, err := Compile(tt.pattern)
-                       if err == nil {
-                               t.Fail()
-                       } else if err.Error() != tt.msg {
-                               t.Errorf("err = %v, want %v", err, tt.msg)
-                       }
-               })
-       }
-}
-
 func Test_Intersect(t *testing.T) {
        tests := []struct {
                pattern1 string
@@ -137,3 +198,79 @@ func Test_Intersect(t *testing.T) {
                })
        }
 }
+
+func Test_Pattern_optimized(t *testing.T) {
+       var p Pattern
+       p.AddState(false)
+       p.AddState(false)
+       p.AddState(false)
+       p.AddState(true)
+       p.AddTransition(0, '1', '1', 1)
+       p.AddTransition(1, '2', '2', 3)
+
+       opt := p.optimized()
+
+       expected := Pattern{[]state{
+               {[]transition{{'1', '1', 1}}, false},
+               {[]transition{{'2', '2', 2}}, false},
+               {nil, true}}}
+       if !reflect.DeepEqual(opt, &expected) {
+               t.Errorf("%#v", p)
+       }
+}
+
+func Test_Pattern_CanMatch(t *testing.T) {
+       tests := []struct {
+               p1   string
+               p2   string
+               want bool
+       }{
+               {"*.c", "*.h", false},
+               {"*.c", "????.?", true},
+               {"[1-9]", "5", true},
+               {"[1-9]", ":", false},
+               {"[1-9A-Za-z]", "[ -/]", false},
+       }
+       for _, tt := range tests {
+               t.Run(tt.p1+" "+tt.p2, func(t *testing.T) {
+                       p1, err1 := Compile(tt.p1)
+                       p2, err2 := Compile(tt.p2)
+                       if err1 != nil {
+                               t.Fatal(err1)
+                       }
+                       if err2 != nil {
+                               t.Fatal(err2)
+                       }
+                       both := Intersect(p1, p2)
+                       got := both.CanMatch()
+                       if got != tt.want {
+                               t.Errorf("CanMatch() = %v, want %v", got, tt.want)
+                       }
+               })
+       }
+
+}
+
+func Test_bmin(t *testing.T) {
+       if bmin(0, 255) != 0 {
+               t.Error()
+       }
+       if bmin(128, 127) != 127 {
+               t.Error()
+       }
+}
+
+func Test_bmax(t *testing.T) {
+       if bmax(0, 255) != 255 {
+               t.Error()
+       }
+       if bmax(128, 127) != 128 {
+               t.Error()
+       }
+}
+
+func Test(t *testing.T) {
+       ck := intqa.NewQAChecker(t.Errorf)
+       ck.Configure("*", "*", "", -intqa.EMissingTest)
+       ck.Check()
+}



Home | Main Index | Thread Index | Old Index