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:           Sun Jul 24 20:07:20 UTC 2022

Modified Files:
        pkgsrc/pkgtools/pkglint: Makefile
        pkgsrc/pkgtools/pkglint/files: mkassignchecker.go mkcondchecker.go
            mkcondchecker_test.go mkparser.go mktypes.go package_test.go
            redundantscope.go vardefs.go vartypecheck.go vartypecheck_test.go
Added Files:
        pkgsrc/pkgtools/pkglint/files: mkcondsimplifier.go
            mkcondsimplifier_test.go

Log Message:
pkgtools/pkglint: update to 22.2.3

Changes since 22.2.2:

CHECK_WRKREF is known to pkglint, which prevents conditions using this
variable from being simplified in a wrong way.

For variables that are guaranteed to be defined, suggest to simplify the
condition '!empty(VAR:M[Yy][Ee][Ss])' to '${VAR:M[Yy][Ee][Ss]}', as that
reduces the number of negations in the condition.

Detect redundant WRKSRC definitions and suggest to remove them.

Fix wrong "c99 is not valid for USE_LANGUAGES" warnings.


To generate a diff of this commit:
cvs rdiff -u -r1.722 -r1.723 pkgsrc/pkgtools/pkglint/Makefile
cvs rdiff -u -r1.14 -r1.15 pkgsrc/pkgtools/pkglint/files/mkassignchecker.go \
    pkgsrc/pkgtools/pkglint/files/redundantscope.go
cvs rdiff -u -r1.13 -r1.14 pkgsrc/pkgtools/pkglint/files/mkcondchecker.go
cvs rdiff -u -r1.11 -r1.12 \
    pkgsrc/pkgtools/pkglint/files/mkcondchecker_test.go
cvs rdiff -u -r0 -r1.1 pkgsrc/pkgtools/pkglint/files/mkcondsimplifier.go \
    pkgsrc/pkgtools/pkglint/files/mkcondsimplifier_test.go
cvs rdiff -u -r1.44 -r1.45 pkgsrc/pkgtools/pkglint/files/mkparser.go
cvs rdiff -u -r1.26 -r1.27 pkgsrc/pkgtools/pkglint/files/mktypes.go
cvs rdiff -u -r1.90 -r1.91 pkgsrc/pkgtools/pkglint/files/package_test.go
cvs rdiff -u -r1.104 -r1.105 pkgsrc/pkgtools/pkglint/files/vardefs.go
cvs rdiff -u -r1.100 -r1.101 pkgsrc/pkgtools/pkglint/files/vartypecheck.go
cvs rdiff -u -r1.92 -r1.93 pkgsrc/pkgtools/pkglint/files/vartypecheck_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.722 pkgsrc/pkgtools/pkglint/Makefile:1.723
--- pkgsrc/pkgtools/pkglint/Makefile:1.722      Wed Jul 13 16:03:04 2022
+++ pkgsrc/pkgtools/pkglint/Makefile    Sun Jul 24 20:07:20 2022
@@ -1,7 +1,6 @@
-# $NetBSD: Makefile,v 1.722 2022/07/13 16:03:04 bsiegert Exp $
+# $NetBSD: Makefile,v 1.723 2022/07/24 20:07:20 rillig Exp $
 
-PKGNAME=       pkglint-22.2.2
-PKGREVISION=   1
+PKGNAME=       pkglint-22.2.3
 CATEGORIES=    pkgtools
 DISTNAME=      tools
 MASTER_SITES=  ${MASTER_SITE_GITHUB:=golang/}

Index: pkgsrc/pkgtools/pkglint/files/mkassignchecker.go
diff -u pkgsrc/pkgtools/pkglint/files/mkassignchecker.go:1.14 pkgsrc/pkgtools/pkglint/files/mkassignchecker.go:1.15
--- pkgsrc/pkgtools/pkglint/files/mkassignchecker.go:1.14       Sat Jul  9 06:40:55 2022
+++ pkgsrc/pkgtools/pkglint/files/mkassignchecker.go    Sun Jul 24 20:07:20 2022
@@ -494,6 +494,11 @@ func (ck *MkAssignChecker) checkRight() 
        mkLineChecker := NewMkLineChecker(ck.MkLines, ck.MkLine)
        mkLineChecker.checkText(value)
        mkLineChecker.checkVartype(varname, op, value, comment)
+       if mkline.IsEmpty() {
+               // The line type can change due to an Autofix, see for example
+               // VartypeCheck.WrkdirSubdirectory.
+               return
+       }
 
        ck.checkMisc()
 
Index: pkgsrc/pkgtools/pkglint/files/redundantscope.go
diff -u pkgsrc/pkgtools/pkglint/files/redundantscope.go:1.14 pkgsrc/pkgtools/pkglint/files/redundantscope.go:1.15
--- pkgsrc/pkgtools/pkglint/files/redundantscope.go:1.14        Wed Jul 22 19:26:30 2020
+++ pkgsrc/pkgtools/pkglint/files/redundantscope.go     Sun Jul 24 20:07:20 2022
@@ -241,7 +241,7 @@ func (s *RedundantScope) handleVarUse(mk
        }
 }
 
