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:           Wed Mar 29 07:12:36 UTC 2023

Modified Files:
        pkgsrc/pkgtools/pkglint: Makefile
        pkgsrc/pkgtools/pkglint/files: check_test.go homepage.go
            logging_test.go mkassignchecker.go mkcondchecker.go
            mkcondchecker_test.go mkcondsimplifier.go mkcondsimplifier_test.go
            mkline.go mkline_test.go mklines.go mkvarusechecker.go
            mkvarusechecker_test.go pkglint.go pkgsrc.go scope.go var.go
            varalignblock_test.go vardefs.go vardefs_test.go vartype.go
            vartype_test.go vartypecheck.go vartypecheck_test.go
        pkgsrc/pkgtools/pkglint/files/makepat: pat.go pat_test.go
        pkgsrc/pkgtools/pkglint/files/textproc: lexer.go

Log Message:
Update pkgtools/pkglint to 23.1.0

Changes since 22.4.1:

In makefiles outside pkgsrc, don't require the first line to contain the
CVS Id.

When simplifying conditions, correctly handle the edge case that a
single-word value may evaluate numerically to zero.

In dependency lines, parse '#' signs correctly.

In error messages about malformed patch files, use the correct plural
form.


To generate a diff of this commit:
cvs rdiff -u -r1.741 -r1.742 pkgsrc/pkgtools/pkglint/Makefile
cvs rdiff -u -r1.82 -r1.83 pkgsrc/pkgtools/pkglint/files/check_test.go
cvs rdiff -u -r1.7 -r1.8 pkgsrc/pkgtools/pkglint/files/homepage.go
cvs rdiff -u -r1.29 -r1.30 pkgsrc/pkgtools/pkglint/files/logging_test.go
cvs rdiff -u -r1.17 -r1.18 pkgsrc/pkgtools/pkglint/files/mkassignchecker.go
cvs rdiff -u -r1.16 -r1.17 pkgsrc/pkgtools/pkglint/files/mkcondchecker.go \
    pkgsrc/pkgtools/pkglint/files/mkvarusechecker_test.go
cvs rdiff -u -r1.15 -r1.16 \
    pkgsrc/pkgtools/pkglint/files/mkcondchecker_test.go \
    pkgsrc/pkgtools/pkglint/files/mkvarusechecker.go
cvs rdiff -u -r1.4 -r1.5 pkgsrc/pkgtools/pkglint/files/mkcondsimplifier.go
cvs rdiff -u -r1.2 -r1.3 \
    pkgsrc/pkgtools/pkglint/files/mkcondsimplifier_test.go
cvs rdiff -u -r1.87 -r1.88 pkgsrc/pkgtools/pkglint/files/mkline.go
cvs rdiff -u -r1.86 -r1.87 pkgsrc/pkgtools/pkglint/files/mkline_test.go
cvs rdiff -u -r1.77 -r1.78 pkgsrc/pkgtools/pkglint/files/mklines.go
cvs rdiff -u -r1.88 -r1.89 pkgsrc/pkgtools/pkglint/files/pkglint.go
cvs rdiff -u -r1.70 -r1.71 pkgsrc/pkgtools/pkglint/files/pkgsrc.go
cvs rdiff -u -r1.3 -r1.4 pkgsrc/pkgtools/pkglint/files/scope.go
cvs rdiff -u -r1.11 -r1.12 pkgsrc/pkgtools/pkglint/files/var.go
cvs rdiff -u -r1.19 -r1.20 \
    pkgsrc/pkgtools/pkglint/files/varalignblock_test.go
cvs rdiff -u -r1.107 -r1.108 pkgsrc/pkgtools/pkglint/files/vardefs.go
cvs rdiff -u -r1.30 -r1.31 pkgsrc/pkgtools/pkglint/files/vardefs_test.go
cvs rdiff -u -r1.55 -r1.56 pkgsrc/pkgtools/pkglint/files/vartype.go
cvs rdiff -u -r1.28 -r1.29 pkgsrc/pkgtools/pkglint/files/vartype_test.go
cvs rdiff -u -r1.103 -r1.104 pkgsrc/pkgtools/pkglint/files/vartypecheck.go
cvs rdiff -u -r1.95 -r1.96 pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go
cvs rdiff -u -r1.4 -r1.5 pkgsrc/pkgtools/pkglint/files/makepat/pat.go \
    pkgsrc/pkgtools/pkglint/files/makepat/pat_test.go
cvs rdiff -u -r1.12 -r1.13 pkgsrc/pkgtools/pkglint/files/textproc/lexer.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.741 pkgsrc/pkgtools/pkglint/Makefile:1.742
--- pkgsrc/pkgtools/pkglint/Makefile:1.741      Wed Mar  8 13:38:53 2023
+++ pkgsrc/pkgtools/pkglint/Makefile    Wed Mar 29 07:12:36 2023
@@ -1,7 +1,6 @@
-# $NetBSD: Makefile,v 1.741 2023/03/08 13:38:53 bsiegert Exp $
+# $NetBSD: Makefile,v 1.742 2023/03/29 07:12:36 rillig Exp $
 
-PKGNAME=       pkglint-22.4.1
-PKGREVISION=   1
+PKGNAME=       pkglint-23.1.0
 CATEGORIES=    pkgtools
 
 MAINTAINER=    rillig%NetBSD.org@localhost

Index: pkgsrc/pkgtools/pkglint/files/check_test.go
diff -u pkgsrc/pkgtools/pkglint/files/check_test.go:1.82 pkgsrc/pkgtools/pkglint/files/check_test.go:1.83
--- pkgsrc/pkgtools/pkglint/files/check_test.go:1.82    Thu Mar  2 08:58:29 2023
+++ pkgsrc/pkgtools/pkglint/files/check_test.go Wed Mar 29 07:12:36 2023
@@ -63,6 +63,7 @@ func (s *Suite) SetUpTest(c *check.C) {
        trace.Out = &t.stdout
 
        G.Pkgsrc = NewPkgsrc(t.File("."))
+       G.Project = G.Pkgsrc
 
        t.c = c
        t.SetUpCommandLine("-Wall")    // To catch duplicate warnings
@@ -231,11 +232,11 @@ func (t *Tester) SetUpCommandLine(args .
 //
 // See SetUpTool for registering tools like echo, awk, perl.
 func (t *Tester) SetUpVartypes() {
-       G.Pkgsrc.vartypes.Init(G.Pkgsrc)
+       G.Pkgsrc.Types().Init(G.Pkgsrc)
 }
 
 func (t *Tester) SetUpMasterSite(varname string, urls ...string) {
-       if !G.Pkgsrc.vartypes.IsDefinedExact(varname) {
+       if !G.Pkgsrc.Types().IsDefinedExact(varname) {
                t.SetUpVarType(varname, BtFetchURL,
                        List|SystemProvided,
                        "buildlink3.mk: none",
@@ -272,7 +273,7 @@ func (t *Tester) SetUpVarType(varname st
                aclEntries = []string{"Makefile, *.mk: default, set, append, use, use-loadtime"}
        }
 
-       G.Pkgsrc.vartypes.acl(varname, basicType, options, aclEntries...)
+       G.Project.Types().acl(varname, basicType, options, aclEntries...)
 
        // Make sure that registering the type succeeds.
        // This is necessary for BtUnknown and guessed types.

Index: pkgsrc/pkgtools/pkglint/files/homepage.go
diff -u pkgsrc/pkgtools/pkglint/files/homepage.go:1.7 pkgsrc/pkgtools/pkglint/files/homepage.go:1.8
--- pkgsrc/pkgtools/pkglint/files/homepage.go:1.7       Sun Jan 29 13:36:31 2023
+++ pkgsrc/pkgtools/pkglint/files/homepage.go   Wed Mar 29 07:12:36 2023
@@ -33,9 +33,9 @@ import (
 //   - http://sf.net/p/$project/
 //
 // TODO: implement complete homepage migration for SourceForge.
-// TODO: allow to suppress the automatic migration for SourceForge,
 //
-//     even if it is not about https vs. http.
+// TODO: allow to suppress the automatic migration for SourceForge,
+// even if it is not about https vs. http.
 type HomepageChecker struct {
        Value      string
        ValueNoVar string

Index: pkgsrc/pkgtools/pkglint/files/logging_test.go
diff -u pkgsrc/pkgtools/pkglint/files/logging_test.go:1.29 pkgsrc/pkgtools/pkglint/files/logging_test.go:1.30
--- pkgsrc/pkgtools/pkglint/files/logging_test.go:1.29  Sun Jan 29 13:36:31 2023
+++ pkgsrc/pkgtools/pkglint/files/logging_test.go       Wed Mar 29 07:12:36 2023
@@ -597,8 +597,7 @@ func (s *Suite) Test_Logger_writeSource_
 // output lines.
 //
 // TODO: Giving the diagnostics again would be useful, but the warning and
-//
-//     error counters should not be affected, as well as the exitcode.
+// error counters should not be affected, as well as the exitcode.
 func (s *Suite) Test_Logger_writeSource__separator_autofix(c *check.C) {
        t := s.Init(c)
 

Index: pkgsrc/pkgtools/pkglint/files/mkassignchecker.go
diff -u pkgsrc/pkgtools/pkglint/files/mkassignchecker.go:1.17 pkgsrc/pkgtools/pkglint/files/mkassignchecker.go:1.18
--- pkgsrc/pkgtools/pkglint/files/mkassignchecker.go:1.17       Thu Mar  2 08:58:29 2023
+++ pkgsrc/pkgtools/pkglint/files/mkassignchecker.go    Wed Mar 29 07:12:36 2023
@@ -25,7 +25,7 @@ func (ck *MkAssignChecker) check() {
 // checkLeft checks everything to the left of the assignment operator.
 func (ck *MkAssignChecker) checkLeft() {
        varname := ck.MkLine.Varname()
-       if !ck.mayBeDefined(varname) {
+       if G.Pkgsrc != nil && !ck.mayBeDefined(varname) {
                ck.MkLine.Warnf("Variable names starting with an underscore (%s) are reserved for internal pkgsrc use.", varname)
        }
 
@@ -72,13 +72,12 @@ func (ck *MkAssignChecker) checkLeftNotU
                return
        }
 
-       vartypes := G.Pkgsrc.vartypes
+       vartypes := G.Project.Types()
        if vartypes.IsDefinedExact(varname) || vartypes.IsDefinedExact(varcanon) {
                return
        }
 
-       deprecated := G.Pkgsrc.Deprecated
-       if deprecated[varname] != "" || deprecated[varcanon] != "" {
+       if G.Project.Deprecated(varname) != "" {
                return
        }
 
@@ -125,11 +124,8 @@ func (ck *MkAssignChecker) checkLeftOpsy
 }
 
 func (ck *MkAssignChecker) checkLeftDeprecated() {
-       varname := ck.MkLine.Varname()
-       if fix := G.Pkgsrc.Deprecated[varname]; fix != "" {
-               ck.MkLine.Warnf("Definition of %s is deprecated. %s", varname, fix)
-       } else if fix = G.Pkgsrc.Deprecated[varnameCanon(varname)]; fix != "" {
-               ck.MkLine.Warnf("Definition of %s is deprecated. %s", varname, fix)
+       if instead := G.Project.Deprecated(ck.MkLine.Varname()); instead != "" {
+               ck.MkLine.Warnf("Definition of %s is deprecated. %s", ck.MkLine.Varname(), instead)
        }
 }
 
@@ -703,7 +699,7 @@ func (ck *MkAssignChecker) mayBeDefined(
        if G.Infrastructure {
                return true
        }
-       if G.Pkgsrc == nil || G.Pkgsrc.vartypes.Canon(varname) != nil {
+       if G.Pkgsrc.Types().Canon(varname) != nil {
                return true
        }
 

Index: pkgsrc/pkgtools/pkglint/files/mkcondchecker.go
diff -u pkgsrc/pkgtools/pkglint/files/mkcondchecker.go:1.16 pkgsrc/pkgtools/pkglint/files/mkcondchecker.go:1.17
--- pkgsrc/pkgtools/pkglint/files/mkcondchecker.go:1.16 Thu Mar  2 08:58:29 2023
+++ pkgsrc/pkgtools/pkglint/files/mkcondchecker.go      Wed Mar 29 07:12:36 2023
@@ -241,8 +241,10 @@ func (ck *MkCondChecker) checkCompareWit
                op != "==" && op != "!=" &&
                matches(num, `^\d+$`) {
 
-               ck.MkLine.Errorf("The Python version must not be compared numerically.")
-               ck.MkLine.Explain(
+               fixedNum := replaceAll(num, `^([0-9])([0-9])$`, `${1}0$2`)
+               fix := ck.MkLine.Autofix()
+               fix.Errorf("_PYTHON_VERSION must not be compared numerically.")
+               fix.Explain(
                        "The variable _PYTHON_VERSION must not be compared",
                        "against an integer number, as these comparisons are",
                        "not meaningful.",
@@ -252,6 +254,8 @@ func (ck *MkCondChecker) checkCompareWit
                        "",
                        "In addition, _PYTHON_VERSION can be \"none\",",
                        "which is not a number.")
+               fix.Replace("${_PYTHON_VERSION} "+op+" "+num, "${PYTHON_VERSION} "+op+" "+fixedNum)
+               fix.Apply()
        }
 }
 
Index: pkgsrc/pkgtools/pkglint/files/mkvarusechecker_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mkvarusechecker_test.go:1.16 pkgsrc/pkgtools/pkglint/files/mkvarusechecker_test.go:1.17
--- pkgsrc/pkgtools/pkglint/files/mkvarusechecker_test.go:1.16  Fri Jun 24 07:16:23 2022
+++ pkgsrc/pkgtools/pkglint/files/mkvarusechecker_test.go       Wed Mar 29 07:12:36 2023
@@ -157,7 +157,7 @@ func (s *Suite) Test_MkVarUseChecker_Che
        // do this. In packages though, LOCALBASE is deprecated.
 
        // There is no warning about DEFAULT_PREFIX being "defined but not used"
-       // since Pkgsrc.loadUntypedVars calls Pkgsrc.vartypes.DefineType, which
+       // since Pkgsrc.loadUntypedVars calls Pkgsrc.Types().DefineType, which
        // registers that variable globally.
        t.CheckOutputEmpty()
 }

Index: pkgsrc/pkgtools/pkglint/files/mkcondchecker_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mkcondchecker_test.go:1.15 pkgsrc/pkgtools/pkglint/files/mkcondchecker_test.go:1.16
--- pkgsrc/pkgtools/pkglint/files/mkcondchecker_test.go:1.15    Thu Mar  2 08:58:29 2023
+++ pkgsrc/pkgtools/pkglint/files/mkcondchecker_test.go Wed Mar 29 07:12:36 2023
@@ -256,6 +256,14 @@ func (s *Suite) Test_MkCondChecker_Check
                                "!empty(MACHINE_PLATFORM:MNetBSD-9.99.*) && "+
                                "!empty(MACHINE_PLATFORM:MNetBSD-[1-9][0-9].*)",
                        ".endif"),
+               "NOTE: filename.mk:5:"+
+                       " \"!empty(MACHINE_PLATFORM:MNetBSD-9.99.*)\" "+
+                       "can be simplified to "+
+                       "\"${MACHINE_PLATFORM:MNetBSD-9.99.*}\".",
+               "NOTE: filename.mk:5: "+
+                       "\"!empty(MACHINE_PLATFORM:MNetBSD-[1-9][0-9].*)\" "+
+                       "can be simplified to "+
+                       "\"${MACHINE_PLATFORM:MNetBSD-[1-9][0-9].*}\".",
                "ERROR: filename.mk:5: The patterns \"NetBSD-9.99.*\" "+
                        "and \"NetBSD-[1-9][0-9].*\" cannot match at the same time.")
 
@@ -620,7 +628,7 @@ func (s *Suite) Test_MkCondChecker_check
 
        t.CheckOutputLines(
                "WARN: filename.mk:4: Numeric comparison > 6.5.",
-               "ERROR: filename.mk:4: The Python version must not be compared numerically.",
+               "ERROR: filename.mk:4: _PYTHON_VERSION must not be compared numerically.",
                "WARN: filename.mk:4: _PYTHON_VERSION is used but not defined.")
 }
 
@@ -648,6 +656,7 @@ func (s *Suite) Test_MkCondChecker_check
 func (s *Suite) Test_MkCondChecker_checkCompareWithNumPython(c *check.C) {
        t := s.Init(c)
 
+       t.SetUpCommandLine("--show-autofix")
        mklines := t.NewMkLines("filename.mk",
                MkCvsID,
                "",
@@ -661,14 +670,16 @@ func (s *Suite) Test_MkCondChecker_check
        mklines.Check()
 
        t.CheckOutputLines(
-               "WARN: filename.mk:3: "+
-                       "Variable names starting with an underscore "+
-                       "(_PYTHON_VERSION) are reserved "+
-                       "for internal pkgsrc use.",
                "ERROR: filename.mk:5: "+
-                       "The Python version must not be compared numerically.",
+                       "_PYTHON_VERSION must not be compared numerically.",
+               "AUTOFIX: filename.mk:5: "+
+                       "Replacing \"${_PYTHON_VERSION} < 38\" "+
+                       "with \"${PYTHON_VERSION} < 308\".",
                "ERROR: filename.mk:6: "+
-                       "The Python version must not be compared numerically.")
+                       "_PYTHON_VERSION must not be compared numerically.",
+               "AUTOFIX: filename.mk:6: "+
+                       "Replacing \"${_PYTHON_VERSION} < 310\" "+
+                       "with \"${PYTHON_VERSION} < 310\".")
 }
 
 func (s *Suite) Test_MkCondChecker_checkCompareVarStrCompiler(c *check.C) {
Index: pkgsrc/pkgtools/pkglint/files/mkvarusechecker.go
diff -u pkgsrc/pkgtools/pkglint/files/mkvarusechecker.go:1.15 pkgsrc/pkgtools/pkglint/files/mkvarusechecker.go:1.16
--- pkgsrc/pkgtools/pkglint/files/mkvarusechecker.go:1.15       Thu Mar  2 08:58:29 2023
+++ pkgsrc/pkgtools/pkglint/files/mkvarusechecker.go    Wed Mar 29 07:12:36 2023
@@ -61,7 +61,7 @@ func (ck *MkVarUseChecker) checkUndefine
                ck.MkLines.allVars.Mentioned(varname) != nil,
                ck.MkLines.pkg != nil && ck.MkLines.pkg.vars.IsDefinedSimilar(varname),
                containsVarUse(varname),
-               G.Pkgsrc.vartypes.IsDefinedCanon(varname),
+               G.Project.Types().IsDefinedCanon(varname),
                varname == "":
                return
        }
@@ -837,10 +837,7 @@ func (ck *MkVarUseChecker) checkBuildDef
 
 func (ck *MkVarUseChecker) checkDeprecated() {
        varname := ck.use.varname
-       instead := G.Pkgsrc.Deprecated[varname]
-       if instead == "" {
-               instead = G.Pkgsrc.Deprecated[varnameCanon(varname)]
-       }
+       instead := G.Project.Deprecated(varname)
        if instead == "" {
                return
        }

Index: pkgsrc/pkgtools/pkglint/files/mkcondsimplifier.go
diff -u pkgsrc/pkgtools/pkglint/files/mkcondsimplifier.go:1.4 pkgsrc/pkgtools/pkglint/files/mkcondsimplifier.go:1.5
--- pkgsrc/pkgtools/pkglint/files/mkcondsimplifier.go:1.4       Sun Jan 29 13:36:31 2023
+++ pkgsrc/pkgtools/pkglint/files/mkcondsimplifier.go   Wed Mar 29 07:12:36 2023
@@ -1,6 +1,7 @@
 package pkglint
 
 import (
+       "netbsd.org/pkglint/makepat"
        "netbsd.org/pkglint/textproc"
        "strings"
 )
@@ -282,13 +283,18 @@ func (s *MkCondSimplifier) simplifyMatch
        //
        // The same reasoning applies to the variable name, even though the
        // variable name typically only uses a restricted character set.
-       if !matches(varuse.Mod(), `^[*.:\w\[\]]+$`) {
+       if !matches(varuse.Mod(), `^[*+\-.:\w\[\]]+$`) {
+               return
+       }
+
+       mayMatchNumber, err := s.mayMatchNumber(pattern)
+       if err != nil {
                return
        }
 
        fixedPart := varname + modsExceptLast + ":M" + pattern
        from := condStr(neg, "!", "") + "empty(" + fixedPart + ")"
-       to := condStr(neg, "", "!") + "${" + fixedPart + "}"
+       to := condStr(neg, "", "!") + "${" + fixedPart + "}" + condStr(mayMatchNumber, " != \"\"", "")
 
        fix := s.MkLine.Autofix()
        fix.Notef("%q can be simplified to %q.", from, to)
@@ -318,3 +324,28 @@ func (s *MkCondSimplifier) isDefined(var
                vartype.Union().Contains(aclpUseLoadtime) &&
                vartype.IsDefinedIfInScope()
 }
+
+var numeric = makepat.Number()
+
+// In the conditional '.if ${EXPR}', the condition '${EXPR}' may evaluate to
+// a single word. If that word is numeric and evaluates to zero, the condition
+// evaluates to false.
+//
+// There are several words that evaluate to zero, such as 0, .0, 0.0, 1e-400,
+// -1e-400, +1e400.
+//
+// When simplifying the condition '!empty(EXPR:Mpattern)' to
+// '${EXPR:Mpattern}', if the pattern can result in a numeric word, the
+// result must be compared with an empty string by adding '!= ""', to
+// preserve the exact behavior.
+func (*MkCondSimplifier) mayMatchNumber(pattern string) (bool, error) {
+       if pattern == "" {
+               return false, nil
+       }
+       p, err := makepat.Compile(pattern)
+       if err != nil {
+               return true, err
+       }
+       both := makepat.Intersect(p, numeric)
+       return both.CanMatch(), nil
+}

Index: pkgsrc/pkgtools/pkglint/files/mkcondsimplifier_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mkcondsimplifier_test.go:1.2 pkgsrc/pkgtools/pkglint/files/mkcondsimplifier_test.go:1.3
--- pkgsrc/pkgtools/pkglint/files/mkcondsimplifier_test.go:1.2  Sat Nov 19 10:51:07 2022
+++ pkgsrc/pkgtools/pkglint/files/mkcondsimplifier_test.go      Wed Mar 29 07:12:36 2023
@@ -3,6 +3,7 @@ package pkglint
 import (
        "gopkg.in/check.v1"
        "netbsd.org/pkglint/regex"
+       "testing"
 )
 
 type MkCondSimplifierTester struct {
@@ -877,6 +878,61 @@ func (s *Suite) Test_MkCondSimplifier_si
                "AUTOFIX: filename.mk:6: "+
                        "Replacing \"!empty(IN_SCOPE_DEFINED:M[Nn][Oo])\" "+
                        "with \"${IN_SCOPE_DEFINED:tl} == no\".")
+
+       // When replacing the '!empty' with a plain expression, there's an
+       // edge case that differs in behavior. A condition evaluates to
+       // false if its expression value has a single word, that word is
+       // numeric and evaluates to 0.0. Since make parses scientific
+       // notation such as 12345e-400, even numbers that contain nonzero
+       // digits may evaluate to false.
+       t.testBeforeAndAfterPrefs(
+               ".if !empty(IN_SCOPE_DEFINED:M[0-9]*)",
+               ".if ${IN_SCOPE_DEFINED:M[0-9]*} != \"\"",
+               "NOTE: filename.mk:6: "+
+                       "\"!empty(IN_SCOPE_DEFINED:M[0-9]*)\" "+
+                       "can be simplified to "+
+                       "\"${IN_SCOPE_DEFINED:M[0-9]*} != \\\"\\\"\".",
+               "AUTOFIX: filename.mk:6: "+
+                       "Replacing \"!empty(IN_SCOPE_DEFINED:M[0-9]*)\" "+
+                       "with \"${IN_SCOPE_DEFINED:M[0-9]*} != \\\"\\\"\".")
+
+       // The pattern '[0123456789]' is equivalent to '[0-9]'.
+       t.testBeforeAndAfterPrefs(
+               ".if !empty(IN_SCOPE_DEFINED:M[0123456789]*)",
+               ".if ${IN_SCOPE_DEFINED:M[0123456789]*} != \"\"",
+               "NOTE: filename.mk:6: "+
+                       "\"!empty(IN_SCOPE_DEFINED:M[0123456789]*)\" "+
+                       "can be simplified to "+
+                       "\"${IN_SCOPE_DEFINED:M[0123456789]*} != \\\"\\\"\".",
+               "AUTOFIX: filename.mk:6: "+
+                       "Replacing \"!empty(IN_SCOPE_DEFINED:M[0123456789]*)\" "+
+                       "with \"${IN_SCOPE_DEFINED:M[0123456789]*} != \\\"\\\"\".")
+
+       // The 'e' may be part of a number, but there is no number that
+       // consists of letters only, therefore the '!= ""' is not necessary.
+       t.testBeforeAndAfterPrefs(
+               ".if !empty(IN_SCOPE_DEFINED:M[abcdef]*)",
+               ".if ${IN_SCOPE_DEFINED:M[abcdef]*}",
+               "NOTE: filename.mk:6: "+
+                       "\"!empty(IN_SCOPE_DEFINED:M[abcdef]*)\" "+
+                       "can be simplified to "+
+                       "\"${IN_SCOPE_DEFINED:M[abcdef]*}\".",
+               "AUTOFIX: filename.mk:6: "+
+                       "Replacing \"!empty(IN_SCOPE_DEFINED:M[abcdef]*)\" "+
+                       "with \"${IN_SCOPE_DEFINED:M[abcdef]*}\".")
+
+       // The letters 'abcd' may form part of a hex number, but there is no
+       // number that starts with any of these letters.
+       t.testBeforeAndAfterPrefs(
+               ".if !empty(IN_SCOPE_DEFINED:M[abcd]*)",
+               ".if ${IN_SCOPE_DEFINED:M[abcd]*}",
+               "NOTE: filename.mk:6: "+
+                       "\"!empty(IN_SCOPE_DEFINED:M[abcd]*)\" "+
+                       "can be simplified to "+
+                       "\"${IN_SCOPE_DEFINED:M[abcd]*}\".",
+               "AUTOFIX: filename.mk:6: "+
+                       "Replacing \"!empty(IN_SCOPE_DEFINED:M[abcd]*)\" "+
+                       "with \"${IN_SCOPE_DEFINED:M[abcd]*}\".")
 }
 
 func (s *Suite) Test_MkCondSimplifier_isDefined(c *check.C) {
@@ -902,3 +958,43 @@ func (s *Suite) Test_MkCondSimplifier_is
        t.CheckOutputLines(
                "WARN: filename.mk:3: UNDEFINED is used but not defined.")
 }
+
+func Test_MkCondSimplifier_mayMatchNumber(t *testing.T) {
+       tests := []struct {
+               pattern string
+               want    bool
+       }{
+               // The empty pattern matches only a single word, namely the
+               // empty string, and that word is not numeric. Except for the
+               // workaround in make's TryParseNumber, which treats the
+               // empty string as zero, for undocumented reasons.
+               {"", false},
+               // Can only match '0', whose numeric value is zero.
+               {"0", true},
+               // Can only match '1', whose numeric value is not zero.
+               {"1", true},
+               // Can match '0', '00000', and so on.
+               {"[0-9]", true},
+               // A pattern consisting of only nonzero digits cannot match a
+               // number whose numeric value is zero.
+               {"[1-9]", true},
+               // Can match '0', '0x0', '0e0', '0e1000'.
+               {"[0-9a-z]", true},
+               // Each number has at least one digit, for example, '.e+' is
+               // not syntactically valid.
+               {"[^0-9]", false},
+               // No word that ends in '.c' is numeric.
+               {"*.c", false},
+       }
+       for _, tt := range tests {
+               t.Run(tt.pattern, func(t *testing.T) {
+                       got, err := (*MkCondSimplifier).mayMatchNumber(nil, tt.pattern)
+                       if err != nil {
+                               t.Fatal(err)
+                       }
+                       if got != tt.want {
+                               t.Errorf("got %v for %q, want %v", got, tt.pattern, tt.want)
+                       }
+               })
+       }
+}

Index: pkgsrc/pkgtools/pkglint/files/mkline.go
diff -u pkgsrc/pkgtools/pkglint/files/mkline.go:1.87 pkgsrc/pkgtools/pkglint/files/mkline.go:1.88
--- pkgsrc/pkgtools/pkglint/files/mkline.go:1.87        Thu Mar  2 08:58:29 2023
+++ pkgsrc/pkgtools/pkglint/files/mkline.go     Wed Mar 29 07:12:36 2023
@@ -1342,8 +1342,7 @@ func (ind *Indentation) CheckFinish(file
 // which would confuse the devel/bmake parser.
 //
 // TODO: The allowed characters differ between the basename and the parameter
-//
-//     of the variable. The square bracket is only allowed in the parameter part.
+// of the variable. The square bracket is only allowed in the parameter part.
 var (
        // TODO: remove the ','
        VarbaseBytes = textproc.NewByteSet("A-Za-z_0-9-+,")

Index: pkgsrc/pkgtools/pkglint/files/mkline_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.86 pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.87
--- pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.86   Thu Mar  2 08:58:29 2023
+++ pkgsrc/pkgtools/pkglint/files/mkline_test.go        Wed Mar 29 07:12:36 2023
@@ -643,8 +643,8 @@ func (s *Suite) Test_MkLine_VariableNeed
                "MASTER_SITES=\t${HOMEPAGE}")
        mkline := mklines.mklines[1]
 
-       vuc := VarUseContext{G.Pkgsrc.vartypes.Canon("MASTER_SITES"), VucRunTime, VucQuotPlain, false}
-       nq := mkline.VariableNeedsQuoting(nil, NewMkVarUse("HOMEPAGE"), G.Pkgsrc.vartypes.Canon("HOMEPAGE"), &vuc)
+       vuc := VarUseContext{G.Pkgsrc.Types().Canon("MASTER_SITES"), VucRunTime, VucQuotPlain, false}
+       nq := mkline.VariableNeedsQuoting(nil, NewMkVarUse("HOMEPAGE"), G.Pkgsrc.Types().Canon("HOMEPAGE"), &vuc)
 
        t.CheckEquals(nq, no)
 
@@ -1025,8 +1025,7 @@ func (s *Suite) Test_MkLine_VariableNeed
 }
 
 // TODO: COMPILER_RPATH_FLAG and LINKER_RPATH_FLAG have different types
-//
-//     defined in vardefs.go; examine why.
+// defined in vardefs.go; examine why.
 func (s *Suite) Test_MkLine_VariableNeedsQuoting__shellword_part(c *check.C) {
        t := s.Init(c)
 

Index: pkgsrc/pkgtools/pkglint/files/mklines.go
diff -u pkgsrc/pkgtools/pkglint/files/mklines.go:1.77 pkgsrc/pkgtools/pkglint/files/mklines.go:1.78
--- pkgsrc/pkgtools/pkglint/files/mklines.go:1.77       Thu Mar  2 08:58:29 2023
+++ pkgsrc/pkgtools/pkglint/files/mklines.go    Wed Mar 29 07:12:36 2023
@@ -426,7 +426,9 @@ func (mklines *MkLines) checkAll() {
                "pre-package": true, "do-package": true, "post-package": true,
                "pre-clean": true, "do-clean": true, "post-clean": true}
 
-       mklines.lines.CheckCvsID(0, `#[\t ]+`, "# ")
+       if G.Pkgsrc != nil {
+               mklines.lines.CheckCvsID(0, `#[\t ]+`, "# ")
+       }
 
        substContext := NewSubstContext(mklines.pkg)
        var varalign VaralignBlock

Index: pkgsrc/pkgtools/pkglint/files/pkglint.go
diff -u pkgsrc/pkgtools/pkglint/files/pkglint.go:1.88 pkgsrc/pkgtools/pkglint/files/pkglint.go:1.89
--- pkgsrc/pkgtools/pkglint/files/pkglint.go:1.88       Thu Mar  2 08:58:29 2023
+++ pkgsrc/pkgtools/pkglint/files/pkglint.go    Wed Mar 29 07:12:36 2023
@@ -32,7 +32,8 @@ type Pkglint struct {
        Network,
        Recursive bool
 
-       Pkgsrc *Pkgsrc // Global data, mostly extracted from mk/*.
+       Project Project
+       Pkgsrc  *Pkgsrc // Global data, mostly extracted from mk/*.
 
        Todo CurrPathQueue // The files or directories that still need to be checked.
 
@@ -205,10 +206,12 @@ func (p *Pkglint) prepareMainLoop() {
                } else {
                        G.Logger.TechFatalf(firstDir, "Must be inside a pkgsrc tree.")
                }
+               p.Project = NewNetBSDProject()
        } else {
                p.Pkgsrc = NewPkgsrc(firstDir.JoinNoClean(relTopdir))
                p.Wip = p.Pkgsrc.IsWip(firstDir) // See Pkglint.checkMode.
                p.Pkgsrc.LoadInfrastructure()
+               p.Project = p.Pkgsrc
        }
 
        currentUser, err := user.Current()

Index: pkgsrc/pkgtools/pkglint/files/pkgsrc.go
diff -u pkgsrc/pkgtools/pkglint/files/pkgsrc.go:1.70 pkgsrc/pkgtools/pkglint/files/pkgsrc.go:1.71
--- pkgsrc/pkgtools/pkglint/files/pkgsrc.go:1.70        Thu Mar  2 08:58:29 2023
+++ pkgsrc/pkgtools/pkglint/files/pkgsrc.go     Wed Mar 29 07:12:36 2023
@@ -43,8 +43,8 @@ type Pkgsrc struct {
        // to BUILD_DEFS.
        UserDefinedVars Scope
 
-       Deprecated map[string]string
-       vartypes   VarTypeRegistry
+       deprecated map[string]string
+       types      VarTypeRegistry
 }
 
 func NewPkgsrc(dir CurrPath) *Pkgsrc {
@@ -72,7 +72,7 @@ func NewPkgsrc(dir CurrPath) *Pkgsrc {
 // simple, since setting up a realistic pkgsrc environment requires
 // a lot of files.
 func (src *Pkgsrc) LoadInfrastructure() {
-       src.vartypes.Init(src)
+       src.Types().Init(src)
        src.loadMasterSites()
        src.loadPkgOptions()
        src.changes.load(src)
@@ -339,7 +339,7 @@ func (src *Pkgsrc) addBuildDefs(varnames
 }
 
 func (src *Pkgsrc) initDeprecatedVars() {
-       src.Deprecated = map[string]string{
+       src.deprecated = map[string]string{
                // December 2003
                "FIX_RPATH": "It has been removed from pkgsrc in 2003.",
 
@@ -513,7 +513,7 @@ func (src *Pkgsrc) loadUntypedVars() {
 
        define := func(varcanon string, mkline *MkLine) {
                switch {
-               case src.vartypes.IsDefinedCanon(varcanon):
+               case src.Types().IsDefinedCanon(varcanon):
                        // Already defined, can also be a tool.
 
                case !matches(varcanon, `^[A-Z]`):
@@ -534,7 +534,7 @@ func (src *Pkgsrc) loadUntypedVars() {
                        if trace.Tracing {
                                trace.Stepf("Untyped variable %q in %s", varcanon, mkline)
                        }
-                       src.vartypes.DefineType(varcanon, unknownType)
+                       src.Types().DefineType(varcanon, unknownType)
                }
        }
 
@@ -640,6 +640,18 @@ func (src *Pkgsrc) loadDefaultBuildDefs(
                "USE_ABI_DEPENDS")
 }
 
+func (src *Pkgsrc) Deprecated(varname string) string {
+       deprecated := src.deprecated
+       if instead := deprecated[varname]; instead != "" {
+               return instead
+       }
+       return deprecated[varnameCanon(varname)]
+}
+
+func (src *Pkgsrc) Types() *VarTypeRegistry {
+       return &src.types
+}
+
 // Latest returns the latest package matching the given pattern.
 // It searches the category for subdirectories matching the given
 // regular expression, takes the latest of them and replaces its
@@ -746,7 +758,7 @@ func (src *Pkgsrc) VariableType(mklines 
        // When scanning mk/** for otherwise unknown variables, their type
        // is set to BtUnknown. These variables must not override the guess
        // based on the variable name.
-       vartype = src.vartypes.Canon(varname)
+       vartype = src.Types().Canon(varname)
        if vartype != nil && vartype.basicType != BtUnknown {
                return vartype
        }
@@ -832,7 +844,7 @@ func (src *Pkgsrc) guessVariableType(var
        // must take precedence over this rule, because otherwise, list
        // variables from the infrastructure would be guessed to be plain
        // variables.
-       vartype = src.vartypes.Canon(varname)
+       vartype = src.Types().Canon(varname)
        if vartype != nil {
                return vartype
        }

Index: pkgsrc/pkgtools/pkglint/files/scope.go
diff -u pkgsrc/pkgtools/pkglint/files/scope.go:1.3 pkgsrc/pkgtools/pkglint/files/scope.go:1.4
--- pkgsrc/pkgtools/pkglint/files/scope.go:1.3  Sun Jan 29 13:36:31 2023
+++ pkgsrc/pkgtools/pkglint/files/scope.go      Wed Mar 29 07:12:36 2023
@@ -6,13 +6,11 @@ import "sort"
 // in a certain scope, such as a package or a file.
 //
 // TODO: Decide whether the scope should consider variable assignments
-//
-//     from the pkgsrc infrastructure. For Package.checkGnuConfigureUseLanguages
-//     it would be better to ignore them completely.
+// from the pkgsrc infrastructure. For Package.checkGnuConfigureUseLanguages
+// it would be better to ignore them completely.
 //
 // TODO: Merge this code with Var, which defines essentially the
-//
-//     same features.
+// same features.
 //
 // See also substScope, which already analyzes the possible variable values
 // based on the conditional code paths.

Index: pkgsrc/pkgtools/pkglint/files/var.go
diff -u pkgsrc/pkgtools/pkglint/files/var.go:1.11 pkgsrc/pkgtools/pkglint/files/var.go:1.12
--- pkgsrc/pkgtools/pkglint/files/var.go:1.11   Thu Mar  2 08:58:29 2023
+++ pkgsrc/pkgtools/pkglint/files/var.go        Wed Mar 29 07:12:36 2023
@@ -92,9 +92,8 @@ func (v *Var) AddRef(varname string) {
 // cases may be implemented later.
 //
 // TODO: Simple .for loops that append to the variable are ok as well.
-//
-//     (This needs to be worded more precisely since that part potentially
-//     adds a lot of complexity to the whole data structure.)
+// (This needs to be worded more precisely since that part potentially
+// adds a lot of complexity to the whole data structure.)
 //
 // Variable assignments in the pkgsrc infrastructure are taken into account
 // for determining whether a variable is constant.

Index: pkgsrc/pkgtools/pkglint/files/varalignblock_test.go
diff -u pkgsrc/pkgtools/pkglint/files/varalignblock_test.go:1.19 pkgsrc/pkgtools/pkglint/files/varalignblock_test.go:1.20
--- pkgsrc/pkgtools/pkglint/files/varalignblock_test.go:1.19    Sun Jan 29 13:36:31 2023
+++ pkgsrc/pkgtools/pkglint/files/varalignblock_test.go Wed Mar 29 07:12:36 2023
@@ -1963,8 +1963,7 @@ func (s *Suite) Test_VaralignBlock__var_
 // initial line, this is intentional.
 //
 // TODO: Make this rule more general: if the indentation of the continuation
-//
-//     lines is more than the initial line, it is intentional.
+// lines is more than the initial line, it is intentional.
 func (s *Suite) Test_VaralignBlock__var_tab24_value_space_cont_tabs32_value_space_cont_tabs32_value(c *check.C) {
        vt := NewVaralignTester(s, c)
        vt.Input(

Index: pkgsrc/pkgtools/pkglint/files/vardefs.go
diff -u pkgsrc/pkgtools/pkglint/files/vardefs.go:1.107 pkgsrc/pkgtools/pkglint/files/vardefs.go:1.108
--- pkgsrc/pkgtools/pkglint/files/vardefs.go:1.107      Sun Jan 29 13:36:31 2023
+++ pkgsrc/pkgtools/pkglint/files/vardefs.go    Wed Mar 29 07:12:36 2023
@@ -36,6 +36,8 @@ func NewVarTypeRegistry() VarTypeRegistr
        return VarTypeRegistry{make(map[string]*Vartype), make(map[string][]ACLEntry)}
 }
 
+// Canon looks up the type of the variable, first by trying the exact
+// variable name, then by trying the canonicalized variable name.
 func (reg *VarTypeRegistry) Canon(varname string) *Vartype {
        vartype := reg.types[varname]
        if vartype == nil {
@@ -101,9 +103,8 @@ func (reg *VarTypeRegistry) DefineName(v
 //   - which packages need this custom permission set.
 //
 // TODO: When prefixed with "infra:", the entry should only
-//
-//     apply to files within the pkgsrc infrastructure. Without this prefix,
-//     the pattern should only apply to files outside the pkgsrc infrastructure.
+// apply to files within the pkgsrc infrastructure. Without this prefix,
+// the pattern should only apply to files outside the pkgsrc infrastructure.
 func (reg *VarTypeRegistry) acl(varname string, basicType *BasicType, options vartypeOptions, aclEntries ...string) {
        parsedEntries := reg.parseACLEntries(varname, aclEntries...)
        reg.Define(varname, basicType, options, parsedEntries)
@@ -209,8 +210,7 @@ func (reg *VarTypeRegistry) pkglistbl3ra
 // when these files are included.
 //
 // TODO: These timing issues should be handled separately from the permissions.
-//
-//     They can be made more precise.
+// They can be made more precise.
 func (reg *VarTypeRegistry) sys(varname string, basicType *BasicType, options ...vartypeOptions) {
        reg.DefineName(varname, basicType, reg.options(SystemProvided, options), "sys")
 }

Index: pkgsrc/pkgtools/pkglint/files/vardefs_test.go
diff -u pkgsrc/pkgtools/pkglint/files/vardefs_test.go:1.30 pkgsrc/pkgtools/pkglint/files/vardefs_test.go:1.31
--- pkgsrc/pkgtools/pkglint/files/vardefs_test.go:1.30  Thu Mar  2 08:58:29 2023
+++ pkgsrc/pkgtools/pkglint/files/vardefs_test.go       Wed Mar 29 07:12:36 2023
@@ -161,7 +161,7 @@ func (s *Suite) Test_VarTypeRegistry_enu
 
        t.ExpectFatal(
                func() {
-                       G.Pkgsrc.vartypes.enumFromDirs(
+                       G.Project.Types().enumFromDirs(
                                G.Pkgsrc, "category", `^pack.*`, "$0", "default")
                },
                "FATAL: ~/category: Must contain at least 1 "+
@@ -193,7 +193,7 @@ func (s *Suite) Test_VarTypeRegistry_enu
 
        t.ExpectFatal(
                func() {
-                       G.Pkgsrc.vartypes.enumFromFiles(G.Pkgsrc,
+                       G.Project.Types().enumFromFiles(G.Pkgsrc,
                                "mk/platform", `^(\w+)\.mk$`, "$1", "default")
                },
                "FATAL: ~/mk/platform: Must contain at least 1 "+
@@ -216,10 +216,10 @@ func (s *Suite) Test_VarTypeRegistry_Ini
        t := s.Init(c)
 
        src := NewPkgsrc(t.File("."))
-       src.vartypes.Init(src)
+       src.Types().Init(src)
 
-       t.CheckEquals(src.vartypes.Canon("BSD_MAKE_ENV").basicType.name, "ShellWord")
-       t.CheckEquals(src.vartypes.Canon("USE_BUILTIN.*").basicType.name, "YesNoIndirectly")
+       t.CheckEquals(src.Types().Canon("BSD_MAKE_ENV").basicType.name, "ShellWord")
+       t.CheckEquals(src.Types().Canon("USE_BUILTIN.*").basicType.name, "YesNoIndirectly")
 }
 
 func (s *Suite) Test_VarTypeRegistry_Init__LP64PLATFORMS(c *check.C) {

Index: pkgsrc/pkgtools/pkglint/files/vartype.go
diff -u pkgsrc/pkgtools/pkglint/files/vartype.go:1.55 pkgsrc/pkgtools/pkglint/files/vartype.go:1.56
--- pkgsrc/pkgtools/pkglint/files/vartype.go:1.55       Sun Jan 29 13:36:31 2023
+++ pkgsrc/pkgtools/pkglint/files/vartype.go    Wed Mar 29 07:12:36 2023
@@ -508,8 +508,7 @@ func init() {
 }
 
 // TODO: Move these values to VarTypeRegistry.Init and read them from the
-//
-//     pkgsrc infrastructure files, as far as possible.
+// pkgsrc infrastructure files, as far as possible.
 const (
        // See mk/emulator/emulator-vars.mk.
        emulOpsysValues = "" +

Index: pkgsrc/pkgtools/pkglint/files/vartype_test.go
diff -u pkgsrc/pkgtools/pkglint/files/vartype_test.go:1.28 pkgsrc/pkgtools/pkglint/files/vartype_test.go:1.29
--- pkgsrc/pkgtools/pkglint/files/vartype_test.go:1.28  Sat Jan  1 12:44:25 2022
+++ pkgsrc/pkgtools/pkglint/files/vartype_test.go       Wed Mar 29 07:12:36 2023
@@ -54,14 +54,14 @@ func (s *Suite) Test_Vartype_EffectivePe
 
        t.SetUpVartypes()
 
-       if typ := G.Pkgsrc.vartypes.Canon("PREFIX"); t.CheckNotNil(typ) {
+       if typ := G.Pkgsrc.Types().Canon("PREFIX"); t.CheckNotNil(typ) {
                t.CheckEquals(typ.basicType.name, "Pathname")
                t.CheckDeepEquals(typ.aclEntries, []ACLEntry{NewACLEntry("*", aclpUse)})
                t.CheckEquals(typ.EffectivePermissions("Makefile"), aclpUse)
                t.CheckEquals(typ.EffectivePermissions("buildlink3.mk"), aclpUse)
        }
 
-       if typ := G.Pkgsrc.vartypes.Canon("EXTRACT_OPTS"); t.CheckNotNil(typ) {
+       if typ := G.Pkgsrc.Types().Canon("EXTRACT_OPTS"); t.CheckNotNil(typ) {
                t.CheckEquals(typ.basicType.name, "ShellWord")
                t.CheckEquals(typ.EffectivePermissions("Makefile"), aclpAllWrite|aclpUse)
                t.CheckEquals(typ.EffectivePermissions("options.mk"), aclpAllWrite|aclpUse)

Index: pkgsrc/pkgtools/pkglint/files/vartypecheck.go
diff -u pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.103 pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.104
--- pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.103 Sun Jan 29 13:36:31 2023
+++ pkgsrc/pkgtools/pkglint/files/vartypecheck.go       Wed Mar 29 07:12:36 2023
@@ -847,7 +847,7 @@ func (cv *VartypeCheck) MachinePlatformP
                // provided in a type registry where they could be looked up
                // by a name. The following line therefore assumes that OPSYS
                // is an operating system name, which sounds like a safe bet.
-               btOpsys := G.Pkgsrc.vartypes.types["OPSYS"].basicType
+               btOpsys := G.Pkgsrc.Types().types["OPSYS"].basicType
 
                opsysCv := cv.WithVarnameValueMatch("the operating system part of "+cv.Varname, opsysPattern)
                btOpsys.checker(opsysCv)

Index: pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go
diff -u pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go:1.95 pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go:1.96
--- pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go:1.95     Sun Jan 29 13:36:31 2023
+++ pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go  Wed Mar 29 07:12:36 2023
@@ -821,7 +821,7 @@ func (s *Suite) Test_VartypeCheck_EmulPl
 
 func (s *Suite) Test_VartypeCheck_Enum(c *check.C) {
        basicType := enum("jdk1 jdk2 jdk4")
-       G.Pkgsrc.vartypes.Define("JDK", basicType, UserSettable, []ACLEntry{})
+       G.Pkgsrc.Types().Define("JDK", basicType, UserSettable, []ACLEntry{})
        vt := NewVartypeCheckTester(s.Init(c), basicType)
 
        vt.Varname("JDK")

Index: pkgsrc/pkgtools/pkglint/files/makepat/pat.go
diff -u pkgsrc/pkgtools/pkglint/files/makepat/pat.go:1.4 pkgsrc/pkgtools/pkglint/files/makepat/pat.go:1.5
--- pkgsrc/pkgtools/pkglint/files/makepat/pat.go:1.4    Sun Jan 29 13:36:32 2023
+++ pkgsrc/pkgtools/pkglint/files/makepat/pat.go        Wed Mar 29 07:12:36 2023
@@ -19,16 +19,16 @@ type state struct {
 
 type transition struct {
        min, max byte
-       to       StateID
+       to       stateID
 }
 
-type StateID uint16
+type stateID uint16
 
 // Compile parses a pattern, including the error checking that is missing
 // from bmake.
 func Compile(pattern string) (*Pattern, error) {
        var p Pattern
-       s := p.AddState(false)
+       s := p.addState(false)
 
        lex := textproc.NewLexer(pattern)
        for !lex.EOF() {
@@ -36,18 +36,18 @@ func Compile(pattern string) (*Pattern, 
 
                switch ch {
                case '*':
-                       p.AddTransition(s, 0, 255, s)
+                       p.addTransition(s, 0, 255, s)
                case '?':
-                       next := p.AddState(false)
-                       p.AddTransition(s, 0, 255, next)
+                       next := p.addState(false)
+                       p.addTransition(s, 0, 255, next)
                        s = next
                case '\\':
                        if lex.EOF() {
                                return nil, errors.New("unfinished escape sequence")
                        }
                        ch := lex.NextByte()
-                       next := p.AddState(false)
-                       p.AddTransition(s, ch, ch, next)
+                       next := p.addState(false)
+                       p.addTransition(s, ch, ch, next)
                        s = next
                case '[':
                        next, err := compileCharClass(&p, lex, ch, s)
@@ -56,8 +56,8 @@ func Compile(pattern string) (*Pattern, 
                        }
                        s = next
                default:
-                       next := p.AddState(false)
-                       p.AddTransition(s, ch, ch, next)
+                       next := p.addState(false)
+                       p.addTransition(s, ch, ch, next)
                        s = next
                }
        }
@@ -66,10 +66,10 @@ func Compile(pattern string) (*Pattern, 
        return &p, nil
 }
 
-func compileCharClass(p *Pattern, lex *textproc.Lexer, ch byte, s StateID) (StateID, error) {
+func compileCharClass(p *Pattern, lex *textproc.Lexer, ch byte, s stateID) (stateID, error) {
        negate := lex.SkipByte('^')
-       chars := make([]bool, 256)
-       next := p.AddState(false)
+       var chars [256]bool
+       next := p.addState(false)
        for {
                if lex.EOF() {
                        return 0, errors.New("unfinished character class")
@@ -99,6 +99,11 @@ func compileCharClass(p *Pattern, lex *t
                }
        }
 
+       p.addTransitions(s, &chars, next)
+       return next, nil
+}
+
+func (p *Pattern) addTransitions(from stateID, chars *[256]bool, to stateID) {
        start := 0
        for start < len(chars) && !chars[start] {
                start++
@@ -111,7 +116,7 @@ func compileCharClass(p *Pattern, lex *t
                }
 
                if start < end {
-                       p.AddTransition(s, byte(start), byte(end-1), next)
+                       p.addTransition(from, byte(start), byte(end-1), to)
                }
 
                start = end
@@ -119,15 +124,14 @@ func compileCharClass(p *Pattern, lex *t
                        start++
                }
        }
-       return next, nil
 }
 
-func (p *Pattern) AddState(end bool) StateID {
+func (p *Pattern) addState(end bool) stateID {
        p.states = append(p.states, state{nil, end})
-       return StateID(len(p.states) - 1)
+       return stateID(len(p.states) - 1)
 }
 
-func (p *Pattern) AddTransition(from StateID, min, max byte, to StateID) {
+func (p *Pattern) addTransition(from stateID, min, max byte, to stateID) {
        state := &p.states[from]
        state.transitions = append(state.transitions, transition{min, max, to})
 }
@@ -178,15 +182,15 @@ func (p *Pattern) Match(s string) bool {
 func Intersect(p1, p2 *Pattern) *Pattern {
        var res Pattern
 
-       newState := make(map[[2]StateID]StateID)
+       newState := make(map[[2]stateID]stateID)
 
        // stateFor returns the state ID in the intersection,
        // creating it if necessary.
-       stateFor := func(s1, s2 StateID) StateID {
-               key := [2]StateID{s1, s2}
+       stateFor := func(s1, s2 stateID) stateID {
+               key := [2]stateID{s1, s2}
                ns, ok := newState[key]
                if !ok {
-                       ns = res.AddState(p1.states[s1].end && p2.states[s2].end)
+                       ns = res.addState(p1.states[s1].end && p2.states[s2].end)
                        newState[key] = ns
                }
                return ns
@@ -202,16 +206,18 @@ func Intersect(p1, p2 *Pattern) *Pattern
                                        min := bmax(t1.min, t2.min)
                                        max := bmin(t1.max, t2.max)
                                        if min <= max {
-                                               from := stateFor(StateID(i1), StateID(i2))
+                                               from := stateFor(stateID(i1), stateID(i2))
                                                to := stateFor(t1.to, t2.to)
-                                               res.AddTransition(from, min, max, to)
+                                               res.addTransition(from, min, max, to)
                                        }
                                }
                        }
                }
        }
 
-       return res.optimized()
+       // If the returned pattern is used more than once,
+       // consider calling .optimize first.
+       return &res
 }
 
 func (p *Pattern) optimized() *Pattern {
@@ -252,7 +258,7 @@ func (p *Pattern) reachable() []bool {
        return reachable
 }
 
-// relevant returns all states from which and end state is reachable.
+// relevant returns all states from which an end state is reachable.
 // In optimized patterns, each state is relevant.
 func (p *Pattern) relevant(reachable []bool) []bool {
        relevant := make([]bool, len(p.states))
@@ -276,7 +282,7 @@ func (p *Pattern) relevant(reachable []b
                        changed = true
                        for from, st := range p.states {
                                for _, tr := range st.transitions {
-                                       if tr.to == StateID(to) && reachable[from] &&
+                                       if tr.to == stateID(to) && reachable[from] &&
                                                progress[from] == 0 {
                                                progress[from] = 1
                                        }
@@ -296,17 +302,17 @@ func (p *Pattern) relevant(reachable []b
 func (p *Pattern) compressed(relevant []bool) *Pattern {
        var opt Pattern
 
-       newIDs := make([]StateID, len(p.states))
+       newIDs := make([]stateID, len(p.states))
        for i, r := range relevant {
                if r {
-                       newIDs[i] = opt.AddState(p.states[i].end)
+                       newIDs[i] = opt.addState(p.states[i].end)
                }
        }
 
        for from, s := range p.states {
                for _, t := range s.transitions {
                        if relevant[from] && relevant[t.to] {
-                               opt.AddTransition(newIDs[from], t.min, t.max, newIDs[t.to])
+                               opt.addTransition(newIDs[from], t.min, t.max, newIDs[t.to])
                        }
                }
        }
@@ -335,6 +341,145 @@ func (p *Pattern) CanMatch() bool {
        return false
 }
 
+// Number creates a pattern that matches integer or floating point constants,
+// as in C99, both decimal and hex.
+func Number() *Pattern {
+       // The states and transitions are taken from a manually constructed
+       // hand-drawn state diagram, based on the syntax rules from C99 6.4.4.
+
+       const (
+               start stateID = iota
+               sign
+
+               dec
+               decDot
+               decFrac
+
+               zero
+               zeroX
+
+               hex
+               hexDot
+               hexFrac
+
+               exp
+               expSign
+               expDec
+       )
+
+       return &Pattern{
+               states: []state{
+                       start: {
+                               []transition{
+                                       {'+', '+', sign},
+                                       {'-', '-', sign},
+                                       {'0', '9', dec},
+                                       {'.', '.', decDot},
+                                       {'0', '9', decFrac},
+                                       {'0', '0', zero},
+                               },
+                               false,
+                       },
+                       sign: {
+                               []transition{
+                                       {'0', '9', dec},
+                                       {'.', '.', decDot},
+                                       {'0', '9', decFrac},
+                                       {'0', '0', zero},
+                               },
+                               false,
+                       },
+                       dec: {
+                               []transition{
+                                       {'0', '9', dec},
+                                       {'.', '.', decFrac},
+                               },
+                               true,
+                       },
+                       decDot: {
+                               []transition{
+                                       {'0', '9', decFrac},
+                               },
+                               false,
+                       },
+                       decFrac: {
+                               []transition{
+                                       {'0', '9', decFrac},
+                                       {'E', 'E', exp},
+                                       {'e', 'e', exp},
+                               },
+                               true,
+                       },
+                       zero: {
+                               []transition{
+                                       {'X', 'X', zeroX},
+                                       {'x', 'x', zeroX},
+                               },
+                               false,
+                       },
+                       zeroX: {
+                               []transition{
+                                       {'0', '9', hex},
+                                       {'A', 'F', hex},
+                                       {'a', 'f', hex},
+                                       {'.', '.', hexDot},
+                                       {'0', '9', hexFrac},
+                                       {'A', 'F', hexFrac},
+                                       {'a', 'f', hexFrac},
+                               },
+                               false,
+                       },
+                       hex: {
+                               []transition{
+                                       {'0', '9', hex},
+                                       {'A', 'F', hex},
+                                       {'a', 'f', hex},
+                                       {'.', '.', hexFrac},
+                               },
+                               true,
+                       },
+                       hexDot: {
+                               []transition{
+                                       {'0', '9', hexFrac},
+                                       {'A', 'F', hexFrac},
+                                       {'a', 'f', hexFrac},
+                               },
+                               false,
+                       },
+                       hexFrac: {
+                               []transition{
+                                       {'0', '9', hexFrac},
+                                       {'A', 'F', hexFrac},
+                                       {'a', 'f', hexFrac},
+                                       {'P', 'P', exp},
+                                       {'p', 'p', exp},
+                               },
+                               false,
+                       },
+                       exp: {
+                               []transition{
+                                       {'+', '+', expSign},
+                                       {'-', '-', expSign},
+                                       {'0', '9', expDec},
+                               },
+                               false,
+                       },
+                       expSign: {
+                               []transition{
+                                       {'0', '9', expDec},
+                               },
+                               false,
+                       },
+                       expDec: {
+                               []transition{
+                                       {'0', '9', expDec},
+                               },
+                               true,
+                       },
+               },
+       }
+}
+
 func bmin(a, b byte) byte {
        if a < b {
                return a
Index: pkgsrc/pkgtools/pkglint/files/makepat/pat_test.go
diff -u pkgsrc/pkgtools/pkglint/files/makepat/pat_test.go:1.4 pkgsrc/pkgtools/pkglint/files/makepat/pat_test.go:1.5
--- pkgsrc/pkgtools/pkglint/files/makepat/pat_test.go:1.4       Sun Aug  8 22:04:15 2021
+++ pkgsrc/pkgtools/pkglint/files/makepat/pat_test.go   Wed Mar 29 07:12:36 2023
@@ -2,6 +2,7 @@ package makepat
 
 import (
        "netbsd.org/pkglint/intqa"
+       "netbsd.org/pkglint/textproc"
        "reflect"
        "testing"
 )
@@ -54,11 +55,57 @@ func Test_compileCharClass(t *testing.T)
        }
 }
 
-func Test_Pattern_AddState(t *testing.T) {
+func Test_Pattern_addTransitions(t *testing.T) {
+       none := textproc.NewByteSet("")
+       numeric := textproc.NewByteSet("-+0-9.Ee")
+       all := none.Inverse()
+
+       tests := []struct {
+               name    string
+               bs      *textproc.ByteSet
+               example byte
+               want    bool
+       }{
+               {"none min", none, 0, false},
+               {"none max", none, 255, false},
+               {"all min", all, 0, true},
+               {"all max", all, 255, true},
+               {"numeric", numeric, '*', false},
+               {"numeric", numeric, '+', true},
+               {"numeric", numeric, ',', false},
+               {"numeric", numeric, '-', true},
+               {"numeric", numeric, '.', true},
+               {"numeric", numeric, '/', false},
+               {"numeric", numeric, '0', true},
+               {"numeric", numeric, '9', true},
+               {"numeric", numeric, ':', false},
+               {"numeric", numeric, 'D', false},
+               {"numeric", numeric, 'E', true},
+               {"numeric", numeric, 'F', false},
+       }
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       var p Pattern
+                       s0 := p.addState(false)
+                       s1 := p.addState(true)
+                       var chars [256]bool
+                       for i := 0; i < 256; i++ {
+                               chars[i] = tt.bs.Contains(byte(i))
+                       }
+                       p.addTransitions(s0, &chars, s1)
+                       got := p.Match(string([]byte{tt.example}))
+                       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)
+       p.addState(false)
+       p.addState(true)
 
        expected := Pattern{states: []state{{nil, false}, {nil, true}}}
        if !reflect.DeepEqual(p, expected) {
@@ -66,13 +113,13 @@ func Test_Pattern_AddState(t *testing.T)
        }
 }
 
-func Test_Pattern_AddTransition(t *testing.T) {
+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)
+       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},
@@ -166,38 +213,63 @@ func Test_Pattern_Match(t *testing.T) {
 }
 
 func Test_Intersect(t *testing.T) {
+       // The state machine of the compiled patterns is more powerful than
+       // the string representation of the patterns, therefore the
+       // intersected pattern is not visualized. Instead, it is tested using
+       // a single example string.
        tests := []struct {
                pattern1 string
                pattern2 string
-               str      string
-               matches  bool
                canMatch bool
+               example  string
+               matches  bool
        }{
-               {"N-*", "N-*", "N-*", true, true},
-               {"N-9.99.*", "N-[1-9].*", "", false, true},
-               {"N-9.99.*", "N-[1-9][0-9].*", "", false, false},
-               {"*.c", "*.h", "", false, false},
-               {"a*", "*b", "ab", true, true},
-               {"a*bc", "ab*c", "abc", true, true},
+               {"N-*", "N-*", true, "N-*", true},
+               {"N-9.99.*", "N-[1-9].*", true, "", false},
+               {"N-9.99.*", "N-[1-9][0-9].*", false, "", false},
+               {"*.c", "*.h", false, "", false},
+               {"a*", "*b", true, "ab", true},
+               {"a*bc", "ab*c", true, "abc", true},
+               {"*1*", "*2*", true, "asdf", false},
+               {"*1*", "*2*", true, "a1a", false},
+               {"*1*", "*2*", true, "a2a", false},
+               {"*1*", "*2*", true, "a12a", true},
+               {"*1*", "*2*", true, "a21a", true},
+               {"*[^0-9-+eE.]*", "*.c", true, ".c", true},
+               {"*[^0-9-+eE.]*", "*.c", true, "0.c", true},
+               {"*[^0-9-+eE.]*", "*.e", true, "0.e", false},
+               {"*[^0-9-+eE.]*", "*.e", true, "a.e", true},
+               {"*[^0-9-+eE.]*", "*.0", true, "000a0.0", true},
+               {"*[^0-9-+eE.]*", "*.0", true, "0000.0", false},
+               {"[0-9]", "[a-f]", false, "0", false},
+               {"[0-9]", "[0a-f]", true, "0", true},
+               {"[0-9]", "[0a-f]", true, "1", false},
+               {"[0-9]", "[0a-f]", true, "00", false},
        }
        for _, tt := range tests {
-               t.Run(tt.str, func(t *testing.T) {
-                       a1, err1 := Compile(tt.pattern1)
-                       a2, err2 := Compile(tt.pattern2)
+               t.Run(tt.example, func(t *testing.T) {
+                       p1, err1 := Compile(tt.pattern1)
+                       p2, err2 := Compile(tt.pattern2)
                        if err1 != nil {
                                t.Fatal(err1)
                        }
                        if err2 != nil {
                                t.Fatal(err2)
                        }
-                       a := Intersect(a1, a2)
-                       matches := a.Match(tt.str)
+                       both := Intersect(p1, p2)
+                       canMatch := both.CanMatch()
+                       if canMatch != tt.canMatch {
+                               t.Errorf("CanMatch() = %v, want %v", canMatch, tt.canMatch)
+                       }
+                       matches := both.Match(tt.example)
                        if matches != tt.matches {
                                t.Errorf("Match() = %v, want %v", matches, tt.matches)
                        }
-                       canMatch := a.CanMatch()
-                       if canMatch != tt.canMatch {
-                               t.Errorf("CanMatch() = %v, want %v", canMatch, tt.canMatch)
+                       if matches && !p1.Match(tt.example) {
+                               t.Errorf("example %q doesn't match pattern1 %q", tt.example, tt.pattern1)
+                       }
+                       if matches && !p2.Match(tt.example) {
+                               t.Errorf("example %q doesn't match pattern2 %q", tt.example, tt.pattern2)
                        }
                })
        }
@@ -205,12 +277,12 @@ 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)
+       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()
 
@@ -297,6 +369,49 @@ func Test_Pattern_CanMatch(t *testing.T)
 
 }
 
+func Test_Number(t *testing.T) {
+       tests := []struct {
+               example string
+               want    bool
+       }{
+               {"", false},
+               {".", false},
+               {"0x", false},
+               {"0xa", true},
+               {"+0xa", true},
+               {"-0xa", true},
+               {"0xa.", false},
+               {"0xa.a", false},
+               {"0xa.aa", false},
+               {"0xa.p", false},
+               {"0xa.p-1", true},
+               {"0xa.p1", true},
+               {"0xa.p11", true},
+               {"0xaa.", false},
+               {"1", true},
+               {"+1", true},
+               {"-1", true},
+               {"1.", true},
+               {"1.1", true},
+               {"1.11", true},
+               {"1.1e", false},
+               {"1.1e+", false},
+               {"1.1e+1", true},
+               {"1.1e+11", true},
+               {"1.1e-1", true},
+               {"1.e+1", true},
+               {"11", true},
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.example, func(t *testing.T) {
+                       if got := Number().Match(tt.example); got != tt.want {
+                               t.Errorf("got %v, want %v", got, tt.want)
+                       }
+               })
+       }
+}
+
 func Test_bmin(t *testing.T) {
        if bmin(0, 255) != 0 {
                t.Error()

Index: pkgsrc/pkgtools/pkglint/files/textproc/lexer.go
diff -u pkgsrc/pkgtools/pkglint/files/textproc/lexer.go:1.12 pkgsrc/pkgtools/pkglint/files/textproc/lexer.go:1.13
--- pkgsrc/pkgtools/pkglint/files/textproc/lexer.go:1.12        Sun Aug  8 22:04:15 2021
+++ pkgsrc/pkgtools/pkglint/files/textproc/lexer.go     Wed Mar 29 07:12:36 2023
@@ -265,7 +265,8 @@ func NewByteSet(chars string) *ByteSet {
        return &set
 }
 
-// Inverse returns a byte set that matches the inverted set of bytes.
+// Inverse returns a byte set that contains exactly those bytes that
+// are not in the given set.
 func (bs *ByteSet) Inverse() *ByteSet {
        var inv ByteSet
        for i := 0; i < 256; i++ {



Home | Main Index | Thread Index | Old Index