-// access returns the info for the given variable, creating it if necessary.
+// get returns the info for the given variable, creating it if necessary.
 func (s *RedundantScope) get(varname string) *redundantScopeVarinfo {
        info := s.vars[varname]
        if info == nil {

Index: pkgsrc/pkgtools/pkglint/files/mkcondchecker.go
diff -u pkgsrc/pkgtools/pkglint/files/mkcondchecker.go:1.13 pkgsrc/pkgtools/pkglint/files/mkcondchecker.go:1.14
--- pkgsrc/pkgtools/pkglint/files/mkcondchecker.go:1.13 Sat Jul  9 06:40:55 2022
+++ pkgsrc/pkgtools/pkglint/files/mkcondchecker.go      Sun Jul 24 20:07:20 2022
@@ -109,7 +109,9 @@ func (ck *MkCondChecker) checkNotEmpty(n
 func (ck *MkCondChecker) checkEmpty(varuse *MkVarUse, fromEmpty bool, neg bool) {
        ck.checkEmptyExpr(varuse)
        ck.checkEmptyType(varuse)
-       ck.simplify(varuse, fromEmpty, neg)
+
+       s := MkCondSimplifier{ck.MkLines, ck.MkLine}
+       s.SimplifyVarUse(varuse, fromEmpty, neg)
 }
 
 func (ck *MkCondChecker) checkEmptyExpr(varuse *MkVarUse) {
@@ -160,145 +162,6 @@ var mkCondStringLiteralUnquoted = textpr
 // TODO: Check whether the ',' really needs to be here.
 var mkCondModifierPatternLiteral = textproc.NewByteSet("-+,./0-9<=>@A-Z_a-z")
 
-// simplify replaces an unnecessarily complex condition with
-// a simpler condition that's still equivalent.
-//
-// * fromEmpty is true for the form empty(VAR...), and false for ${VAR...}.
-//
-// * neg is true for the form !empty(VAR...), and false for empty(VAR...).
-func (ck *MkCondChecker) simplify(varuse *MkVarUse, fromEmpty bool, neg bool) {
-       varname := varuse.varname
-       modifiers := varuse.modifiers
-
-       n := len(modifiers)
-       if n == 0 {
-               return
-       }
-       modsExceptLast := NewMkVarUse("", modifiers[:n-1]...).Mod()
-       vartype := G.Pkgsrc.VariableType(ck.MkLines, varname)
-
-       isDefined := func() bool {
-               if vartype.IsAlwaysInScope() && vartype.IsDefinedIfInScope() {
-                       return true
-               }
-
-               // For run time expressions, such as ${${VAR} == value:?yes:no},
-               // the scope would need to be changed to ck.MkLines.allVars.
-               if ck.MkLines.checkAllData.vars.IsDefined(varname) {
-                       return true
-               }
-
-               return ck.MkLines.Tools.SeenPrefs &&
-                       vartype.Union().Contains(aclpUseLoadtime) &&
-                       vartype.IsDefinedIfInScope()
-       }
-
-       // replace constructs the state before and after the autofix.
-       // The before state is constructed to ensure that only very simple
-       // patterns get replaced automatically.
-       //
-       // Before putting any cases involving special characters into
-       // production, there need to be more tests for the edge cases.
-       replace := func(positive bool, pattern string) (bool, string, string) {
-               defined := isDefined()
-               if !defined && !positive {
-                       // TODO: This is a double negation, maybe even triple.
-                       //  There is an :N pattern, and the variable may be undefined.
-                       //  If it is indeed undefined, should the whole condition
-                       //  evaluate to true or false?
-                       //  The cases to be distinguished are: undefined, empty, filled.
-
-                       // For now, be conservative and don't suggest anything wrong.
-                       return false, "", ""
-               }
-               uMod := condStr(!defined && !varuse.HasModifier("U"), ":U", "")
-
-               op := condStr(neg == positive, "==", "!=")
-
-               from := sprintf("%s%s%s%s%s%s%s",
-                       condStr(neg != fromEmpty, "", "!"),
-                       condStr(fromEmpty, "empty(", "${"),
-                       varname,
-                       modsExceptLast,
-                       condStr(positive, ":M", ":N"),
-                       pattern,
-                       condStr(fromEmpty, ")", "}"))
-
-               needsQuotes := textproc.NewLexer(pattern).NextBytesSet(mkCondStringLiteralUnquoted) != pattern ||
-                       pattern == "" ||
-                       matches(pattern, `^\d+\.?\d*$`)
-               quote := condStr(needsQuotes, "\"", "")
-
-               to := sprintf(
-                       "${%s%s%s} %s %s%s%s",
-                       varname, uMod, modsExceptLast, op, quote, pattern, quote)
-
-               return true, from, to
-       }
-
-       modifier := modifiers[n-1]
-       ok, positive, pattern, exact := modifier.MatchMatch()
-       if !ok || !positive && n != 1 {
-               return
-       }
-
-       // Replace !empty(VAR:M*.c) with ${VAR:M*.c}.
-       // Replace empty(VAR:M*.c) with !${VAR:M*.c}.
-       if fromEmpty && positive && !exact && vartype != nil && isDefined() &&
-               // Restrict replacements to very simple patterns with only few
-               // special characters, for now.
-               // Before generalizing this to arbitrary strings, there has to be
-               // a proper code generator for these conditions that handles all
-               // possible escaping.
-               // The same reasoning applies to the variable name, even though the
-               // variable name typically only uses a restricted character set.
-               matches(varuse.Mod(), `^[*.:\w]+$`) {
-
-               fixedPart := varname + modsExceptLast + ":M" + pattern
-               from := condStr(neg, "!", "") + "empty(" + fixedPart + ")"
-               to := condStr(neg, "", "!") + "${" + fixedPart + "}"
-
-               fix := ck.MkLine.Autofix()
-               fix.Notef("%q can be simplified to %q.", from, to)
-               fix.Explain(
-                       "This variable is guaranteed to be defined at this point.",
-                       "Therefore it may occur on the left-hand side of a comparison",
-                       "and doesn't have to be guarded by the function 'empty'.")
-               fix.Replace(from, to)
-               fix.Apply()
-
-               return
-       }
-
-       switch {
-       case !exact,
-               vartype == nil,
-               vartype.IsList(),
-               textproc.NewLexer(pattern).NextBytesSet(mkCondModifierPatternLiteral) != pattern:
-               return
-       }
-
-       ok, from, to := replace(positive, pattern)
-       if !ok {
-               return
-       }
-
-       fix := ck.MkLine.Autofix()
-       fix.Notef("%s can be compared using the simpler \"%s\" "+
-               "instead of matching against %q.",
-               varname, to, ":"+modifier.String()) // TODO: Quoted
-       fix.Explain(
-               "This variable has a single value, not a list of values.",
-               "Therefore it feels strange to apply list operators like :M and :N onto it.",
-               "A more direct approach is to use the == and != operators.",
-               "",
-               "An entirely different case is when the pattern contains",
-               "wildcards like *, ?, [].",
-               "In such a case, using the :M or :N modifiers is useful and preferred.")
-       fix.Replace(from, to)
-       fix.Apply()
-}
-
 func (ck *MkCondChecker) checkCompare(left *MkCondTerm, op string, right *MkCondTerm) {
        switch {
        case right.Num != "":

Index: pkgsrc/pkgtools/pkglint/files/mkcondchecker_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mkcondchecker_test.go:1.11 pkgsrc/pkgtools/pkglint/files/mkcondchecker_test.go:1.12
--- pkgsrc/pkgtools/pkglint/files/mkcondchecker_test.go:1.11    Wed Jul  6 06:14:24 2022
+++ pkgsrc/pkgtools/pkglint/files/mkcondchecker_test.go Sun Jul 24 20:07:20 2022
@@ -530,723 +530,6 @@ func (s *Suite) Test_MkCondChecker_check
                nil...)
 }
 
-func (s *Suite) Test_MkCondChecker_simplify(c *check.C) {
-       t := s.Init(c)
-
-       t.CreateFileLines("mk/bsd.prefs.mk")
-       t.Chdir("category/package")
-
-       // The Anything type suppresses the warnings from type checking.
-       // BtUnknown would not work here, see Pkgsrc.VariableType.
-       btAnything := &BasicType{"Anything", func(cv *VartypeCheck) {}}
-
-       // For simplifying the expressions, it is necessary to know whether
-       // a variable can be undefined. Undefined variables need the
-       // :U modifier or must be enclosed in double quotes, otherwise
-       // bmake will complain about a "Malformed conditional". That error
-       // message is not entirely precise since the expression
-       // is syntactically valid, it's just the evaluation that fails.
-       //
-       // Some variables such as MACHINE_ARCH are in scope from the very
-       // beginning.
-       //
-       // Some variables such as PKGPATH are in scope after bsd.prefs.mk
-       // has been included.
-       //
-       // Some variables such as PREFIX (as of December 2019) are only in
-       // scope after bsd.pkg.mk has been included. These cannot be used
-       // in .if conditions at all.
-       //
-       // Even when they are in scope, some variables such as PKGREVISION
-       // or MAKE_JOBS may be undefined.
-
-       t.SetUpVarType("IN_SCOPE_DEFINED", btAnything, AlwaysInScope|DefinedIfInScope,
-               "*.mk: use, use-loadtime")
-       t.SetUpVarType("IN_SCOPE", btAnything, AlwaysInScope,
-               "*.mk: use, use-loadtime")
-       t.SetUpVarType("PREFS_DEFINED", btAnything, DefinedIfInScope,
-               "*.mk: use, use-loadtime")
-       t.SetUpVarType("PREFS", btAnything, NoVartypeOptions,
-               "*.mk: use, use-loadtime")
-       t.SetUpVarType("LATER_DEFINED", btAnything, DefinedIfInScope,
-               "*.mk: use")
-       t.SetUpVarType("LATER", btAnything, NoVartypeOptions,
-               "*.mk: use")
-       // UNDEFINED is also used in the following tests, but is obviously
-       // not defined here.
-
-       // prefs: whether to include bsd.prefs.mk before
-       // before: the directive before the condition is simplified
-       // after: the directive after the condition is simplified
-       doTest := func(prefs bool, before, after string, diagnostics ...string) {
-               if !matches(before, `IN_SCOPE|PREFS|LATER|UNDEFINED`) {
-                       c.Errorf("Condition %q must include one of the above variable names.", before)
-               }
-               mklines := t.SetUpFileMkLines("filename.mk",
-                       MkCvsID,
-                       condStr(prefs, ".include \"../../mk/bsd.prefs.mk\"", ""),
-                       before,
-                       ".endif")
-
-               action := func(autofix bool) {
-                       mklines.ForEach(func(mkline *MkLine) {
-                               // Sets mklines.Tools.SeenPrefs, which decides whether the :U modifier
-                               // is necessary; see MkLines.checkLine.
-                               mklines.Tools.ParseToolLine(mklines, mkline, false, false)
-
-                               if mkline.IsDirective() && mkline.Directive() != "endif" {
-                                       // TODO: Replace Check with a more
-                                       //  specific method that does not do the type checks.
-                                       NewMkCondChecker(mkline, mklines).Check()
-                               }
-                       })
-
-                       if autofix {
-                               afterMklines := LoadMk(t.File("filename.mk"), nil, MustSucceed)
-                               t.CheckEquals(afterMklines.mklines[2].Text, after)
-                       }
-               }
-
-               t.ExpectDiagnosticsAutofix(action, diagnostics...)
-       }
-
-       testBeforePrefs := func(before, after string, diagnostics ...string) {
-               doTest(false, before, after, diagnostics...)
-       }
-       testAfterPrefs := func(before, after string, diagnostics ...string) {
-               doTest(true, before, after, diagnostics...)
-       }
-       testBeforeAndAfterPrefs := func(before, after string, diagnostics ...string) {
-               doTest(false, before, after, diagnostics...)
-               doTest(true, before, after, diagnostics...)
-       }
-
-       testBeforeAndAfterPrefs(
-               ".if ${IN_SCOPE_DEFINED:Mpattern}",
-               ".if ${IN_SCOPE_DEFINED} == pattern",
-
-               "NOTE: filename.mk:3: IN_SCOPE_DEFINED can be "+
-                       "compared using the simpler \"${IN_SCOPE_DEFINED} == pattern\" "+
-                       "instead of matching against \":Mpattern\".",
-               "AUTOFIX: filename.mk:3: Replacing \"${IN_SCOPE_DEFINED:Mpattern}\" "+
-                       "with \"${IN_SCOPE_DEFINED} == pattern\".")
-
-       testBeforeAndAfterPrefs(
-               ".if ${IN_SCOPE:Mpattern}",
-               ".if ${IN_SCOPE:U} == pattern",
-
-               "NOTE: filename.mk:3: IN_SCOPE can be "+
-                       "compared using the simpler \"${IN_SCOPE:U} == pattern\" "+
-                       "instead of matching against \":Mpattern\".",
-               "AUTOFIX: filename.mk:3: Replacing \"${IN_SCOPE:Mpattern}\" "+
-                       "with \"${IN_SCOPE:U} == pattern\".")
-
-       // Even though PREFS_DEFINED is declared as DefinedIfInScope,
-       // it is not in scope yet. Therefore it needs the :U modifier.
-       // The warning that this variable is not yet in scope comes from
-       // a different part of pkglint.
-       testBeforePrefs(
-               ".if ${PREFS_DEFINED:Mpattern}",
-               ".if ${PREFS_DEFINED:U} == pattern",
-
-               "NOTE: filename.mk:3: PREFS_DEFINED can be "+
-                       "compared using the simpler \"${PREFS_DEFINED:U} == pattern\" "+
-                       "instead of matching against \":Mpattern\".",
-               "WARN: filename.mk:3: To use PREFS_DEFINED at load time, "+
-                       ".include \"../../mk/bsd.prefs.mk\" first.",
-               "AUTOFIX: filename.mk:3: Replacing \"${PREFS_DEFINED:Mpattern}\" "+
-                       "with \"${PREFS_DEFINED:U} == pattern\".")
-
-       testAfterPrefs(
-               ".if ${PREFS_DEFINED:Mpattern}",
-               ".if ${PREFS_DEFINED} == pattern",
-
-               "NOTE: filename.mk:3: PREFS_DEFINED can be "+
-                       "compared using the simpler \"${PREFS_DEFINED} == pattern\" "+
-                       "instead of matching against \":Mpattern\".",
-               "AUTOFIX: filename.mk:3: Replacing \"${PREFS_DEFINED:Mpattern}\" "+
-                       "with \"${PREFS_DEFINED} == pattern\".")
-
-       testBeforePrefs(
-               ".if ${PREFS:Mpattern}",
-               ".if ${PREFS:U} == pattern",
-
-               "NOTE: filename.mk:3: PREFS can be "+
-                       "compared using the simpler \"${PREFS:U} == pattern\" "+
-                       "instead of matching against \":Mpattern\".",
-               "WARN: filename.mk:3: To use PREFS at load time, "+
-                       ".include \"../../mk/bsd.prefs.mk\" first.",
-               "AUTOFIX: filename.mk:3: Replacing \"${PREFS:Mpattern}\" "+
-                       "with \"${PREFS:U} == pattern\".")
-
-       // Preferences that may be undefined always need the :U modifier,
-       // even when they are in scope.
-       testAfterPrefs(
-               ".if ${PREFS:Mpattern}",
-               ".if ${PREFS:U} == pattern",
-
-               "NOTE: filename.mk:3: PREFS can be "+
-                       "compared using the simpler \"${PREFS:U} == pattern\" "+
-                       "instead of matching against \":Mpattern\".",
-               "AUTOFIX: filename.mk:3: Replacing \"${PREFS:Mpattern}\" "+
-                       "with \"${PREFS:U} == pattern\".")
-
-       // Variables that are defined later always need the :U modifier.
-       // It is probably a mistake to use them in conditions at all.
-       testBeforeAndAfterPrefs(
-               ".if ${LATER_DEFINED:Mpattern}",
-               ".if ${LATER_DEFINED:U} == pattern",
-
-               "NOTE: filename.mk:3: LATER_DEFINED can be "+
-                       "compared using the simpler \"${LATER_DEFINED:U} == pattern\" "+
-                       "instead of matching against \":Mpattern\".",
-               "WARN: filename.mk:3: "+
-                       "LATER_DEFINED should not be used at load time in any file.",
-               "AUTOFIX: filename.mk:3: Replacing \"${LATER_DEFINED:Mpattern}\" "+
-                       "with \"${LATER_DEFINED:U} == pattern\".")
-
-       // Variables that are defined later always need the :U modifier.
-       // It is probably a mistake to use them in conditions at all.
-       testBeforeAndAfterPrefs(
-               ".if ${LATER:Mpattern}",
-               ".if ${LATER:U} == pattern",
-
-               "NOTE: filename.mk:3: LATER can be "+
-                       "compared using the simpler \"${LATER:U} == pattern\" "+
-                       "instead of matching against \":Mpattern\".",
-               "WARN: filename.mk:3: "+
-                       "LATER should not be used at load time in any file.",
-               "AUTOFIX: filename.mk:3: Replacing \"${LATER:Mpattern}\" "+
-                       "with \"${LATER:U} == pattern\".")
-
-       testBeforeAndAfterPrefs(
-               ".if ${UNDEFINED:Mpattern}",
-               ".if ${UNDEFINED:Mpattern}",
-
-               "WARN: filename.mk:3: UNDEFINED is used but not defined.")
-
-       // When the pattern contains placeholders, it cannot be converted to == or !=.
-       testAfterPrefs(
-               ".if ${PREFS_DEFINED:Mpa*n}",
-               ".if ${PREFS_DEFINED:Mpa*n}",
-
-               nil...)
-
-       // When deciding whether to replace the expression, only the
-       // last modifier is inspected. All the others are copied.
-       testAfterPrefs(
-               ".if ${PREFS_DEFINED:tl:Mpattern}",
-               ".if ${PREFS_DEFINED:tl} == pattern",
-
-               "NOTE: filename.mk:3: PREFS_DEFINED can be "+
-                       "compared using the simpler \"${PREFS_DEFINED:tl} == pattern\" "+
-                       "instead of matching against \":Mpattern\".",
-               "AUTOFIX: filename.mk:3: Replacing \"${PREFS_DEFINED:tl:Mpattern}\" "+
-                       "with \"${PREFS_DEFINED:tl} == pattern\".")
-
-       // Negated pattern matches are supported as well,
-       // as long as the variable is guaranteed to be nonempty.
-       // TODO: Actually implement this.
-       //  As of December 2019, IsNonemptyIfDefined is not used anywhere.
-       testAfterPrefs(
-               ".if ${PREFS_DEFINED:Npattern}",
-               ".if ${PREFS_DEFINED} != pattern",
-
-               "NOTE: filename.mk:3: PREFS_DEFINED can be "+
-                       "compared using the simpler \"${PREFS_DEFINED} != pattern\" "+
-                       "instead of matching against \":Npattern\".",
-               "AUTOFIX: filename.mk:3: Replacing \"${PREFS_DEFINED:Npattern}\" "+
-                       "with \"${PREFS_DEFINED} != pattern\".")
-
-       // ${PREFS_DEFINED:None:Ntwo} is a short variant of
-       // ${PREFS_DEFINED} != "one" && ${PREFS_DEFINED} != "two".
-       // Applying the transformation would make the condition longer
-       // than before, therefore nothing can be simplified here,
-       // even though all patterns are exact matches.
-       testAfterPrefs(
-               ".if ${PREFS_DEFINED:None:Ntwo}",
-               ".if ${PREFS_DEFINED:None:Ntwo}",
-
-               nil...)
-
-       // Note: this combination doesn't make sense since the patterns
-       // "one" and "two" don't overlap.
-       // Nevertheless it is possible and valid to simplify the condition.
-       testAfterPrefs(
-               ".if ${PREFS_DEFINED:Mone:Mtwo}",
-               ".if ${PREFS_DEFINED:Mone} == two",
-
-               "NOTE: filename.mk:3: PREFS_DEFINED can be "+
-                       "compared using the simpler \"${PREFS_DEFINED:Mone} == two\" "+
-                       "instead of matching against \":Mtwo\".",
-               "AUTOFIX: filename.mk:3: Replacing \"${PREFS_DEFINED:Mone:Mtwo}\" "+
-                       "with \"${PREFS_DEFINED:Mone} == two\".")
-
-       // There is no ! before the empty, which is easy to miss.
-       // Because of this missing negation, the comparison operator is !=.
-       testAfterPrefs(
-               ".if empty(PREFS_DEFINED:Mpattern)",
-               ".if ${PREFS_DEFINED} != pattern",
-
-               "NOTE: filename.mk:3: PREFS_DEFINED can be "+
-                       "compared using the simpler \"${PREFS_DEFINED} != pattern\" "+
-                       "instead of matching against \":Mpattern\".",
-               "AUTOFIX: filename.mk:3: Replacing \"empty(PREFS_DEFINED:Mpattern)\" "+
-                       "with \"${PREFS_DEFINED} != pattern\".")
-
-       testAfterPrefs(
-               ".if !!empty(PREFS_DEFINED:Mpattern)",
-               // TODO: The ! and == could be combined into a !=.
-               //  Luckily the !! pattern doesn't occur in practice.
-               ".if !${PREFS_DEFINED} == pattern",
-
-               // TODO: When taking all the ! into account, this is actually a
-               //  test for emptiness, therefore the diagnostics should suggest
-               //  the != operator instead of ==.
-               "NOTE: filename.mk:3: PREFS_DEFINED can be "+
-                       "compared using the simpler \"${PREFS_DEFINED} == pattern\" "+
-                       "instead of matching against \":Mpattern\".",
-               "AUTOFIX: filename.mk:3: Replacing \"!empty(PREFS_DEFINED:Mpattern)\" "+
-                       "with \"${PREFS_DEFINED} == pattern\".")
-
-       // Simplifying the condition also works in complex expressions.
-       testAfterPrefs(".if empty(PREFS_DEFINED:Mpattern) || 0",
-               ".if ${PREFS_DEFINED} != pattern || 0",
-
-               "NOTE: filename.mk:3: PREFS_DEFINED can be "+
-                       "compared using the simpler \"${PREFS_DEFINED} != pattern\" "+
-                       "instead of matching against \":Mpattern\".",
-               "AUTOFIX: filename.mk:3: Replacing \"empty(PREFS_DEFINED:Mpattern)\" "+
-                       "with \"${PREFS_DEFINED} != pattern\".")
-
-       // No note in this case since there is no implicit !empty around the varUse.
-       // There is no obvious way of writing this expression in a simpler form.
-       testAfterPrefs(
-               ".if ${PREFS_DEFINED:Mpattern} != ${OTHER}",
-               ".if ${PREFS_DEFINED:Mpattern} != ${OTHER}",
-
-               "WARN: filename.mk:3: OTHER is used but not defined.")
-
-       // The condition is also simplified if it doesn't use the !empty
-       // form but the implicit conversion to boolean.
-       testAfterPrefs(
-               ".if ${PREFS_DEFINED:Mpattern}",
-               ".if ${PREFS_DEFINED} == pattern",
-
-               "NOTE: filename.mk:3: PREFS_DEFINED can be "+
-                       "compared using the simpler \"${PREFS_DEFINED} == pattern\" "+
-                       "instead of matching against \":Mpattern\".",
-               "AUTOFIX: filename.mk:3: Replacing \"${PREFS_DEFINED:Mpattern}\" "+
-                       "with \"${PREFS_DEFINED} == pattern\".")
-
-       // A single negation outside the implicit conversion is taken into
-       // account when simplifying the condition.
-       testAfterPrefs(
-               ".if !${PREFS_DEFINED:Mpattern}",
-               ".if ${PREFS_DEFINED} != pattern",
-
-               "NOTE: filename.mk:3: PREFS_DEFINED can be "+
-                       "compared using the simpler \"${PREFS_DEFINED} != pattern\" "+
-                       "instead of matching against \":Mpattern\".",
-               "AUTOFIX: filename.mk:3: Replacing \"!${PREFS_DEFINED:Mpattern}\" "+
-                       "with \"${PREFS_DEFINED} != pattern\".")
-
-       // TODO: Merge the double negation into the comparison operator.
-       testAfterPrefs(
-               ".if !!${PREFS_DEFINED:Mpattern}",
-               ".if !${PREFS_DEFINED} != pattern",
-
-               "NOTE: filename.mk:3: PREFS_DEFINED can be "+
-                       "compared using the simpler \"${PREFS_DEFINED} != pattern\" "+
-                       "instead of matching against \":Mpattern\".",
-               "AUTOFIX: filename.mk:3: Replacing \"!${PREFS_DEFINED:Mpattern}\" "+
-                       "with \"${PREFS_DEFINED} != pattern\".")
-
-       // This pattern with spaces doesn't make sense at all in the :M
-       // modifier since it can never match.
-       // Or can it, if the PKGPATH contains quotes?
-       // TODO: How exactly does bmake apply the matching here,
-       //  are both values unquoted first? Probably not, but who knows.
-       testBeforeAndAfterPrefs(
-               ".if ${IN_SCOPE_DEFINED:Mpattern with spaces}",
-               ".if ${IN_SCOPE_DEFINED:Mpattern with spaces}",
-
-               nil...)
-       // TODO: ".if ${PKGPATH} == \"pattern with spaces\"")
-
-       testBeforeAndAfterPrefs(
-               ".if ${IN_SCOPE_DEFINED:M'pattern with spaces'}",
-               ".if ${IN_SCOPE_DEFINED:M'pattern with spaces'}",
-
-               nil...)
-       // TODO: ".if ${PKGPATH} == 'pattern with spaces'")
-
-       testBeforeAndAfterPrefs(
-               ".if ${IN_SCOPE_DEFINED:M&&}",
-               ".if ${IN_SCOPE_DEFINED:M&&}",
-
-               nil...)
-       // TODO: ".if ${PKGPATH} == '&&'")
-
-       // The :N modifier involves another negation and is therefore more
-       // difficult to understand. That's even more reason to use the
-       // well-known == and != comparison operators instead.
-       //
-       // If PKGPATH is "", the condition is false.
-       // If PKGPATH is "negative-pattern", the condition is false.
-       // In all other cases, the condition is true.
-       //
-       // Therefore this condition cannot simply be transformed into
-       // ${PKGPATH} != negative-pattern, since that would produce a
-       // different result in the case where PKGPATH is empty.
-       //
-       // For system-provided variables that are guaranteed to be non-empty,
-       // such as OPSYS or PKGPATH, this replacement is valid.
-       // These variables are only guaranteed to be defined after bsd.prefs.mk
-       // has been included, like everywhere else.
-       //
-       // TODO: This is where NonemptyIfDefined comes into play.
-       testAfterPrefs(
-               ".if ${PREFS_DEFINED:Nnegative-pattern}",
-               ".if ${PREFS_DEFINED} != negative-pattern",
-
-               "NOTE: filename.mk:3: PREFS_DEFINED can be "+
-                       "compared using the simpler \"${PREFS_DEFINED} != negative-pattern\" "+
-                       "instead of matching against \":Nnegative-pattern\".",
-               "AUTOFIX: filename.mk:3: Replacing \"${PREFS_DEFINED:Nnegative-pattern}\" "+
-                       "with \"${PREFS_DEFINED} != negative-pattern\".")
-
-       // Since UNDEFINED is not a well-known variable that is
-       // guaranteed to be non-empty (see the previous example), it is not
-       // transformed at all.
-       testBeforePrefs(
-               ".if ${UNDEFINED:Nnegative-pattern}",
-               ".if ${UNDEFINED:Nnegative-pattern}",
-
-               "WARN: filename.mk:3: UNDEFINED is used but not defined.")
-
-       testAfterPrefs(
-               ".if ${UNDEFINED:Nnegative-pattern}",
-               ".if ${UNDEFINED:Nnegative-pattern}",
-
-               "WARN: filename.mk:3: UNDEFINED is used but not defined.")
-
-       // A complex condition may contain several simple conditions
-       // that are each simplified independently, in the same go.
-       testAfterPrefs(
-               ".if ${PREFS_DEFINED:Mpath1} || ${PREFS_DEFINED:Mpath2}",
-               ".if ${PREFS_DEFINED} == path1 || ${PREFS_DEFINED} == path2",
-
-               "NOTE: filename.mk:3: PREFS_DEFINED can be "+
-                       "compared using the simpler \"${PREFS_DEFINED} == path1\" "+
-                       "instead of matching against \":Mpath1\".",
-               "NOTE: filename.mk:3: PREFS_DEFINED can be "+
-                       "compared using the simpler \"${PREFS_DEFINED} == path2\" "+
-                       "instead of matching against \":Mpath2\".",
-               "AUTOFIX: filename.mk:3: Replacing \"${PREFS_DEFINED:Mpath1}\" "+
-                       "with \"${PREFS_DEFINED} == path1\".",
-               "AUTOFIX: filename.mk:3: Replacing \"${PREFS_DEFINED:Mpath2}\" "+
-                       "with \"${PREFS_DEFINED} == path2\".")
-
-       // Removing redundant parentheses may be useful one day but is
-       // not urgent.
-       // Simplifying the inner expression keeps all parentheses as-is.
-       testAfterPrefs(
-               ".if (((((${PREFS_DEFINED:Mpath})))))",
-               ".if (((((${PREFS_DEFINED} == path)))))",
-
-               "NOTE: filename.mk:3: PREFS_DEFINED can be "+
-                       "compared using the simpler \"${PREFS_DEFINED} == path\" "+
-                       "instead of matching against \":Mpath\".",
-               "AUTOFIX: filename.mk:3: Replacing \"${PREFS_DEFINED:Mpath}\" "+
-                       "with \"${PREFS_DEFINED} == path\".")
-
-       // Several modifiers like :S and :C may change the variable value.
-       // Whether the condition can be simplified or not only depends on the
-       // last modifier in the chain.
-       testAfterPrefs(
-               ".if !empty(PREFS_DEFINED:S,NetBSD,ok,:Mok)",
-               ".if ${PREFS_DEFINED:S,NetBSD,ok,} == ok",
-
-               "NOTE: filename.mk:3: PREFS_DEFINED can be "+
-                       "compared using the simpler \"${PREFS_DEFINED:S,NetBSD,ok,} == ok\" "+
-                       "instead of matching against \":Mok\".",
-               "AUTOFIX: filename.mk:3: Replacing \"!empty(PREFS_DEFINED:S,NetBSD,ok,:Mok)\" "+
-                       "with \"${PREFS_DEFINED:S,NetBSD,ok,} == ok\".")
-
-       testAfterPrefs(
-               ".if empty(PREFS_DEFINED:tl:Msunos)",
-               ".if ${PREFS_DEFINED:tl} != sunos",
-
-               "NOTE: filename.mk:3: PREFS_DEFINED can be "+
-                       "compared using the simpler \"${PREFS_DEFINED:tl} != sunos\" "+
-                       "instead of matching against \":Msunos\".",
-               "AUTOFIX: filename.mk:3: Replacing \"empty(PREFS_DEFINED:tl:Msunos)\" "+
-                       "with \"${PREFS_DEFINED:tl} != sunos\".")
-
-       // The condition can only be simplified if the :M or :N modifier
-       // appears at the end of the chain.
-       testAfterPrefs(
-               ".if !empty(PREFS_DEFINED:O:MUnknown:S,a,b,)",
-               ".if !empty(PREFS_DEFINED:O:MUnknown:S,a,b,)",
-
-               nil...)
-
-       // The dot is just an ordinary character in a pattern.
-       // In comparisons, an unquoted 1.2 is interpreted as a number though.
-       testAfterPrefs(
-               ".if !empty(PREFS_DEFINED:Mpackage1.2)",
-               ".if ${PREFS_DEFINED} == package1.2",
-
-               "NOTE: filename.mk:3: PREFS_DEFINED can be "+
-                       "compared using the simpler \"${PREFS_DEFINED} == package1.2\" "+
-                       "instead of matching against \":Mpackage1.2\".",
-               "AUTOFIX: filename.mk:3: Replacing \"!empty(PREFS_DEFINED:Mpackage1.2)\" "+
-                       "with \"${PREFS_DEFINED} == package1.2\".")
-
-       // Numbers must be enclosed in quotes, otherwise they are compared
-       // as numbers, not as strings.
-       // The :M and :N modifiers always compare strings.
-       testAfterPrefs(
-               ".if empty(PREFS:U:M64)",
-               ".if ${PREFS:U} != \"64\"",
-
-               "NOTE: filename.mk:3: PREFS can be "+
-                       "compared using the simpler \"${PREFS:U} != \"64\"\" "+
-                       "instead of matching against \":M64\".",
-               "AUTOFIX: filename.mk:3: Replacing \"empty(PREFS:U:M64)\" "+
-                       "with \"${PREFS:U} != \\\"64\\\"\".")
-
-       // Fractional numbers must also be enclosed in quotes.
-       testAfterPrefs(
-               ".if empty(PREFS:U:M19.12)",
-               ".if ${PREFS:U} != \"19.12\"",
-
-               "NOTE: filename.mk:3: PREFS can be "+
-                       "compared using the simpler \"${PREFS:U} != \"19.12\"\" "+
-                       "instead of matching against \":M19.12\".",
-               "AUTOFIX: filename.mk:3: Replacing \"empty(PREFS:U:M19.12)\" "+
-                       "with \"${PREFS:U} != \\\"19.12\\\"\".")
-
-       testAfterPrefs(
-               ".if !empty(LATER:Npattern)",
-               ".if !empty(LATER:Npattern)",
-
-               // No diagnostics about the :N modifier yet,
-               // see MkCondChecker.simplify.replace.
-               "WARN: filename.mk:3: LATER should not be used "+
-                       "at load time in any file.")
-
-       // TODO: Add a note that the :U is unnecessary, and explain why.
-       testAfterPrefs(
-               ".if ${PREFS_DEFINED:U:Mpattern}",
-               ".if ${PREFS_DEFINED:U} == pattern",
-
-               "NOTE: filename.mk:3: PREFS_DEFINED can be "+
-                       "compared using the simpler \"${PREFS_DEFINED:U} == pattern\" "+
-                       "instead of matching against \":Mpattern\".",
-               "AUTOFIX: filename.mk:3: "+
-                       "Replacing \"${PREFS_DEFINED:U:Mpattern}\" "+
-                       "with \"${PREFS_DEFINED:U} == pattern\".")
-
-       // Conditions without any modifiers cannot be simplified
-       // and are therefore skipped.
-       testBeforeAndAfterPrefs(
-               ".if ${IN_SCOPE_DEFINED}",
-               ".if ${IN_SCOPE_DEFINED}",
-
-               nil...)
-
-       // Special characters must be enclosed in quotes when they are
-       // used in string literals.
-       // As of December 2019, strings with special characters are not yet
-       // replaced automatically, see mkCondLiteralChars.
-       // TODO: Add tests for all characters that are special in string literals or patterns.
-       // TODO: Then, extend the set of characters for which the expressions are simplified.
-       testBeforePrefs(
-               ".if ${PREFS_DEFINED:M&&}",
-               ".if ${PREFS_DEFINED:M&&}",
-
-               "WARN: filename.mk:3: To use PREFS_DEFINED at load time, .include \"../../mk/bsd.prefs.mk\" first.")
-       testAfterPrefs(
-               ".if ${PREFS_DEFINED:M&&}",
-               ".if ${PREFS_DEFINED:M&&}",
-
-               nil...)
-
-       testBeforePrefs(
-               ".if ${PREFS:M&&}",
-               ".if ${PREFS:M&&}",
-
-               // TODO: Warn that the :U is missing.
-               "WARN: filename.mk:3: To use PREFS at load time, .include \"../../mk/bsd.prefs.mk\" first.")
-       testAfterPrefs(
-               ".if ${PREFS:M&&}",
-               ".if ${PREFS:M&&}",
-
-               // TODO: Warn that the :U is missing.
-               nil...)
-
-       // The + is contained in both mkCondStringLiteralUnquoted and
-       // mkCondModifierPatternLiteral, therefore it is copied verbatim.
-       testAfterPrefs(
-               ".if ${PREFS_DEFINED:Mcategory/gtk+}",
-               ".if ${PREFS_DEFINED} == category/gtk+",
-
-               "NOTE: filename.mk:3: PREFS_DEFINED can be "+
-                       "compared using the simpler \"${PREFS_DEFINED} == category/gtk+\" "+
-                       "instead of matching against \":Mcategory/gtk+\".",
-               "AUTOFIX: filename.mk:3: "+
-                       "Replacing \"${PREFS_DEFINED:Mcategory/gtk+}\" "+
-                       "with \"${PREFS_DEFINED} == category/gtk+\".")
-
-       // The characters <=> may be used unescaped in :M and :N patterns
-       // but not in .if conditions. There they must be enclosed in quotes.
-       testBeforePrefs(
-               ".if ${PREFS_DEFINED:M<=>}",
-               ".if ${PREFS_DEFINED:U} == \"<=>\"",
-
-               "NOTE: filename.mk:3: PREFS_DEFINED can be "+
-                       "compared using the simpler \"${PREFS_DEFINED:U} == \"<=>\"\" "+
-                       "instead of matching against \":M<=>\".",
-               "WARN: filename.mk:3: To use PREFS_DEFINED at load time, "+
-                       ".include \"../../mk/bsd.prefs.mk\" first.",
-               "AUTOFIX: filename.mk:3: "+
-                       "Replacing \"${PREFS_DEFINED:M<=>}\" "+
-                       "with \"${PREFS_DEFINED:U} == \\\"<=>\\\"\".")
-       testAfterPrefs(
-               ".if ${PREFS_DEFINED:M<=>}",
-               ".if ${PREFS_DEFINED} == \"<=>\"",
-
-               "NOTE: filename.mk:3: PREFS_DEFINED can be "+
-                       "compared using the simpler \"${PREFS_DEFINED} == \"<=>\"\" "+
-                       "instead of matching against \":M<=>\".",
-               "AUTOFIX: filename.mk:3: "+
-                       "Replacing \"${PREFS_DEFINED:M<=>}\" "+
-                       "with \"${PREFS_DEFINED} == \\\"<=>\\\"\".")
-
-       // If pkglint replaces this particular pattern, the resulting string
-       // literal must be escaped properly.
-       testBeforeAndAfterPrefs(
-               ".if ${IN_SCOPE_DEFINED:M\"}",
-               ".if ${IN_SCOPE_DEFINED:M\"}",
-
-               nil...)
-
-       testBeforeAndAfterPrefs(
-               ".if !empty(IN_SCOPE_DEFINED:M)",
-               ".if ${IN_SCOPE_DEFINED} == \"\"",
-
-               "NOTE: filename.mk:3: IN_SCOPE_DEFINED can be "+
-                       "compared using the simpler "+"\"${IN_SCOPE_DEFINED} == \"\"\" "+
-                       "instead of matching against \":M\".",
-               "AUTOFIX: filename.mk:3: "+
-                       "Replacing \"!empty(IN_SCOPE_DEFINED:M)\" "+
-                       "with \"${IN_SCOPE_DEFINED} == \\\"\\\"\".",
-       )
-
-       testBeforeAndAfterPrefs(
-               ".if !empty(IN_SCOPE_DEFINED:M*.c)",
-               ".if ${IN_SCOPE_DEFINED:M*.c}",
-
-               "NOTE: filename.mk:3: \"!empty(IN_SCOPE_DEFINED:M*.c)\" "+
-                       "can be simplified to \"${IN_SCOPE_DEFINED:M*.c}\".",
-               "AUTOFIX: filename.mk:3: "+
-                       "Replacing \"!empty(IN_SCOPE_DEFINED:M*.c)\" "+
-                       "with \"${IN_SCOPE_DEFINED:M*.c}\".")
-
-       testBeforeAndAfterPrefs(
-               ".if empty(IN_SCOPE_DEFINED:M*.c)",
-               ".if !${IN_SCOPE_DEFINED:M*.c}",
-
-               "NOTE: filename.mk:3: \"empty(IN_SCOPE_DEFINED:M*.c)\" "+
-                       "can be simplified to \"!${IN_SCOPE_DEFINED:M*.c}\".",
-               "AUTOFIX: filename.mk:3: "+
-                       "Replacing \"empty(IN_SCOPE_DEFINED:M*.c)\" "+
-                       "with \"!${IN_SCOPE_DEFINED:M*.c}\".")
-}
-
-func (s *Suite) Test_MkCondChecker_simplify__defined_in_same_file(c *check.C) {
-       t := s.Init(c)
-
-       t.SetUpPackage("category/package")
-       t.Chdir("category/package")
-       t.FinishSetUp()
-
-       doTest := func(before string) {
-               mklines := t.SetUpFileMkLines("filename.mk",
-                       MkCvsID,
-                       "OK=\t\tok",
-                       "OK_DIR=\t\tok", // See Pkgsrc.guessVariableType.
-                       before,
-                       "LATER=\t\tlater",
-                       "LATER_DIR=\tlater", // See Pkgsrc.guessVariableType.
-                       ".endif",
-                       "USED=\t\t${OK} ${LATER} ${OK_DIR} ${LATER_DIR} ${USED}")
-
-               // The high-level call MkLines.Check is used here to
-               // get MkLines.Tools.SeenPrefs correct, which decides
-               // whether the :U modifier is necessary.
-               mklines.Check()
-       }
-
-       // before: the directive before the condition is simplified
-       // after: the directive after the condition is simplified
-       test := func(before, after string, diagnostics ...string) {
-
-               t.ExpectDiagnosticsAutofix(
-                       func(bool) { doTest(before) },
-                       diagnostics...)
-
-               // TODO: Move this assertion above the assertion about the diagnostics.
-               afterMklines := LoadMk(t.File("filename.mk"), nil, MustSucceed)
-               t.CheckEquals(afterMklines.mklines[3].Text, after)
-       }
-
-       // For variables with completely unknown names, the type is nil
-       // and the complete check is skipped.
-       test(
-               ".if ${OK:Mpattern}",
-               ".if ${OK:Mpattern}",
-
-               nil...)
-
-       // For variables with completely unknown names, the type is nil
-       // and the complete check is skipped.
-       test(
-               ".if ${LATER:Mpattern}",
-               ".if ${LATER:Mpattern}",
-
-               nil...)
-
-       // OK_DIR is defined earlier than the .if condition,
-       // which is the correct order.
-       test(
-               ".if ${OK_DIR:Mpattern}",
-               ".if ${OK_DIR} == pattern",
-
-               "NOTE: filename.mk:4: OK_DIR can be "+
-                       "compared using the simpler \"${OK_DIR} == pattern\" "+
-                       "instead of matching against \":Mpattern\".",
-               "AUTOFIX: filename.mk:4: "+
-                       "Replacing \"${OK_DIR:Mpattern}\" "+
-                       "with \"${OK_DIR} == pattern\".")
-
-       // LATER_DIR is defined later than the .if condition,
-       // therefore at the time of the .if statement, it is still empty.
-       test(
-               ".if ${LATER_DIR:Mpattern}",
-               ".if ${LATER_DIR:U} == pattern",
-
-               // TODO: Warn that LATER_DIR is used before it is defined.
-               "NOTE: filename.mk:4: LATER_DIR can be "+
-                       "compared using the simpler \"${LATER_DIR:U} == pattern\" "+
-                       "instead of matching against \":Mpattern\".",
-               "AUTOFIX: filename.mk:4: "+
-                       "Replacing \"${LATER_DIR:Mpattern}\" "+
-                       "with \"${LATER_DIR:U} == pattern\".")
-}
-
 func (s *Suite) Test_MkCondChecker_checkCompare(c *check.C) {
        t := s.Init(c)
 

Index: pkgsrc/pkgtools/pkglint/files/mkparser.go
diff -u pkgsrc/pkgtools/pkglint/files/mkparser.go:1.44 pkgsrc/pkgtools/pkglint/files/mkparser.go:1.45
--- pkgsrc/pkgtools/pkglint/files/mkparser.go:1.44      Sat Jul  9 06:40:55 2022
+++ pkgsrc/pkgtools/pkglint/files/mkparser.go   Sun Jul 24 20:07:20 2022
@@ -5,7 +5,7 @@ import (
        "strings"
 )
 
-// MkParser wraps a Parser and provides methods for parsing
+// MkParser wraps a MkLexer and provides methods for parsing
 // things related to Makefiles.
 type MkParser struct {
        diag         Diagnoser

Index: pkgsrc/pkgtools/pkglint/files/mktypes.go
diff -u pkgsrc/pkgtools/pkglint/files/mktypes.go:1.26 pkgsrc/pkgtools/pkglint/files/mktypes.go:1.27
--- pkgsrc/pkgtools/pkglint/files/mktypes.go:1.26       Wed Jul  1 13:17:41 2020
+++ pkgsrc/pkgtools/pkglint/files/mktypes.go    Sun Jul 24 20:07:20 2022
@@ -130,10 +130,12 @@ func (MkVarUseModifier) EvalSubst(s stri
 
 // MatchMatch tries to match the modifier to a :M or a :N pattern matching.
 // Examples:
-//  :Mpattern   => true,  true,  "pattern", true
-//  :M*         => true,  true,  "*",       false
-//  :M${VAR}    => true,  true,  "${VAR}",  false
-//  :Npattern   => true,  false, "pattern", true
+//  modifier    => ok     positive pattern    exact
+//  ------------------------------------------------
+//  :Mpattern   => true,  true,    "pattern", true
+//  :M*         => true,  true,    "*",       false
+//  :M${VAR}    => true,  true,    "${VAR}",  false
+//  :Npattern   => true,  false,   "pattern", true
 //  :X          => false
 func (m MkVarUseModifier) MatchMatch() (ok bool, positive bool, pattern string, exact bool) {
        if m.HasPrefix("M") || m.HasPrefix("N") {

Index: pkgsrc/pkgtools/pkglint/files/package_test.go
diff -u pkgsrc/pkgtools/pkglint/files/package_test.go:1.90 pkgsrc/pkgtools/pkglint/files/package_test.go:1.91
--- pkgsrc/pkgtools/pkglint/files/package_test.go:1.90  Fri Mar 11 00:33:12 2022
+++ pkgsrc/pkgtools/pkglint/files/package_test.go       Sun Jul 24 20:07:20 2022
@@ -1341,6 +1341,19 @@ func (s *Suite) Test_Package_check__patc
                "1 warning found.")
 }
 
+func (s *Suite) Test_Package_check__redundant_WRKSRC(c *check.C) {
+       t := s.Init(c)
+
+       t.SetUpPackage("category/package",
+               "WRKSRC=\t${WRKDIR}/package-1.0")
+
+       t.Main("-q", "category/package")
+
+       t.CheckOutputLines(
+               "NOTE: ~/category/package/Makefile:20: " +
+                       "Setting WRKSRC to \"${WRKDIR}/package-1.0\" is redundant.")
+}
+
 func (s *Suite) Test_Package_checkDescr__DESCR_SRC(c *check.C) {
        t := s.Init(c)
 

Index: pkgsrc/pkgtools/pkglint/files/vardefs.go
diff -u pkgsrc/pkgtools/pkglint/files/vardefs.go:1.104 pkgsrc/pkgtools/pkglint/files/vardefs.go:1.105
--- pkgsrc/pkgtools/pkglint/files/vardefs.go:1.104      Sat Jan  1 12:44:25 2022
+++ pkgsrc/pkgtools/pkglint/files/vardefs.go    Sun Jul 24 20:07:20 2022
@@ -283,7 +283,7 @@ func (reg *VarTypeRegistry) compilerLang
        if mklines != nil {
                for _, mkline := range mklines.mklines {
 
-                       if mkline.IsVarassign() && mkline.Varname() == "_CXX_STD_VERSIONS" {
+                       if mkline.IsVarassign() && hasSuffix(mkline.Varname(), "_STD_VERSIONS") {
                                words := mkline.ValueFields(mkline.Value())
                                for _, word := range words {
                                        languages[intern(word)] = true
@@ -1088,6 +1088,7 @@ func (reg *VarTypeRegistry) Init(src *Pk
        reg.pkg("CHECK_SHLIBS", BtYesNo)
        reg.pkglist("CHECK_SHLIBS_SKIP", BtPathPattern)
        reg.pkg("CHECK_SHLIBS_SUPPORTED", BtYesNo)
+       reg.usrlist("CHECK_WRKREF", enum("tools wrappers home wrksrc work wrkobjdir pkgsrc buildlink extra"))
        reg.pkglist("CHECK_WRKREF_SKIP", BtPathPattern)
        reg.pkg("CMAKE_ARG_PATH", BtPathname)
        reg.pkglist("CMAKE_ARGS", BtShellWord)

Index: pkgsrc/pkgtools/pkglint/files/vartypecheck.go
diff -u pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.100 pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.101
--- pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.100 Sat Jan  1 12:44:25 2022
+++ pkgsrc/pkgtools/pkglint/files/vartypecheck.go       Sun Jul 24 20:07:20 2022
@@ -1606,6 +1606,22 @@ func (cv *VartypeCheck) WrapperTransform
 
 func (cv *VartypeCheck) WrkdirSubdirectory() {
        cv.Pathname()
+
+       if hasPrefix(cv.Value, "${WRKDIR}/") && cv.MkLines.pkg != nil {
+               distname := cv.MkLines.pkg.redundant.get("DISTNAME").vari
+               if distname.IsConstant() && cv.Value[10:] == distname.ConstantValue() {
+                       fix := cv.Autofix()
+                       fix.Notef("Setting WRKSRC to %q is redundant.", cv.Value)
+                       fix.Delete()
+                       fix.Apply()
+
+                       // XXX: Resetting the Line and MkLine should be handled universally
+                       //  by Autofix. Without this hack, pkglint would crash later when
+                       //  aligning the variable assignments.
+                       cv.MkLine.Line.Text = ""
+                       cv.MkLine.data = mkLineEmpty{}
+               }
+       }
 }
 
 // WrksrcPathPattern is a shell pattern for existing pathnames, possibly including slashes.

Index: pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go
diff -u pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go:1.92 pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go:1.93
--- pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go:1.92     Fri Jun 24 07:16:23 2022
+++ pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go  Sun Jul 24 20:07:20 2022
@@ -2409,8 +2409,13 @@ func (s *Suite) Test_VartypeCheck_Wrappe
 }
 
 func (s *Suite) Test_VartypeCheck_WrkdirSubdirectory(c *check.C) {
-       vt := NewVartypeCheckTester(s.Init(c), BtWrkdirSubdirectory)
+       t := s.Init(c)
+       pkg := NewPackage(t.SetUpPackage("category/package"))
+       t.FinishSetUp()
+       vt := NewVartypeCheckTester(t, BtWrkdirSubdirectory)
+       pkg.Check() // To initialize pkg.redundant.
 
+       vt.Package(pkg)
        vt.Varname("WRKSRC")
        vt.Op(opAssign)
        vt.Values(
@@ -2430,6 +2435,18 @@ func (s *Suite) Test_VartypeCheck_Wrkdir
        vt.Output(
                "WARN: filename.mk:8: The pathname \"two words\" " +
                        "contains the invalid character \" \".")
+
+       vt.Values(
+               // TODO: Note the redundant definition.
+               "${WRKDIR}/package-1.0",
+               "${WRKDIR}/pkg-1.0",       // different package base
+               "${WRKDIR}/package-1.000", // different version string
+               "${WRKDIR}/package-1.1",   // different version
+       )
+
+       vt.Output(
+               "NOTE: filename.mk:21: " +
+                       "Setting WRKSRC to \"${WRKDIR}/package-1.0\" is redundant.")
 }
 
 func (s *Suite) Test_VartypeCheck_WrksrcPathPattern(c *check.C) {
@@ -2621,8 +2638,16 @@ func NewVartypeCheckTester(t *Tester, ba
        return &VartypeCheckTester{t, basicType, "filename.mk", 1, "", opAssign, nil}
 }
 
+// Package sets the package that gives context to the MkLines that are
+// temporarily created in all following calls to Values.
+//
+// Depending on the test case at hand, it may be enough to have a bare
+// package created by NewPackage, in other cases the package data needs to be
+// loaded using Package.load.
 func (vt *VartypeCheckTester) Package(pkg *Package) { vt.pkg = pkg }
 
+// Varname sets the variable name that will be used in all following calls to
+// Values.
 func (vt *VartypeCheckTester) Varname(varname string) {
        vartype := G.Pkgsrc.VariableType(nil, varname)
        assertNotNil(vartype)
@@ -2632,6 +2657,9 @@ func (vt *VartypeCheckTester) Varname(va
        vt.nextSection()
 }
 
+// File sets the filename that will be used in all following calls to Values.
+// This is useful when testing the permissions of the variable, see
+// VarTypeRegistry.
 func (vt *VartypeCheckTester) File(filename CurrPath) {
        vt.filename = filename
        vt.lineno = 1

Added files:

Index: pkgsrc/pkgtools/pkglint/files/mkcondsimplifier.go
diff -u /dev/null pkgsrc/pkgtools/pkglint/files/mkcondsimplifier.go:1.1
--- /dev/null   Sun Jul 24 20:07:20 2022
+++ pkgsrc/pkgtools/pkglint/files/mkcondsimplifier.go   Sun Jul 24 20:07:20 2022
@@ -0,0 +1,194 @@
+package pkglint
+
+import "netbsd.org/pkglint/textproc"
+
+// MkCondSimplifier replaces unnecessarily complex conditions with simpler yet
+// equivalent conditions.
+type MkCondSimplifier struct {
+       MkLines *MkLines
+       MkLine  *MkLine
+}
+
+// SimplifyVarUse replaces an unnecessarily complex condition with
+// a simpler condition that's still equivalent.
+//
+// * fromEmpty is true for the form empty(VAR...), and false for ${VAR...}.
+//
+// * neg is true for the form !empty(VAR...), and false for empty(VAR...).
+func (s *MkCondSimplifier) SimplifyVarUse(varuse *MkVarUse, fromEmpty bool, neg bool) {
+       s.simplifyMatch(varuse, fromEmpty, neg)
+       s.simplifyWord(varuse, fromEmpty, neg)
+}
+
+// simplifyWord simplifies a condition like '${VAR:Mword}' in the common case
+// where VAR is a single-word variable. This case can be written without any
+// list operators, as '${VAR} == word'.
+func (s *MkCondSimplifier) simplifyWord(varuse *MkVarUse, fromEmpty bool, neg bool) {
+       varname := varuse.varname
+       modifiers := varuse.modifiers
+
+       n := len(modifiers)
+       if n == 0 {
+               return
+       }
+       modsExceptLast := NewMkVarUse("", modifiers[:n-1]...).Mod()
+       vartype := G.Pkgsrc.VariableType(s.MkLines, varname)
+
+       // replace constructs the state before and after the autofix.
+       // The before state is constructed to ensure that only very simple
+       // patterns get replaced automatically.
+       //
+       // Before putting any cases involving special characters into
+       // production, there need to be more tests for the edge cases.
+       replace := func(positive bool, pattern string) (bool, string, string) {
+               defined := s.isDefined(varname, vartype)
+               if !defined && !positive {
+                       // TODO: This is a double negation, maybe even triple.
+                       //  There is an :N pattern, and the variable may be undefined.
+                       //  If it is indeed undefined, should the whole condition
+                       //  evaluate to true or false?
+                       //  The cases to be distinguished are: undefined, empty, filled.
+
+                       // For now, be conservative and don't suggest anything wrong.
+                       return false, "", ""
+               }
+               uMod := condStr(!defined && !varuse.HasModifier("U"), ":U", "")
+
+               op := condStr(neg == positive, "==", "!=")
+
+               from := sprintf("%s%s%s%s%s%s%s",
+                       condStr(neg != fromEmpty, "", "!"),
+                       condStr(fromEmpty, "empty(", "${"),
+                       varname,
+                       modsExceptLast,
+                       condStr(positive, ":M", ":N"),
+                       pattern,
+                       condStr(fromEmpty, ")", "}"))
+
+               needsQuotes := textproc.NewLexer(pattern).NextBytesSet(mkCondStringLiteralUnquoted) != pattern ||
+                       pattern == "" ||
+                       matches(pattern, `^\d+\.?\d*$`)
+               quote := condStr(needsQuotes, "\"", "")
+
+               to := sprintf(
+                       "${%s%s%s} %s %s%s%s",
+                       varname, uMod, modsExceptLast, op, quote, pattern, quote)
+
+               return true, from, to
+       }
+
+       modifier := modifiers[n-1]
+       ok, positive, pattern, exact := modifier.MatchMatch()
+       if !ok || !positive && n != 1 {
+               return
+       }
+
+       switch {
+       case !exact,
+               vartype == nil,
+               vartype.IsList(),
+               textproc.NewLexer(pattern).NextBytesSet(mkCondModifierPatternLiteral) != pattern:
+               return
+       }
+
+       ok, from, to := replace(positive, pattern)
+       if !ok {
+               return
+       }
+
+       fix := s.MkLine.Autofix()
+       fix.Notef("%s can be compared using the simpler \"%s\" "+
+               "instead of matching against %q.",
+               varname, to, ":"+modifier.String()) // TODO: Quoted
+       fix.Explain(
+               "This variable has a single value, not a list of values.",
+               "Therefore it feels strange to apply list operators like :M and :N onto it.",
+               "A more direct approach is to use the == and != operators.",
+               "",
+               "An entirely different case is when the pattern contains",
+               "wildcards like *, ?, [].",
+               "In such a case, using the :M or :N modifiers is useful and preferred.")
+       fix.Replace(from, to)
+       fix.Apply()
+}
+
+// simplifyMatch replaces:
+//  !empty(VAR:M*.c) with ${VAR:M*.c}
+//  empty(VAR:M*.c) with !${VAR:M*.c}
+//
+// * fromEmpty is true for the form empty(VAR...), and false for ${VAR...}.
+//
+// * neg is true for the form !empty(VAR...), and false for empty(VAR...).
+func (s *MkCondSimplifier) simplifyMatch(varuse *MkVarUse, fromEmpty bool, neg bool) {
+       varname := varuse.varname
+       modifiers := varuse.modifiers
+
+       n := len(modifiers)
+       if n == 0 {
+               return
+       }
+       modsExceptLast := NewMkVarUse("", modifiers[:n-1]...).Mod()
+       vartype := G.Pkgsrc.VariableType(s.MkLines, varname)
+
+       modifier := modifiers[n-1]
+       ok, positive, pattern, exact := modifier.MatchMatch()
+       if !ok || !positive && n != 1 {
+               return
+       }
+
+       if !(fromEmpty && positive && !exact && vartype != nil) {
+               return
+       }
+
+       // For now, only handle cases where the variable is guaranteed to be
+       // defined, to avoid having to place an additional ':U' modifier in the
+       // expression.
+       if !s.isDefined(varname, vartype) {
+               return
+       }
+
+       // For now, restrict replacements to very simple patterns with only few
+       // special characters.
+       //
+       // Before generalizing this to arbitrary strings, there has to be
+       // a proper code generator for these conditions that handles all
+       // possible escaping.
+       //
+       // 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\[\]]+$`) {
+               return
+       }
+
+       fixedPart := varname + modsExceptLast + ":M" + pattern
+       from := condStr(neg, "!", "") + "empty(" + fixedPart + ")"
+       to := condStr(neg, "", "!") + "${" + fixedPart + "}"
+
+       fix := s.MkLine.Autofix()
+       fix.Notef("%q can be simplified to %q.", from, to)
+       fix.Explain(
+               "This variable is guaranteed to be defined at this point.",
+               "Therefore it may occur on the left-hand side of a comparison",
+               "and doesn't have to be guarded by the function 'empty'.")
+       fix.Replace(from, to)
+       fix.Apply()
+}
+
+// isDefined determines whether the variable is guaranteed to be defined at
+// this point of reading the makefile. If it is defined, conditions do not
+// need the ':U' modifier.
+func (s *MkCondSimplifier) isDefined(varname string, vartype *Vartype) bool {
+       if vartype.IsAlwaysInScope() && vartype.IsDefinedIfInScope() {
+               return true
+       }
+
+       // For run time expressions, such as ${${VAR} == value:?yes:no},
+       // the scope would need to be changed to ck.MkLines.allVars.
+       if s.MkLines.checkAllData.vars.IsDefined(varname) {
+               return true
+       }
+
+       return s.MkLines.Tools.SeenPrefs &&
+               vartype.Union().Contains(aclpUseLoadtime) &&
+               vartype.IsDefinedIfInScope()
+}
Index: pkgsrc/pkgtools/pkglint/files/mkcondsimplifier_test.go
diff -u /dev/null pkgsrc/pkgtools/pkglint/files/mkcondsimplifier_test.go:1.1
--- /dev/null   Sun Jul 24 20:07:20 2022
+++ pkgsrc/pkgtools/pkglint/files/mkcondsimplifier_test.go      Sun Jul 24 20:07:20 2022
@@ -0,0 +1,801 @@
+package pkglint
+
+import (
+       "gopkg.in/check.v1"
+)
+
+type MkCondSimplifierTester struct {
+       c *check.C
+       *Tester
+}
+
+func (t *MkCondSimplifierTester) setUp() {
+       t.CreateFileLines("mk/bsd.prefs.mk")
+       t.Chdir("category/package")
+
+       // The Anything type suppresses the warnings from type checking.
+       // BtUnknown would not work here, see Pkgsrc.VariableType.
+       btAnything := &BasicType{"Anything", func(cv *VartypeCheck) {}}
+
+       // For simplifying the expressions, it is necessary to know whether
+       // a variable can be undefined. Undefined variables need the
+       // :U modifier or must be enclosed in double quotes, otherwise
+       // bmake will complain about a "Malformed conditional". That error
+       // message is not entirely precise since the expression
+       // is syntactically valid, it's just the evaluation that fails.
+       //
+       // Some variables such as MACHINE_ARCH are in scope from the very
+       // beginning.
+       //
+       // Some variables such as PKGPATH are in scope after bsd.prefs.mk
+       // has been included.
+       //
+       // Some variables such as PREFIX (as of December 2019) are only in
+       // scope after bsd.pkg.mk has been included. These cannot be used
+       // in .if conditions at all.
+       //
+       // Even when they are in scope, some variables such as PKGREVISION
+       // or MAKE_JOBS may be undefined.
+
+       t.SetUpVarType("IN_SCOPE_DEFINED", btAnything, AlwaysInScope|DefinedIfInScope,
+               "*.mk: use, use-loadtime")
+       t.SetUpVarType("IN_SCOPE", btAnything, AlwaysInScope,
+               "*.mk: use, use-loadtime")
+       t.SetUpVarType("PREFS_DEFINED", btAnything, DefinedIfInScope,
+               "*.mk: use, use-loadtime")
+       t.SetUpVarType("PREFS", btAnything, NoVartypeOptions,
+               "*.mk: use, use-loadtime")
+       t.SetUpVarType("LATER_DEFINED", btAnything, DefinedIfInScope,
+               "*.mk: use")
+       t.SetUpVarType("LATER", btAnything, NoVartypeOptions,
+               "*.mk: use")
+       // UNDEFINED is also used in the following tests, but is obviously
+       // not defined here.
+}
+
+func (t *MkCondSimplifierTester) testBeforePrefs(before, after string, diagnostics ...string) {
+       t.doTest(false, before, after, diagnostics...)
+}
+
+func (t *MkCondSimplifierTester) testAfterPrefs(before, after string, diagnostics ...string) {
+       t.doTest(true, before, after, diagnostics...)
+}
+func (t *MkCondSimplifierTester) testBeforeAndAfterPrefs(before, after string, diagnostics ...string) {
+       t.doTest(false, before, after, diagnostics...)
+       t.doTest(true, before, after, diagnostics...)
+}
+
+// prefs: whether to include bsd.prefs.mk before the condition
+// before: the directive before the condition is simplified
+// after: the directive after the condition is simplified
+func (t *MkCondSimplifierTester) doTest(prefs bool, before, after string, diagnostics ...string) {
+       if !matches(before, `IN_SCOPE|PREFS|LATER|UNDEFINED`) {
+               t.c.Errorf("Condition %q must include one of the above variable names.", before)
+       }
+       mklines := t.SetUpFileMkLines("filename.mk",
+               MkCvsID,
+               condStr(prefs, ".include \"../../mk/bsd.prefs.mk\"", ""),
+               "", // a few spare lines
+               "", // a few spare lines
+               "", // a few spare lines
+               before,
+               ".endif")
+
+       action := func(autofix bool) {
+               mklines.ForEach(func(mkline *MkLine) {
+                       // Sets mklines.Tools.SeenPrefs, which decides whether the :U modifier
+                       // is necessary; see MkLines.checkLine.
+                       mklines.Tools.ParseToolLine(mklines, mkline, false, false)
+
+                       if mkline.IsDirective() && mkline.Directive() != "endif" {
+                               // TODO: Replace Check with a more
+                               //  specific method that does not do the type checks.
+                               NewMkCondChecker(mkline, mklines).Check()
+                       }
+               })
+
+               if autofix {
+                       afterMklines := LoadMk(t.File("filename.mk"), nil, MustSucceed)
+                       t.CheckEquals(afterMklines.mklines[5].Text, after)
+               }
+       }
+
+       t.ExpectDiagnosticsAutofix(action, diagnostics...)
+}
+
+func (s *Suite) Test_MkCondSimplifier_SimplifyVarUse(c *check.C) {
+       t := MkCondSimplifierTester{c, s.Init(c)}
+
+       t.setUp()
+
+       t.testBeforeAndAfterPrefs(
+               ".if ${IN_SCOPE_DEFINED:Mpattern}",
+               ".if ${IN_SCOPE_DEFINED} == pattern",
+
+               "NOTE: filename.mk:6: IN_SCOPE_DEFINED can be "+
+                       "compared using the simpler \"${IN_SCOPE_DEFINED} == pattern\" "+
+                       "instead of matching against \":Mpattern\".",
+               "AUTOFIX: filename.mk:6: Replacing \"${IN_SCOPE_DEFINED:Mpattern}\" "+
+                       "with \"${IN_SCOPE_DEFINED} == pattern\".")
+
+       t.testBeforeAndAfterPrefs(
+               ".if !empty(IN_SCOPE_DEFINED:M[Nn][Oo])",
+               ".if ${IN_SCOPE_DEFINED:M[Nn][Oo]}",
+
+               "NOTE: filename.mk:6: \"!empty(IN_SCOPE_DEFINED:M[Nn][Oo])\" "+
+                       "can be simplified to \"${IN_SCOPE_DEFINED:M[Nn][Oo]}\".",
+               "AUTOFIX: filename.mk:6: "+
+                       "Replacing \"!empty(IN_SCOPE_DEFINED:M[Nn][Oo])\" "+
+                       "with \"${IN_SCOPE_DEFINED:M[Nn][Oo]}\".")
+}
+
+func (s *Suite) Test_MkCondSimplifier_simplifyWord(c *check.C) {
+       t := MkCondSimplifierTester{c, s.Init(c)}
+
+       t.setUp()
+
+       t.testBeforeAndAfterPrefs(
+               ".if ${IN_SCOPE_DEFINED:Mpattern}",
+               ".if ${IN_SCOPE_DEFINED} == pattern",
+
+               "NOTE: filename.mk:6: IN_SCOPE_DEFINED can be "+
+                       "compared using the simpler \"${IN_SCOPE_DEFINED} == pattern\" "+
+                       "instead of matching against \":Mpattern\".",
+               "AUTOFIX: filename.mk:6: Replacing \"${IN_SCOPE_DEFINED:Mpattern}\" "+
+                       "with \"${IN_SCOPE_DEFINED} == pattern\".")
+
+       t.testBeforeAndAfterPrefs(
+               ".if ${IN_SCOPE:Mpattern}",
+               ".if ${IN_SCOPE:U} == pattern",
+
+               "NOTE: filename.mk:6: IN_SCOPE can be "+
+                       "compared using the simpler \"${IN_SCOPE:U} == pattern\" "+
+                       "instead of matching against \":Mpattern\".",
+               "AUTOFIX: filename.mk:6: Replacing \"${IN_SCOPE:Mpattern}\" "+
+                       "with \"${IN_SCOPE:U} == pattern\".")
+
+       // Even though PREFS_DEFINED is declared as DefinedIfInScope,
+       // it is not in scope yet. Therefore it needs the :U modifier.
+       // The warning that this variable is not yet in scope comes from
+       // a different part of pkglint.
+       t.testBeforePrefs(
+               ".if ${PREFS_DEFINED:Mpattern}",
+               ".if ${PREFS_DEFINED:U} == pattern",
+
+               "NOTE: filename.mk:6: PREFS_DEFINED can be "+
+                       "compared using the simpler \"${PREFS_DEFINED:U} == pattern\" "+
+                       "instead of matching against \":Mpattern\".",
+               "WARN: filename.mk:6: To use PREFS_DEFINED at load time, "+
+                       ".include \"../../mk/bsd.prefs.mk\" first.",
+               "AUTOFIX: filename.mk:6: Replacing \"${PREFS_DEFINED:Mpattern}\" "+
+                       "with \"${PREFS_DEFINED:U} == pattern\".")
+
+       t.testAfterPrefs(
+               ".if ${PREFS_DEFINED:Mpattern}",
+               ".if ${PREFS_DEFINED} == pattern",
+
+               "NOTE: filename.mk:6: PREFS_DEFINED can be "+
+                       "compared using the simpler \"${PREFS_DEFINED} == pattern\" "+
+                       "instead of matching against \":Mpattern\".",
+               "AUTOFIX: filename.mk:6: Replacing \"${PREFS_DEFINED:Mpattern}\" "+
+                       "with \"${PREFS_DEFINED} == pattern\".")
+
+       t.testBeforePrefs(
+               ".if ${PREFS:Mpattern}",
+               ".if ${PREFS:U} == pattern",
+
+               "NOTE: filename.mk:6: PREFS can be "+
+                       "compared using the simpler \"${PREFS:U} == pattern\" "+
+                       "instead of matching against \":Mpattern\".",
+               "WARN: filename.mk:6: To use PREFS at load time, "+
+                       ".include \"../../mk/bsd.prefs.mk\" first.",
+               "AUTOFIX: filename.mk:6: Replacing \"${PREFS:Mpattern}\" "+
+                       "with \"${PREFS:U} == pattern\".")
+
+       // Preferences that may be undefined always need the :U modifier,
+       // even when they are in scope.
+       t.testAfterPrefs(
+               ".if ${PREFS:Mpattern}",
+               ".if ${PREFS:U} == pattern",
+
+               "NOTE: filename.mk:6: PREFS can be "+
+                       "compared using the simpler \"${PREFS:U} == pattern\" "+
+                       "instead of matching against \":Mpattern\".",
+               "AUTOFIX: filename.mk:6: Replacing \"${PREFS:Mpattern}\" "+
+                       "with \"${PREFS:U} == pattern\".")
+
+       // Variables that are defined later always need the :U modifier.
+       // It is probably a mistake to use them in conditions at all.
+       t.testBeforeAndAfterPrefs(
+               ".if ${LATER_DEFINED:Mpattern}",
+               ".if ${LATER_DEFINED:U} == pattern",
+
+               "NOTE: filename.mk:6: LATER_DEFINED can be "+
+                       "compared using the simpler \"${LATER_DEFINED:U} == pattern\" "+
+                       "instead of matching against \":Mpattern\".",
+               "WARN: filename.mk:6: "+
+                       "LATER_DEFINED should not be used at load time in any file.",
+               "AUTOFIX: filename.mk:6: Replacing \"${LATER_DEFINED:Mpattern}\" "+
+                       "with \"${LATER_DEFINED:U} == pattern\".")
+
+       // Variables that are defined later always need the :U modifier.
+       // It is probably a mistake to use them in conditions at all.
+       t.testBeforeAndAfterPrefs(
+               ".if ${LATER:Mpattern}",
+               ".if ${LATER:U} == pattern",
+
+               "NOTE: filename.mk:6: LATER can be "+
+                       "compared using the simpler \"${LATER:U} == pattern\" "+
+                       "instead of matching against \":Mpattern\".",
+               "WARN: filename.mk:6: "+
+                       "LATER should not be used at load time in any file.",
+               "AUTOFIX: filename.mk:6: Replacing \"${LATER:Mpattern}\" "+
+                       "with \"${LATER:U} == pattern\".")
+
+       t.testBeforeAndAfterPrefs(
+               ".if ${UNDEFINED:Mpattern}",
+               ".if ${UNDEFINED:Mpattern}",
+
+               "WARN: filename.mk:6: UNDEFINED is used but not defined.")
+
+       // When the pattern contains placeholders, it cannot be converted to == or !=.
+       t.testAfterPrefs(
+               ".if ${PREFS_DEFINED:Mpa*n}",
+               ".if ${PREFS_DEFINED:Mpa*n}",
+
+               nil...)
+
+       // When deciding whether to replace the expression, only the
+       // last modifier is inspected. All the others are copied.
+       t.testAfterPrefs(
+               ".if ${PREFS_DEFINED:tl:Mpattern}",
+               ".if ${PREFS_DEFINED:tl} == pattern",
+
+               "NOTE: filename.mk:6: PREFS_DEFINED can be "+
+                       "compared using the simpler \"${PREFS_DEFINED:tl} == pattern\" "+
+                       "instead of matching against \":Mpattern\".",
+               "AUTOFIX: filename.mk:6: Replacing \"${PREFS_DEFINED:tl:Mpattern}\" "+
+                       "with \"${PREFS_DEFINED:tl} == pattern\".")
+
+       // Negated pattern matches are supported as well,
+       // as long as the variable is guaranteed to be nonempty.
+       // TODO: Actually implement this.
+       //  As of December 2019, IsNonemptyIfDefined is not used anywhere.
+       t.testAfterPrefs(
+               ".if ${PREFS_DEFINED:Npattern}",
+               ".if ${PREFS_DEFINED} != pattern",
+
+               "NOTE: filename.mk:6: PREFS_DEFINED can be "+
+                       "compared using the simpler \"${PREFS_DEFINED} != pattern\" "+
+                       "instead of matching against \":Npattern\".",
+               "AUTOFIX: filename.mk:6: Replacing \"${PREFS_DEFINED:Npattern}\" "+
+                       "with \"${PREFS_DEFINED} != pattern\".")
+
+       // ${PREFS_DEFINED:None:Ntwo} is a short variant of
+       // ${PREFS_DEFINED} != "one" && ${PREFS_DEFINED} != "two".
+       // Applying the transformation would make the condition longer
+       // than before, therefore nothing can be simplified here,
+       // even though all patterns are exact matches.
+       t.testAfterPrefs(
+               ".if ${PREFS_DEFINED:None:Ntwo}",
+               ".if ${PREFS_DEFINED:None:Ntwo}",
+
+               nil...)
+
+       // Note: this combination doesn't make sense since the patterns
+       // "one" and "two" don't overlap.
+       // Nevertheless it is possible and valid to simplify the condition.
+       t.testAfterPrefs(
+               ".if ${PREFS_DEFINED:Mone:Mtwo}",
+               ".if ${PREFS_DEFINED:Mone} == two",
+
+               "NOTE: filename.mk:6: PREFS_DEFINED can be "+
+                       "compared using the simpler \"${PREFS_DEFINED:Mone} == two\" "+
+                       "instead of matching against \":Mtwo\".",
+               "AUTOFIX: filename.mk:6: Replacing \"${PREFS_DEFINED:Mone:Mtwo}\" "+
+                       "with \"${PREFS_DEFINED:Mone} == two\".")
+
+       // There is no ! before the empty, which is easy to miss.
+       // Because of this missing negation, the comparison operator is !=.
+       t.testAfterPrefs(
+               ".if empty(PREFS_DEFINED:Mpattern)",
+               ".if ${PREFS_DEFINED} != pattern",
+
+               "NOTE: filename.mk:6: PREFS_DEFINED can be "+
+                       "compared using the simpler \"${PREFS_DEFINED} != pattern\" "+
+                       "instead of matching against \":Mpattern\".",
+               "AUTOFIX: filename.mk:6: Replacing \"empty(PREFS_DEFINED:Mpattern)\" "+
+                       "with \"${PREFS_DEFINED} != pattern\".")
+
+       t.testAfterPrefs(
+               ".if !!empty(PREFS_DEFINED:Mpattern)",
+               // TODO: The ! and == could be combined into a !=.
+               //  Luckily the !! pattern doesn't occur in practice.
+               ".if !${PREFS_DEFINED} == pattern",
+
+               // TODO: When taking all the ! into account, this is actually a
+               //  test for emptiness, therefore the diagnostics should suggest
+               //  the != operator instead of ==.
+               "NOTE: filename.mk:6: PREFS_DEFINED can be "+
+                       "compared using the simpler \"${PREFS_DEFINED} == pattern\" "+
+                       "instead of matching against \":Mpattern\".",
+               "AUTOFIX: filename.mk:6: Replacing \"!empty(PREFS_DEFINED:Mpattern)\" "+
+                       "with \"${PREFS_DEFINED} == pattern\".")
+
+       // Simplifying the condition also works in complex expressions.
+       t.testAfterPrefs(".if empty(PREFS_DEFINED:Mpattern) || 0",
+               ".if ${PREFS_DEFINED} != pattern || 0",
+
+               "NOTE: filename.mk:6: PREFS_DEFINED can be "+
+                       "compared using the simpler \"${PREFS_DEFINED} != pattern\" "+
+                       "instead of matching against \":Mpattern\".",
+               "AUTOFIX: filename.mk:6: Replacing \"empty(PREFS_DEFINED:Mpattern)\" "+
+                       "with \"${PREFS_DEFINED} != pattern\".")
+
+       // No note in this case since there is no implicit !empty around the varUse.
+       // There is no obvious way of writing this expression in a simpler form.
+       t.testAfterPrefs(
+               ".if ${PREFS_DEFINED:Mpattern} != ${OTHER}",
+               ".if ${PREFS_DEFINED:Mpattern} != ${OTHER}",
+
+               "WARN: filename.mk:6: OTHER is used but not defined.")
+
+       // The condition is also simplified if it doesn't use the !empty
+       // form but the implicit conversion to boolean.
+       t.testAfterPrefs(
+               ".if ${PREFS_DEFINED:Mpattern}",
+               ".if ${PREFS_DEFINED} == pattern",
+
+               "NOTE: filename.mk:6: PREFS_DEFINED can be "+
+                       "compared using the simpler \"${PREFS_DEFINED} == pattern\" "+
+                       "instead of matching against \":Mpattern\".",
+               "AUTOFIX: filename.mk:6: Replacing \"${PREFS_DEFINED:Mpattern}\" "+
+                       "with \"${PREFS_DEFINED} == pattern\".")
+
+       // A single negation outside the implicit conversion is taken into
+       // account when simplifying the condition.
+       t.testAfterPrefs(
+               ".if !${PREFS_DEFINED:Mpattern}",
+               ".if ${PREFS_DEFINED} != pattern",
+
+               "NOTE: filename.mk:6: PREFS_DEFINED can be "+
+                       "compared using the simpler \"${PREFS_DEFINED} != pattern\" "+
+                       "instead of matching against \":Mpattern\".",
+               "AUTOFIX: filename.mk:6: Replacing \"!${PREFS_DEFINED:Mpattern}\" "+
+                       "with \"${PREFS_DEFINED} != pattern\".")
+
+       // TODO: Merge the double negation into the comparison operator.
+       t.testAfterPrefs(
+               ".if !!${PREFS_DEFINED:Mpattern}",
+               ".if !${PREFS_DEFINED} != pattern",
+
+               "NOTE: filename.mk:6: PREFS_DEFINED can be "+
+                       "compared using the simpler \"${PREFS_DEFINED} != pattern\" "+
+                       "instead of matching against \":Mpattern\".",
+               "AUTOFIX: filename.mk:6: Replacing \"!${PREFS_DEFINED:Mpattern}\" "+
+                       "with \"${PREFS_DEFINED} != pattern\".")
+
+       // This pattern with spaces doesn't make sense at all in the :M
+       // modifier since it can never match.
+       // Or can it, if the PKGPATH contains quotes?
+       // TODO: How exactly does bmake apply the matching here,
+       //  are both values unquoted first? Probably not, but who knows.
+       t.testBeforeAndAfterPrefs(
+               ".if ${IN_SCOPE_DEFINED:Mpattern with spaces}",
+               ".if ${IN_SCOPE_DEFINED:Mpattern with spaces}",
+
+               nil...)
+       // TODO: ".if ${PKGPATH} == \"pattern with spaces\"")
+
+       t.testBeforeAndAfterPrefs(
+               ".if ${IN_SCOPE_DEFINED:M'pattern with spaces'}",
+               ".if ${IN_SCOPE_DEFINED:M'pattern with spaces'}",
+
+               nil...)
+       // TODO: ".if ${PKGPATH} == 'pattern with spaces'")
+
+       t.testBeforeAndAfterPrefs(
+               ".if ${IN_SCOPE_DEFINED:M&&}",
+               ".if ${IN_SCOPE_DEFINED:M&&}",
+
+               nil...)
+       // TODO: ".if ${PKGPATH} == '&&'")
+
+       // The :N modifier involves another negation and is therefore more
+       // difficult to understand. That's even more reason to use the
+       // well-known == and != comparison operators instead.
+       //
+       // If PKGPATH is "", the condition is false.
+       // If PKGPATH is "negative-pattern", the condition is false.
+       // In all other cases, the condition is true.
+       //
+       // Therefore this condition cannot simply be transformed into
+       // ${PKGPATH} != negative-pattern, since that would produce a
+       // different result in the case where PKGPATH is empty.
+       //
+       // For system-provided variables that are guaranteed to be non-empty,
+       // such as OPSYS or PKGPATH, this replacement is valid.
+       // These variables are only guaranteed to be defined after bsd.prefs.mk
+       // has been included, like everywhere else.
+       //
+       // TODO: This is where NonemptyIfDefined comes into play.
+       t.testAfterPrefs(
+               ".if ${PREFS_DEFINED:Nnegative-pattern}",
+               ".if ${PREFS_DEFINED} != negative-pattern",
+
+               "NOTE: filename.mk:6: PREFS_DEFINED can be "+
+                       "compared using the simpler \"${PREFS_DEFINED} != negative-pattern\" "+
+                       "instead of matching against \":Nnegative-pattern\".",
+               "AUTOFIX: filename.mk:6: Replacing \"${PREFS_DEFINED:Nnegative-pattern}\" "+
+                       "with \"${PREFS_DEFINED} != negative-pattern\".")
+
+       // Since UNDEFINED is not a well-known variable that is
+       // guaranteed to be non-empty (see the previous example), it is not
+       // transformed at all.
+       t.testBeforePrefs(
+               ".if ${UNDEFINED:Nnegative-pattern}",
+               ".if ${UNDEFINED:Nnegative-pattern}",
+
+               "WARN: filename.mk:6: UNDEFINED is used but not defined.")
+
+       t.testAfterPrefs(
+               ".if ${UNDEFINED:Nnegative-pattern}",
+               ".if ${UNDEFINED:Nnegative-pattern}",
+
+               "WARN: filename.mk:6: UNDEFINED is used but not defined.")
+
+       // A complex condition may contain several simple conditions
+       // that are each simplified independently, in the same go.
+       t.testAfterPrefs(
+               ".if ${PREFS_DEFINED:Mpath1} || ${PREFS_DEFINED:Mpath2}",
+               ".if ${PREFS_DEFINED} == path1 || ${PREFS_DEFINED} == path2",
+
+               "NOTE: filename.mk:6: PREFS_DEFINED can be "+
+                       "compared using the simpler \"${PREFS_DEFINED} == path1\" "+
+                       "instead of matching against \":Mpath1\".",
+               "NOTE: filename.mk:6: PREFS_DEFINED can be "+
+                       "compared using the simpler \"${PREFS_DEFINED} == path2\" "+
+                       "instead of matching against \":Mpath2\".",
+               "AUTOFIX: filename.mk:6: Replacing \"${PREFS_DEFINED:Mpath1}\" "+
+                       "with \"${PREFS_DEFINED} == path1\".",
+               "AUTOFIX: filename.mk:6: Replacing \"${PREFS_DEFINED:Mpath2}\" "+
+                       "with \"${PREFS_DEFINED} == path2\".")
+
+       // Removing redundant parentheses may be useful one day but is
+       // not urgent.
+       // Simplifying the inner expression keeps all parentheses as-is.
+       t.testAfterPrefs(
+               ".if (((((${PREFS_DEFINED:Mpath})))))",
+               ".if (((((${PREFS_DEFINED} == path)))))",
+
+               "NOTE: filename.mk:6: PREFS_DEFINED can be "+
+                       "compared using the simpler \"${PREFS_DEFINED} == path\" "+
+                       "instead of matching against \":Mpath\".",
+               "AUTOFIX: filename.mk:6: Replacing \"${PREFS_DEFINED:Mpath}\" "+
+                       "with \"${PREFS_DEFINED} == path\".")
+
+       // Several modifiers like :S and :C may change the variable value.
+       // Whether the condition can be simplified or not only depends on the
+       // last modifier in the chain.
+       t.testAfterPrefs(
+               ".if !empty(PREFS_DEFINED:S,NetBSD,ok,:Mok)",
+               ".if ${PREFS_DEFINED:S,NetBSD,ok,} == ok",
+
+               "NOTE: filename.mk:6: PREFS_DEFINED can be "+
+                       "compared using the simpler \"${PREFS_DEFINED:S,NetBSD,ok,} == ok\" "+
+                       "instead of matching against \":Mok\".",
+               "AUTOFIX: filename.mk:6: Replacing \"!empty(PREFS_DEFINED:S,NetBSD,ok,:Mok)\" "+
+                       "with \"${PREFS_DEFINED:S,NetBSD,ok,} == ok\".")
+
+       t.testAfterPrefs(
+               ".if empty(PREFS_DEFINED:tl:Msunos)",
+               ".if ${PREFS_DEFINED:tl} != sunos",
+
+               "NOTE: filename.mk:6: PREFS_DEFINED can be "+
+                       "compared using the simpler \"${PREFS_DEFINED:tl} != sunos\" "+
+                       "instead of matching against \":Msunos\".",
+               "AUTOFIX: filename.mk:6: Replacing \"empty(PREFS_DEFINED:tl:Msunos)\" "+
+                       "with \"${PREFS_DEFINED:tl} != sunos\".")
+
+       // The condition can only be simplified if the :M or :N modifier
+       // appears at the end of the chain.
+       t.testAfterPrefs(
+               ".if !empty(PREFS_DEFINED:O:MUnknown:S,a,b,)",
+               ".if !empty(PREFS_DEFINED:O:MUnknown:S,a,b,)",
+
+               nil...)
+
+       // The dot is just an ordinary character in a pattern.
+       // In comparisons, an unquoted 1.2 is interpreted as a number though.
+       t.testAfterPrefs(
+               ".if !empty(PREFS_DEFINED:Mpackage1.2)",
+               ".if ${PREFS_DEFINED} == package1.2",
+
+               "NOTE: filename.mk:6: PREFS_DEFINED can be "+
+                       "compared using the simpler \"${PREFS_DEFINED} == package1.2\" "+
+                       "instead of matching against \":Mpackage1.2\".",
+               "AUTOFIX: filename.mk:6: Replacing \"!empty(PREFS_DEFINED:Mpackage1.2)\" "+
+                       "with \"${PREFS_DEFINED} == package1.2\".")
+
+       // Numbers must be enclosed in quotes, otherwise they are compared
+       // as numbers, not as strings.
+       // The :M and :N modifiers always compare strings.
+       t.testAfterPrefs(
+               ".if empty(PREFS:U:M64)",
+               ".if ${PREFS:U} != \"64\"",
+
+               "NOTE: filename.mk:6: PREFS can be "+
+                       "compared using the simpler \"${PREFS:U} != \"64\"\" "+
+                       "instead of matching against \":M64\".",
+               "AUTOFIX: filename.mk:6: Replacing \"empty(PREFS:U:M64)\" "+
+                       "with \"${PREFS:U} != \\\"64\\\"\".")
+
+       // Fractional numbers must also be enclosed in quotes.
+       t.testAfterPrefs(
+               ".if empty(PREFS:U:M19.12)",
+               ".if ${PREFS:U} != \"19.12\"",
+
+               "NOTE: filename.mk:6: PREFS can be "+
+                       "compared using the simpler \"${PREFS:U} != \"19.12\"\" "+
+                       "instead of matching against \":M19.12\".",
+               "AUTOFIX: filename.mk:6: Replacing \"empty(PREFS:U:M19.12)\" "+
+                       "with \"${PREFS:U} != \\\"19.12\\\"\".")
+
+       t.testAfterPrefs(
+               ".if !empty(LATER:Npattern)",
+               ".if !empty(LATER:Npattern)",
+
+               // No diagnostics about the :N modifier yet,
+               // see MkCondChecker.simplify.replace.
+               "WARN: filename.mk:6: LATER should not be used "+
+                       "at load time in any file.")
+
+       // TODO: Add a note that the :U is unnecessary, and explain why.
+       t.testAfterPrefs(
+               ".if ${PREFS_DEFINED:U:Mpattern}",
+               ".if ${PREFS_DEFINED:U} == pattern",
+
+               "NOTE: filename.mk:6: PREFS_DEFINED can be "+
+                       "compared using the simpler \"${PREFS_DEFINED:U} == pattern\" "+
+                       "instead of matching against \":Mpattern\".",
+               "AUTOFIX: filename.mk:6: "+
+                       "Replacing \"${PREFS_DEFINED:U:Mpattern}\" "+
+                       "with \"${PREFS_DEFINED:U} == pattern\".")
+
+       // Conditions without any modifiers cannot be simplified
+       // and are therefore skipped.
+       t.testBeforeAndAfterPrefs(
+               ".if ${IN_SCOPE_DEFINED}",
+               ".if ${IN_SCOPE_DEFINED}",
+
+               nil...)
+
+       // Special characters must be enclosed in quotes when they are
+       // used in string literals.
+       // As of December 2019, strings with special characters are not yet
+       // replaced automatically, see mkCondLiteralChars.
+       // TODO: Add tests for all characters that are special in string literals or patterns.
+       // TODO: Then, extend the set of characters for which the expressions are simplified.
+       t.testBeforePrefs(
+               ".if ${PREFS_DEFINED:M&&}",
+               ".if ${PREFS_DEFINED:M&&}",
+
+               "WARN: filename.mk:6: To use PREFS_DEFINED at load time, .include \"../../mk/bsd.prefs.mk\" first.")
+       t.testAfterPrefs(
+               ".if ${PREFS_DEFINED:M&&}",
+               ".if ${PREFS_DEFINED:M&&}",
+
+               nil...)
+
+       t.testBeforePrefs(
+               ".if ${PREFS:M&&}",
+               ".if ${PREFS:M&&}",
+
+               // TODO: Warn that the :U is missing.
+               "WARN: filename.mk:6: To use PREFS at load time, .include \"../../mk/bsd.prefs.mk\" first.")
+       t.testAfterPrefs(
+               ".if ${PREFS:M&&}",
+               ".if ${PREFS:M&&}",
+
+               // TODO: Warn that the :U is missing.
+               nil...)
+
+       // The + is contained in both mkCondStringLiteralUnquoted and
+       // mkCondModifierPatternLiteral, therefore it is copied verbatim.
+       t.testAfterPrefs(
+               ".if ${PREFS_DEFINED:Mcategory/gtk+}",
+               ".if ${PREFS_DEFINED} == category/gtk+",
+
+               "NOTE: filename.mk:6: PREFS_DEFINED can be "+
+                       "compared using the simpler \"${PREFS_DEFINED} == category/gtk+\" "+
+                       "instead of matching against \":Mcategory/gtk+\".",
+               "AUTOFIX: filename.mk:6: "+
+                       "Replacing \"${PREFS_DEFINED:Mcategory/gtk+}\" "+
+                       "with \"${PREFS_DEFINED} == category/gtk+\".")
+
+       // The characters <=> may be used unescaped in :M and :N patterns
+       // but not in .if conditions. There they must be enclosed in quotes.
+       t.testBeforePrefs(
+               ".if ${PREFS_DEFINED:M<=>}",
+               ".if ${PREFS_DEFINED:U} == \"<=>\"",
+
+               "NOTE: filename.mk:6: PREFS_DEFINED can be "+
+                       "compared using the simpler \"${PREFS_DEFINED:U} == \"<=>\"\" "+
+                       "instead of matching against \":M<=>\".",
+               "WARN: filename.mk:6: To use PREFS_DEFINED at load time, "+
+                       ".include \"../../mk/bsd.prefs.mk\" first.",
+               "AUTOFIX: filename.mk:6: "+
+                       "Replacing \"${PREFS_DEFINED:M<=>}\" "+
+                       "with \"${PREFS_DEFINED:U} == \\\"<=>\\\"\".")
+       t.testAfterPrefs(
+               ".if ${PREFS_DEFINED:M<=>}",
+               ".if ${PREFS_DEFINED} == \"<=>\"",
+
+               "NOTE: filename.mk:6: PREFS_DEFINED can be "+
+                       "compared using the simpler \"${PREFS_DEFINED} == \"<=>\"\" "+
+                       "instead of matching against \":M<=>\".",
+               "AUTOFIX: filename.mk:6: "+
+                       "Replacing \"${PREFS_DEFINED:M<=>}\" "+
+                       "with \"${PREFS_DEFINED} == \\\"<=>\\\"\".")
+
+       // If pkglint replaces this particular pattern, the resulting string
+       // literal must be escaped properly.
+       t.testBeforeAndAfterPrefs(
+               ".if ${IN_SCOPE_DEFINED:M\"}",
+               ".if ${IN_SCOPE_DEFINED:M\"}",
+
+               nil...)
+
+       t.testBeforeAndAfterPrefs(
+               ".if !empty(IN_SCOPE_DEFINED:M)",
+               ".if ${IN_SCOPE_DEFINED} == \"\"",
+
+               "NOTE: filename.mk:6: IN_SCOPE_DEFINED can be "+
+                       "compared using the simpler "+"\"${IN_SCOPE_DEFINED} == \"\"\" "+
+                       "instead of matching against \":M\".",
+               "AUTOFIX: filename.mk:6: "+
+                       "Replacing \"!empty(IN_SCOPE_DEFINED:M)\" "+
+                       "with \"${IN_SCOPE_DEFINED} == \\\"\\\"\".",
+       )
+}
+
+func (s *Suite) Test_MkCondSimplifier_simplifyWord__defined_in_same_file(c *check.C) {
+       t := s.Init(c)
+
+       t.SetUpPackage("category/package")
+       t.Chdir("category/package")
+       t.FinishSetUp()
+
+       doTest := func(before string) {
+               mklines := t.SetUpFileMkLines("filename.mk",
+                       MkCvsID,
+                       "OK=\t\tok",
+                       "OK_DIR=\t\tok", // See Pkgsrc.guessVariableType.
+                       before,
+                       "LATER=\t\tlater",
+                       "LATER_DIR=\tlater", // See Pkgsrc.guessVariableType.
+                       ".endif",
+                       "USED=\t\t${OK} ${LATER} ${OK_DIR} ${LATER_DIR} ${USED}")
+
+               // The high-level call MkLines.Check is used here to
+               // get MkLines.Tools.SeenPrefs correct, which decides
+               // whether the :U modifier is necessary.
+               mklines.Check()
+       }
+
+       // before: the directive before the condition is simplified
+       // after: the directive after the condition is simplified
+       test := func(before, after string, diagnostics ...string) {
+
+               t.ExpectDiagnosticsAutofix(
+                       func(bool) { doTest(before) },
+                       diagnostics...)
+
+               // TODO: Move this assertion above the assertion about the diagnostics.
+               afterMklines := LoadMk(t.File("filename.mk"), nil, MustSucceed)
+               t.CheckEquals(afterMklines.mklines[3].Text, after)
+       }
+
+       // For variables with completely unknown names, the type is nil
+       // and the complete check is skipped.
+       test(
+               ".if ${OK:Mpattern}",
+               ".if ${OK:Mpattern}",
+
+               nil...)
+
+       // For variables with completely unknown names, the type is nil
+       // and the complete check is skipped.
+       test(
+               ".if ${LATER:Mpattern}",
+               ".if ${LATER:Mpattern}",
+
+               nil...)
+
+       // OK_DIR is defined earlier than the .if condition,
+       // which is the correct order.
+       test(
+               ".if ${OK_DIR:Mpattern}",
+               ".if ${OK_DIR} == pattern",
+
+               "NOTE: filename.mk:4: OK_DIR can be "+
+                       "compared using the simpler \"${OK_DIR} == pattern\" "+
+                       "instead of matching against \":Mpattern\".",
+               "AUTOFIX: filename.mk:4: "+
+                       "Replacing \"${OK_DIR:Mpattern}\" "+
+                       "with \"${OK_DIR} == pattern\".")
+
+       // LATER_DIR is defined later than the .if condition,
+       // therefore at the time of the .if statement, it is still empty.
+       test(
+               ".if ${LATER_DIR:Mpattern}",
+               ".if ${LATER_DIR:U} == pattern",
+
+               // TODO: Warn that LATER_DIR is used before it is defined.
+               "NOTE: filename.mk:4: LATER_DIR can be "+
+                       "compared using the simpler \"${LATER_DIR:U} == pattern\" "+
+                       "instead of matching against \":Mpattern\".",
+               "AUTOFIX: filename.mk:4: "+
+                       "Replacing \"${LATER_DIR:Mpattern}\" "+
+                       "with \"${LATER_DIR:U} == pattern\".")
+}
+
+func (s *Suite) Test_MkCondSimplifier_simplifyMatch(c *check.C) {
+       t := MkCondSimplifierTester{c, s.Init(c)}
+
+       t.setUp()
+
+       t.testBeforeAndAfterPrefs(
+               ".if !empty(IN_SCOPE_DEFINED:M*.c)",
+               ".if ${IN_SCOPE_DEFINED:M*.c}",
+
+               "NOTE: filename.mk:6: \"!empty(IN_SCOPE_DEFINED:M*.c)\" "+
+                       "can be simplified to \"${IN_SCOPE_DEFINED:M*.c}\".",
+               "AUTOFIX: filename.mk:6: "+
+                       "Replacing \"!empty(IN_SCOPE_DEFINED:M*.c)\" "+
+                       "with \"${IN_SCOPE_DEFINED:M*.c}\".")
+
+       t.testBeforeAndAfterPrefs(
+               ".if empty(IN_SCOPE_DEFINED:M*.c)",
+               ".if !${IN_SCOPE_DEFINED:M*.c}",
+
+               "NOTE: filename.mk:6: \"empty(IN_SCOPE_DEFINED:M*.c)\" "+
+                       "can be simplified to \"!${IN_SCOPE_DEFINED:M*.c}\".",
+               "AUTOFIX: filename.mk:6: "+
+                       "Replacing \"empty(IN_SCOPE_DEFINED:M*.c)\" "+
+                       "with \"!${IN_SCOPE_DEFINED:M*.c}\".")
+
+       t.testBeforeAndAfterPrefs(
+               ".if !empty(IN_SCOPE_DEFINED:M[Nn][Oo])",
+               ".if ${IN_SCOPE_DEFINED:M[Nn][Oo]}",
+
+               "NOTE: filename.mk:6: \"!empty(IN_SCOPE_DEFINED:M[Nn][Oo])\" "+
+                       "can be simplified to \"${IN_SCOPE_DEFINED:M[Nn][Oo]}\".",
+               "AUTOFIX: filename.mk:6: "+
+                       "Replacing \"!empty(IN_SCOPE_DEFINED:M[Nn][Oo])\" "+
+                       "with \"${IN_SCOPE_DEFINED:M[Nn][Oo]}\".")
+}
+
+func (s *Suite) Test_MkCondSimplifier_isDefined(c *check.C) {
+       t := s.Init(c)
+
+       mklines := t.NewMkLines("filename.mk",
+               MkCvsID,
+               "DEFINED=\tyes",
+               ".if defined(UNDEFINED) && defined(DEFINED)",
+               ".endif")
+       // Initialize whether the variables are defined.
+       mklines.Check()
+
+       mklines.ForEach(func(mkline *MkLine) {
+               if mkline.IsDirective() && mkline.Directive() == "if" {
+                       mcs := MkCondSimplifier{mklines, mkline}
+                       vartype := NewVartype(BtMessage, 0, nil...)
+
+                       t.AssertEquals(false, mcs.isDefined("UNDEFINED", vartype))
+                       t.AssertEquals(true, mcs.isDefined("DEFINED", vartype))
+               }
+       })
+       t.CheckOutputLines(
+               "WARN: filename.mk:3: UNDEFINED is used but not defined.")
+}



Home | Main Index | Thread Index | Old Index