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


(rillig)
diff -r1.722 -r1.723 pkgsrc/pkgtools/pkglint/Makefile
diff -r1.14 -r1.15 pkgsrc/pkgtools/pkglint/files/mkassignchecker.go
diff -r1.14 -r1.15 pkgsrc/pkgtools/pkglint/files/redundantscope.go
diff -r1.13 -r1.14 pkgsrc/pkgtools/pkglint/files/mkcondchecker.go
diff -r1.11 -r1.12 pkgsrc/pkgtools/pkglint/files/mkcondchecker_test.go
diff -r0 -r1.1 pkgsrc/pkgtools/pkglint/files/mkcondsimplifier.go
diff -r0 -r1.1 pkgsrc/pkgtools/pkglint/files/mkcondsimplifier_test.go
diff -r1.44 -r1.45 pkgsrc/pkgtools/pkglint/files/mkparser.go
diff -r1.26 -r1.27 pkgsrc/pkgtools/pkglint/files/mktypes.go
diff -r1.90 -r1.91 pkgsrc/pkgtools/pkglint/files/package_test.go
diff -r1.104 -r1.105 pkgsrc/pkgtools/pkglint/files/vardefs.go
diff -r1.100 -r1.101 pkgsrc/pkgtools/pkglint/files/vartypecheck.go
diff -r1.92 -r1.93 pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go

cvs diff -r1.722 -r1.723 pkgsrc/pkgtools/pkglint/Makefile (expand / switch to unified diff)

--- pkgsrc/pkgtools/pkglint/Makefile 2022/07/13 16:03:04 1.722
+++ pkgsrc/pkgtools/pkglint/Makefile 2022/07/24 20:07:20 1.723
@@ -1,17 +1,16 @@ @@ -1,17 +1,16 @@
1# $NetBSD: Makefile,v 1.722 2022/07/13 16:03:04 bsiegert Exp $ 1# $NetBSD: Makefile,v 1.723 2022/07/24 20:07:20 rillig Exp $
2 2
3PKGNAME= pkglint-22.2.2 3PKGNAME= pkglint-22.2.3
4PKGREVISION= 1 
5CATEGORIES= pkgtools 4CATEGORIES= pkgtools
6DISTNAME= tools 5DISTNAME= tools
7MASTER_SITES= ${MASTER_SITE_GITHUB:=golang/} 6MASTER_SITES= ${MASTER_SITE_GITHUB:=golang/}
8GITHUB_PROJECT= tools 7GITHUB_PROJECT= tools
9GITHUB_TAG= 92d8274bd7b8a4c65f24bafe401a029e58392704 8GITHUB_TAG= 92d8274bd7b8a4c65f24bafe401a029e58392704
10 9
11MAINTAINER= rillig@NetBSD.org 10MAINTAINER= rillig@NetBSD.org
12HOMEPAGE= https://github.com/rillig/pkglint 11HOMEPAGE= https://github.com/rillig/pkglint
13COMMENT= Verifier for NetBSD packages 12COMMENT= Verifier for NetBSD packages
14LICENSE= 2-clause-bsd 13LICENSE= 2-clause-bsd
15CONFLICTS+= pkglint4-[0-9]* 14CONFLICTS+= pkglint4-[0-9]*
16 15
17USE_TOOLS+= pax 16USE_TOOLS+= pax

cvs diff -r1.14 -r1.15 pkgsrc/pkgtools/pkglint/files/Attic/mkassignchecker.go (expand / switch to unified diff)

--- pkgsrc/pkgtools/pkglint/files/Attic/mkassignchecker.go 2022/07/09 06:40:55 1.14
+++ pkgsrc/pkgtools/pkglint/files/Attic/mkassignchecker.go 2022/07/24 20:07:20 1.15
@@ -484,26 +484,31 @@ func (ck *MkAssignChecker) checkRight()  @@ -484,26 +484,31 @@ func (ck *MkAssignChecker) checkRight()
484 mkline := ck.MkLine 484 mkline := ck.MkLine
485 varname := mkline.Varname() 485 varname := mkline.Varname()
486 op := mkline.Op() 486 op := mkline.Op()
487 value := mkline.Value() 487 value := mkline.Value()
488 comment := condStr(mkline.HasComment(), "#", "") + mkline.Comment() 488 comment := condStr(mkline.HasComment(), "#", "") + mkline.Comment()
489 489
490 if trace.Tracing { 490 if trace.Tracing {
491 defer trace.Call(varname, op, value)() 491 defer trace.Call(varname, op, value)()
492 } 492 }
493 493
494 mkLineChecker := NewMkLineChecker(ck.MkLines, ck.MkLine) 494 mkLineChecker := NewMkLineChecker(ck.MkLines, ck.MkLine)
495 mkLineChecker.checkText(value) 495 mkLineChecker.checkText(value)
496 mkLineChecker.checkVartype(varname, op, value, comment) 496 mkLineChecker.checkVartype(varname, op, value, comment)
 497 if mkline.IsEmpty() {
 498 // The line type can change due to an Autofix, see for example
 499 // VartypeCheck.WrkdirSubdirectory.
 500 return
 501 }
497 502
498 ck.checkMisc() 503 ck.checkMisc()
499 504
500 ck.checkRightVaruse() 505 ck.checkRightVaruse()
501} 506}
502 507
503func (ck *MkAssignChecker) checkRightCategory() { 508func (ck *MkAssignChecker) checkRightCategory() {
504 mkline := ck.MkLine 509 mkline := ck.MkLine
505 if mkline.Op() != opAssign && mkline.Op() != opAssignDefault { 510 if mkline.Op() != opAssign && mkline.Op() != opAssignDefault {
506 return 511 return
507 } 512 }
508 513
509 categories := mkline.ValueFields(mkline.Value()) 514 categories := mkline.ValueFields(mkline.Value())

cvs diff -r1.14 -r1.15 pkgsrc/pkgtools/pkglint/files/Attic/redundantscope.go (expand / switch to unified diff)

--- pkgsrc/pkgtools/pkglint/files/Attic/redundantscope.go 2020/07/22 19:26:30 1.14
+++ pkgsrc/pkgtools/pkglint/files/Attic/redundantscope.go 2022/07/24 20:07:20 1.15
@@ -231,27 +231,27 @@ func (s *RedundantScope) handleVarUse(mk @@ -231,27 +231,27 @@ func (s *RedundantScope) handleVarUse(mk
231 case mkline.IsDirective(): 231 case mkline.IsDirective():
232 // TODO: Handle varuse for conditions and loops. 232 // TODO: Handle varuse for conditions and loops.
233 break 233 break
234 234
235 case mkline.IsInclude(), mkline.IsSysinclude(): 235 case mkline.IsInclude(), mkline.IsSysinclude():
236 // TODO: Handle VarUse for includes, which may reference variables. 236 // TODO: Handle VarUse for includes, which may reference variables.
237 break 237 break
238 238
239 case mkline.IsDependency(): 239 case mkline.IsDependency():
240 // TODO: Handle VarUse for this case. 240 // TODO: Handle VarUse for this case.
241 } 241 }
242} 242}
243 243
244// access returns the info for the given variable, creating it if necessary. 244// get returns the info for the given variable, creating it if necessary.
245func (s *RedundantScope) get(varname string) *redundantScopeVarinfo { 245func (s *RedundantScope) get(varname string) *redundantScopeVarinfo {
246 info := s.vars[varname] 246 info := s.vars[varname]
247 if info == nil { 247 if info == nil {
248 v := NewVar(varname) 248 v := NewVar(varname)
249 info = &redundantScopeVarinfo{v, nil, 0} 249 info = &redundantScopeVarinfo{v, nil, 0}
250 s.vars[varname] = info 250 s.vars[varname] = info
251 } 251 }
252 return info 252 return info
253} 253}
254 254
255// access records the current file location, to be used in later inclusion checks. 255// access records the current file location, to be used in later inclusion checks.
256func (s *RedundantScope) access(varname string) { 256func (s *RedundantScope) access(varname string) {
257 info := s.vars[varname] 257 info := s.vars[varname]

cvs diff -r1.13 -r1.14 pkgsrc/pkgtools/pkglint/files/Attic/mkcondchecker.go (expand / switch to unified diff)

--- pkgsrc/pkgtools/pkglint/files/Attic/mkcondchecker.go 2022/07/09 06:40:55 1.13
+++ pkgsrc/pkgtools/pkglint/files/Attic/mkcondchecker.go 2022/07/24 20:07:20 1.14
@@ -99,27 +99,29 @@ func (ck *MkCondChecker) checkNotEmpty(n @@ -99,27 +99,29 @@ func (ck *MkCondChecker) checkNotEmpty(n
99 "Besides being simpler to read, the expression will also fail", 99 "Besides being simpler to read, the expression will also fail",
100 "quickly with a \"Malformed conditional\" error from bmake", 100 "quickly with a \"Malformed conditional\" error from bmake",
101 "if it should ever be undefined at this point.", 101 "if it should ever be undefined at this point.",
102 "This catches typos and other programming mistakes.") 102 "This catches typos and other programming mistakes.")
103 fix.Replace(from, to) 103 fix.Replace(from, to)
104 fix.Apply() 104 fix.Apply()
105} 105}
106 106
107// checkEmpty checks a condition of the form empty(VAR), 107// checkEmpty checks a condition of the form empty(VAR),
108// empty(VAR:Mpattern) or ${VAR:Mpattern} in an .if directive. 108// empty(VAR:Mpattern) or ${VAR:Mpattern} in an .if directive.
109func (ck *MkCondChecker) checkEmpty(varuse *MkVarUse, fromEmpty bool, neg bool) { 109func (ck *MkCondChecker) checkEmpty(varuse *MkVarUse, fromEmpty bool, neg bool) {
110 ck.checkEmptyExpr(varuse) 110 ck.checkEmptyExpr(varuse)
111 ck.checkEmptyType(varuse) 111 ck.checkEmptyType(varuse)
112 ck.simplify(varuse, fromEmpty, neg) 112
 113 s := MkCondSimplifier{ck.MkLines, ck.MkLine}
 114 s.SimplifyVarUse(varuse, fromEmpty, neg)
113} 115}
114 116
115func (ck *MkCondChecker) checkEmptyExpr(varuse *MkVarUse) { 117func (ck *MkCondChecker) checkEmptyExpr(varuse *MkVarUse) {
116 if !matches(varuse.varname, `^\$.*:[MN]`) { 118 if !matches(varuse.varname, `^\$.*:[MN]`) {
117 return 119 return
118 } 120 }
119 121
120 ck.MkLine.Warnf("The empty() function takes a variable name as parameter, " + 122 ck.MkLine.Warnf("The empty() function takes a variable name as parameter, " +
121 "not a variable expression.") 123 "not a variable expression.")
122 ck.MkLine.Explain( 124 ck.MkLine.Explain(
123 "Instead of empty(${VARNAME:Mpattern}), you should write either of the following:", 125 "Instead of empty(${VARNAME:Mpattern}), you should write either of the following:",
124 "", 126 "",
125 "\tempty(VARNAME:Mpattern)", 127 "\tempty(VARNAME:Mpattern)",
@@ -150,165 +152,26 @@ func (ck *MkCondChecker) checkEmptyType( @@ -150,165 +152,26 @@ func (ck *MkCondChecker) checkEmptyType(
150} 152}
151 153
152// mkCondStringLiteralUnquoted contains a safe subset of the characters 154// mkCondStringLiteralUnquoted contains a safe subset of the characters
153// that may be used without surrounding quotes in a comparison such as 155// that may be used without surrounding quotes in a comparison such as
154// ${PKGPATH} == category/package. 156// ${PKGPATH} == category/package.
155// TODO: Check whether the ',' really needs to be here. 157// TODO: Check whether the ',' really needs to be here.
156var mkCondStringLiteralUnquoted = textproc.NewByteSet("-+,./0-9@A-Z_a-z") 158var mkCondStringLiteralUnquoted = textproc.NewByteSet("-+,./0-9@A-Z_a-z")
157 159
158// mkCondModifierPatternLiteral contains a safe subset of the characters 160// mkCondModifierPatternLiteral contains a safe subset of the characters
159// that are interpreted literally in the :M and :N modifiers. 161// that are interpreted literally in the :M and :N modifiers.
160// TODO: Check whether the ',' really needs to be here. 162// TODO: Check whether the ',' really needs to be here.
161var mkCondModifierPatternLiteral = textproc.NewByteSet("-+,./0-9<=>@A-Z_a-z") 163var mkCondModifierPatternLiteral = textproc.NewByteSet("-+,./0-9<=>@A-Z_a-z")
162 164
163// simplify replaces an unnecessarily complex condition with 
164// a simpler condition that's still equivalent. 
165// 
166// * fromEmpty is true for the form empty(VAR...), and false for ${VAR...}. 
167// 
168// * neg is true for the form !empty(VAR...), and false for empty(VAR...). 
169func (ck *MkCondChecker) simplify(varuse *MkVarUse, fromEmpty bool, neg bool) { 
170 varname := varuse.varname 
171 modifiers := varuse.modifiers 
172 
173 n := len(modifiers) 
174 if n == 0 { 
175 return 
176 } 
177 modsExceptLast := NewMkVarUse("", modifiers[:n-1]...).Mod() 
178 vartype := G.Pkgsrc.VariableType(ck.MkLines, varname) 
179 
180 isDefined := func() bool { 
181 if vartype.IsAlwaysInScope() && vartype.IsDefinedIfInScope() { 
182 return true 
183 } 
184 
185 // For run time expressions, such as ${${VAR} == value:?yes:no}, 
186 // the scope would need to be changed to ck.MkLines.allVars. 
187 if ck.MkLines.checkAllData.vars.IsDefined(varname) { 
188 return true 
189 } 
190 
191 return ck.MkLines.Tools.SeenPrefs && 
192 vartype.Union().Contains(aclpUseLoadtime) && 
193 vartype.IsDefinedIfInScope() 
194 } 
195 
196 // replace constructs the state before and after the autofix. 
197 // The before state is constructed to ensure that only very simple 
198 // patterns get replaced automatically. 
199 // 
200 // Before putting any cases involving special characters into 
201 // production, there need to be more tests for the edge cases. 
202 replace := func(positive bool, pattern string) (bool, string, string) { 
203 defined := isDefined() 
204 if !defined && !positive { 
205 // TODO: This is a double negation, maybe even triple. 
206 // There is an :N pattern, and the variable may be undefined. 
207 // If it is indeed undefined, should the whole condition 
208 // evaluate to true or false? 
209 // The cases to be distinguished are: undefined, empty, filled. 
210 
211 // For now, be conservative and don't suggest anything wrong. 
212 return false, "", "" 
213 } 
214 uMod := condStr(!defined && !varuse.HasModifier("U"), ":U", "") 
215 
216 op := condStr(neg == positive, "==", "!=") 
217 
218 from := sprintf("%s%s%s%s%s%s%s", 
219 condStr(neg != fromEmpty, "", "!"), 
220 condStr(fromEmpty, "empty(", "${"), 
221 varname, 
222 modsExceptLast, 
223 condStr(positive, ":M", ":N"), 
224 pattern, 
225 condStr(fromEmpty, ")", "}")) 
226 
227 needsQuotes := textproc.NewLexer(pattern).NextBytesSet(mkCondStringLiteralUnquoted) != pattern || 
228 pattern == "" || 
229 matches(pattern, `^\d+\.?\d*$`) 
230 quote := condStr(needsQuotes, "\"", "") 
231 
232 to := sprintf( 
233 "${%s%s%s} %s %s%s%s", 
234 varname, uMod, modsExceptLast, op, quote, pattern, quote) 
235 
236 return true, from, to 
237 } 
238 
239 modifier := modifiers[n-1] 
240 ok, positive, pattern, exact := modifier.MatchMatch() 
241 if !ok || !positive && n != 1 { 
242 return 
243 } 
244 
245 // Replace !empty(VAR:M*.c) with ${VAR:M*.c}. 
246 // Replace empty(VAR:M*.c) with !${VAR:M*.c}. 
247 if fromEmpty && positive && !exact && vartype != nil && isDefined() && 
248 // Restrict replacements to very simple patterns with only few 
249 // special characters, for now. 
250 // Before generalizing this to arbitrary strings, there has to be 
251 // a proper code generator for these conditions that handles all 
252 // possible escaping. 
253 // The same reasoning applies to the variable name, even though the 
254 // variable name typically only uses a restricted character set. 
255 matches(varuse.Mod(), `^[*.:\w]+$`) { 
256 
257 fixedPart := varname + modsExceptLast + ":M" + pattern 
258 from := condStr(neg, "!", "") + "empty(" + fixedPart + ")" 
259 to := condStr(neg, "", "!") + "${" + fixedPart + "}" 
260 
261 fix := ck.MkLine.Autofix() 
262 fix.Notef("%q can be simplified to %q.", from, to) 
263 fix.Explain( 
264 "This variable is guaranteed to be defined at this point.", 
265 "Therefore it may occur on the left-hand side of a comparison", 
266 "and doesn't have to be guarded by the function 'empty'.") 
267 fix.Replace(from, to) 
268 fix.Apply() 
269 
270 return 
271 } 
272 
273 switch { 
274 case !exact, 
275 vartype == nil, 
276 vartype.IsList(), 
277 textproc.NewLexer(pattern).NextBytesSet(mkCondModifierPatternLiteral) != pattern: 
278 return 
279 } 
280 
281 ok, from, to := replace(positive, pattern) 
282 if !ok { 
283 return 
284 } 
285 
286 fix := ck.MkLine.Autofix() 
287 fix.Notef("%s can be compared using the simpler \"%s\" "+ 
288 "instead of matching against %q.", 
289 varname, to, ":"+modifier.String()) // TODO: Quoted 
290 fix.Explain( 
291 "This variable has a single value, not a list of values.", 
292 "Therefore it feels strange to apply list operators like :M and :N onto it.", 
293 "A more direct approach is to use the == and != operators.", 
294 "", 
295 "An entirely different case is when the pattern contains", 
296 "wildcards like *, ?, [].", 
297 "In such a case, using the :M or :N modifiers is useful and preferred.") 
298 fix.Replace(from, to) 
299 fix.Apply() 
300} 
301 
302func (ck *MkCondChecker) checkCompare(left *MkCondTerm, op string, right *MkCondTerm) { 165func (ck *MkCondChecker) checkCompare(left *MkCondTerm, op string, right *MkCondTerm) {
303 switch { 166 switch {
304 case right.Num != "": 167 case right.Num != "":
305 ck.checkCompareVarNum(op, right.Num) 168 ck.checkCompareVarNum(op, right.Num)
306 case left.Var != nil && right.Var == nil: 169 case left.Var != nil && right.Var == nil:
307 ck.checkCompareVarStr(left.Var, op, right.Str) 170 ck.checkCompareVarStr(left.Var, op, right.Str)
308 } 171 }
309} 172}
310 173
311func (ck *MkCondChecker) checkCompareVarStr(varuse *MkVarUse, op string, str string) { 174func (ck *MkCondChecker) checkCompareVarStr(varuse *MkVarUse, op string, str string) {
312 varname := varuse.varname 175 varname := varuse.varname
313 varmods := varuse.modifiers 176 varmods := varuse.modifiers
314 switch len(varmods) { 177 switch len(varmods) {

cvs diff -r1.11 -r1.12 pkgsrc/pkgtools/pkglint/files/Attic/mkcondchecker_test.go (expand / switch to unified diff)

--- pkgsrc/pkgtools/pkglint/files/Attic/mkcondchecker_test.go 2022/07/06 06:14:24 1.11
+++ pkgsrc/pkgtools/pkglint/files/Attic/mkcondchecker_test.go 2022/07/24 20:07:20 1.12
@@ -520,743 +520,26 @@ func (s *Suite) Test_MkCondChecker_check @@ -520,743 +520,26 @@ func (s *Suite) Test_MkCondChecker_check
520 nil...) 520 nil...)
521 test(".if !empty(OPSYS:H:Mok)", 521 test(".if !empty(OPSYS:H:Mok)",
522 nil...) 522 nil...)
523 test(".if !empty(OPSYS:R:Mok)", 523 test(".if !empty(OPSYS:R:Mok)",
524 nil...) 524 nil...)
525 test(".if !empty(OPSYS:tl:Mok)", 525 test(".if !empty(OPSYS:tl:Mok)",
526 nil...) 526 nil...)
527 test(".if !empty(OPSYS:tW:Mok)", 527 test(".if !empty(OPSYS:tW:Mok)",
528 nil...) 528 nil...)
529 test(".if !empty(OPSYS:tW:Mok)", 529 test(".if !empty(OPSYS:tW:Mok)",
530 nil...) 530 nil...)
531} 531}
532 532
533func (s *Suite) Test_MkCondChecker_simplify(c *check.C) { 
534 t := s.Init(c) 
535 
536 t.CreateFileLines("mk/bsd.prefs.mk") 
537 t.Chdir("category/package") 
538 
539 // The Anything type suppresses the warnings from type checking. 
540 // BtUnknown would not work here, see Pkgsrc.VariableType. 
541 btAnything := &BasicType{"Anything", func(cv *VartypeCheck) {}} 
542 
543 // For simplifying the expressions, it is necessary to know whether 
544 // a variable can be undefined. Undefined variables need the 
545 // :U modifier or must be enclosed in double quotes, otherwise 
546 // bmake will complain about a "Malformed conditional". That error 
547 // message is not entirely precise since the expression 
548 // is syntactically valid, it's just the evaluation that fails. 
549 // 
550 // Some variables such as MACHINE_ARCH are in scope from the very 
551 // beginning. 
552 // 
553 // Some variables such as PKGPATH are in scope after bsd.prefs.mk 
554 // has been included. 
555 // 
556 // Some variables such as PREFIX (as of December 2019) are only in 
557 // scope after bsd.pkg.mk has been included. These cannot be used 
558 // in .if conditions at all. 
559 // 
560 // Even when they are in scope, some variables such as PKGREVISION 
561 // or MAKE_JOBS may be undefined. 
562 
563 t.SetUpVarType("IN_SCOPE_DEFINED", btAnything, AlwaysInScope|DefinedIfInScope, 
564 "*.mk: use, use-loadtime") 
565 t.SetUpVarType("IN_SCOPE", btAnything, AlwaysInScope, 
566 "*.mk: use, use-loadtime") 
567 t.SetUpVarType("PREFS_DEFINED", btAnything, DefinedIfInScope, 
568 "*.mk: use, use-loadtime") 
569 t.SetUpVarType("PREFS", btAnything, NoVartypeOptions, 
570 "*.mk: use, use-loadtime") 
571 t.SetUpVarType("LATER_DEFINED", btAnything, DefinedIfInScope, 
572 "*.mk: use") 
573 t.SetUpVarType("LATER", btAnything, NoVartypeOptions, 
574 "*.mk: use") 
575 // UNDEFINED is also used in the following tests, but is obviously 
576 // not defined here. 
577 
578 // prefs: whether to include bsd.prefs.mk before 
579 // before: the directive before the condition is simplified 
580 // after: the directive after the condition is simplified 
581 doTest := func(prefs bool, before, after string, diagnostics ...string) { 
582 if !matches(before, `IN_SCOPE|PREFS|LATER|UNDEFINED`) { 
583 c.Errorf("Condition %q must include one of the above variable names.", before) 
584 } 
585 mklines := t.SetUpFileMkLines("filename.mk", 
586 MkCvsID, 
587 condStr(prefs, ".include \"../../mk/bsd.prefs.mk\"", ""), 
588 before, 
589 ".endif") 
590 
591 action := func(autofix bool) { 
592 mklines.ForEach(func(mkline *MkLine) { 
593 // Sets mklines.Tools.SeenPrefs, which decides whether the :U modifier 
594 // is necessary; see MkLines.checkLine. 
595 mklines.Tools.ParseToolLine(mklines, mkline, false, false) 
596 
597 if mkline.IsDirective() && mkline.Directive() != "endif" { 
598 // TODO: Replace Check with a more 
599 // specific method that does not do the type checks. 
600 NewMkCondChecker(mkline, mklines).Check() 
601 } 
602 }) 
603 
604 if autofix { 
605 afterMklines := LoadMk(t.File("filename.mk"), nil, MustSucceed) 
606 t.CheckEquals(afterMklines.mklines[2].Text, after) 
607 } 
608 } 
609 
610 t.ExpectDiagnosticsAutofix(action, diagnostics...) 
611 } 
612 
613 testBeforePrefs := func(before, after string, diagnostics ...string) { 
614 doTest(false, before, after, diagnostics...) 
615 } 
616 testAfterPrefs := func(before, after string, diagnostics ...string) { 
617 doTest(true, before, after, diagnostics...) 
618 } 
619 testBeforeAndAfterPrefs := func(before, after string, diagnostics ...string) { 
620 doTest(false, before, after, diagnostics...) 
621 doTest(true, before, after, diagnostics...) 
622 } 
623 
624 testBeforeAndAfterPrefs( 
625 ".if ${IN_SCOPE_DEFINED:Mpattern}", 
626 ".if ${IN_SCOPE_DEFINED} == pattern", 
627 
628 "NOTE: filename.mk:3: IN_SCOPE_DEFINED can be "+ 
629 "compared using the simpler \"${IN_SCOPE_DEFINED} == pattern\" "+ 
630 "instead of matching against \":Mpattern\".", 
631 "AUTOFIX: filename.mk:3: Replacing \"${IN_SCOPE_DEFINED:Mpattern}\" "+ 
632 "with \"${IN_SCOPE_DEFINED} == pattern\".") 
633 
634 testBeforeAndAfterPrefs( 
635 ".if ${IN_SCOPE:Mpattern}", 
636 ".if ${IN_SCOPE:U} == pattern", 
637 
638 "NOTE: filename.mk:3: IN_SCOPE can be "+ 
639 "compared using the simpler \"${IN_SCOPE:U} == pattern\" "+ 
640 "instead of matching against \":Mpattern\".", 
641 "AUTOFIX: filename.mk:3: Replacing \"${IN_SCOPE:Mpattern}\" "+ 
642 "with \"${IN_SCOPE:U} == pattern\".") 
643 
644 // Even though PREFS_DEFINED is declared as DefinedIfInScope, 
645 // it is not in scope yet. Therefore it needs the :U modifier. 
646 // The warning that this variable is not yet in scope comes from 
647 // a different part of pkglint. 
648 testBeforePrefs( 
649 ".if ${PREFS_DEFINED:Mpattern}", 
650 ".if ${PREFS_DEFINED:U} == pattern", 
651 
652 "NOTE: filename.mk:3: PREFS_DEFINED can be "+ 
653 "compared using the simpler \"${PREFS_DEFINED:U} == pattern\" "+ 
654 "instead of matching against \":Mpattern\".", 
655 "WARN: filename.mk:3: To use PREFS_DEFINED at load time, "+ 
656 ".include \"../../mk/bsd.prefs.mk\" first.", 
657 "AUTOFIX: filename.mk:3: Replacing \"${PREFS_DEFINED:Mpattern}\" "+ 
658 "with \"${PREFS_DEFINED:U} == pattern\".") 
659 
660 testAfterPrefs( 
661 ".if ${PREFS_DEFINED:Mpattern}", 
662 ".if ${PREFS_DEFINED} == pattern", 
663 
664 "NOTE: filename.mk:3: PREFS_DEFINED can be "+ 
665 "compared using the simpler \"${PREFS_DEFINED} == pattern\" "+ 
666 "instead of matching against \":Mpattern\".", 
667 "AUTOFIX: filename.mk:3: Replacing \"${PREFS_DEFINED:Mpattern}\" "+ 
668 "with \"${PREFS_DEFINED} == pattern\".") 
669 
670 testBeforePrefs( 
671 ".if ${PREFS:Mpattern}", 
672 ".if ${PREFS:U} == pattern", 
673 
674 "NOTE: filename.mk:3: PREFS can be "+ 
675 "compared using the simpler \"${PREFS:U} == pattern\" "+ 
676 "instead of matching against \":Mpattern\".", 
677 "WARN: filename.mk:3: To use PREFS at load time, "+ 
678 ".include \"../../mk/bsd.prefs.mk\" first.", 
679 "AUTOFIX: filename.mk:3: Replacing \"${PREFS:Mpattern}\" "+ 
680 "with \"${PREFS:U} == pattern\".") 
681 
682 // Preferences that may be undefined always need the :U modifier, 
683 // even when they are in scope. 
684 testAfterPrefs( 
685 ".if ${PREFS:Mpattern}", 
686 ".if ${PREFS:U} == pattern", 
687 
688 "NOTE: filename.mk:3: PREFS can be "+ 
689 "compared using the simpler \"${PREFS:U} == pattern\" "+ 
690 "instead of matching against \":Mpattern\".", 
691 "AUTOFIX: filename.mk:3: Replacing \"${PREFS:Mpattern}\" "+ 
692 "with \"${PREFS:U} == pattern\".") 
693 
694 // Variables that are defined later always need the :U modifier. 
695 // It is probably a mistake to use them in conditions at all. 
696 testBeforeAndAfterPrefs( 
697 ".if ${LATER_DEFINED:Mpattern}", 
698 ".if ${LATER_DEFINED:U} == pattern", 
699 
700 "NOTE: filename.mk:3: LATER_DEFINED can be "+ 
701 "compared using the simpler \"${LATER_DEFINED:U} == pattern\" "+ 
702 "instead of matching against \":Mpattern\".", 
703 "WARN: filename.mk:3: "+ 
704 "LATER_DEFINED should not be used at load time in any file.", 
705 "AUTOFIX: filename.mk:3: Replacing \"${LATER_DEFINED:Mpattern}\" "+ 
706 "with \"${LATER_DEFINED:U} == pattern\".") 
707 
708 // Variables that are defined later always need the :U modifier. 
709 // It is probably a mistake to use them in conditions at all. 
710 testBeforeAndAfterPrefs( 
711 ".if ${LATER:Mpattern}", 
712 ".if ${LATER:U} == pattern", 
713 
714 "NOTE: filename.mk:3: LATER can be "+ 
715 "compared using the simpler \"${LATER:U} == pattern\" "+ 
716 "instead of matching against \":Mpattern\".", 
717 "WARN: filename.mk:3: "+ 
718 "LATER should not be used at load time in any file.", 
719 "AUTOFIX: filename.mk:3: Replacing \"${LATER:Mpattern}\" "+ 
720 "with \"${LATER:U} == pattern\".") 
721 
722 testBeforeAndAfterPrefs( 
723 ".if ${UNDEFINED:Mpattern}", 
724 ".if ${UNDEFINED:Mpattern}", 
725 
726 "WARN: filename.mk:3: UNDEFINED is used but not defined.") 
727 
728 // When the pattern contains placeholders, it cannot be converted to == or !=. 
729 testAfterPrefs( 
730 ".if ${PREFS_DEFINED:Mpa*n}", 
731 ".if ${PREFS_DEFINED:Mpa*n}", 
732 
733 nil...) 
734 
735 // When deciding whether to replace the expression, only the 
736 // last modifier is inspected. All the others are copied. 
737 testAfterPrefs( 
738 ".if ${PREFS_DEFINED:tl:Mpattern}", 
739 ".if ${PREFS_DEFINED:tl} == pattern", 
740 
741 "NOTE: filename.mk:3: PREFS_DEFINED can be "+ 
742 "compared using the simpler \"${PREFS_DEFINED:tl} == pattern\" "+ 
743 "instead of matching against \":Mpattern\".", 
744 "AUTOFIX: filename.mk:3: Replacing \"${PREFS_DEFINED:tl:Mpattern}\" "+ 
745 "with \"${PREFS_DEFINED:tl} == pattern\".") 
746 
747 // Negated pattern matches are supported as well, 
748 // as long as the variable is guaranteed to be nonempty. 
749 // TODO: Actually implement this. 
750 // As of December 2019, IsNonemptyIfDefined is not used anywhere. 
751 testAfterPrefs( 
752 ".if ${PREFS_DEFINED:Npattern}", 
753 ".if ${PREFS_DEFINED} != pattern", 
754 
755 "NOTE: filename.mk:3: PREFS_DEFINED can be "+ 
756 "compared using the simpler \"${PREFS_DEFINED} != pattern\" "+ 
757 "instead of matching against \":Npattern\".", 
758 "AUTOFIX: filename.mk:3: Replacing \"${PREFS_DEFINED:Npattern}\" "+ 
759 "with \"${PREFS_DEFINED} != pattern\".") 
760 
761 // ${PREFS_DEFINED:None:Ntwo} is a short variant of 
762 // ${PREFS_DEFINED} != "one" && ${PREFS_DEFINED} != "two". 
763 // Applying the transformation would make the condition longer 
764 // than before, therefore nothing can be simplified here, 
765 // even though all patterns are exact matches. 
766 testAfterPrefs( 
767 ".if ${PREFS_DEFINED:None:Ntwo}", 
768 ".if ${PREFS_DEFINED:None:Ntwo}", 
769 
770 nil...) 
771 
772 // Note: this combination doesn't make sense since the patterns 
773 // "one" and "two" don't overlap. 
774 // Nevertheless it is possible and valid to simplify the condition. 
775 testAfterPrefs( 
776 ".if ${PREFS_DEFINED:Mone:Mtwo}", 
777 ".if ${PREFS_DEFINED:Mone} == two", 
778 
779 "NOTE: filename.mk:3: PREFS_DEFINED can be "+ 
780 "compared using the simpler \"${PREFS_DEFINED:Mone} == two\" "+ 
781 "instead of matching against \":Mtwo\".", 
782 "AUTOFIX: filename.mk:3: Replacing \"${PREFS_DEFINED:Mone:Mtwo}\" "+ 
783 "with \"${PREFS_DEFINED:Mone} == two\".") 
784 
785 // There is no ! before the empty, which is easy to miss. 
786 // Because of this missing negation, the comparison operator is !=. 
787 testAfterPrefs( 
788 ".if empty(PREFS_DEFINED:Mpattern)", 
789 ".if ${PREFS_DEFINED} != pattern", 
790 
791 "NOTE: filename.mk:3: PREFS_DEFINED can be "+ 
792 "compared using the simpler \"${PREFS_DEFINED} != pattern\" "+ 
793 "instead of matching against \":Mpattern\".", 
794 "AUTOFIX: filename.mk:3: Replacing \"empty(PREFS_DEFINED:Mpattern)\" "+ 
795 "with \"${PREFS_DEFINED} != pattern\".") 
796 
797 testAfterPrefs( 
798 ".if !!empty(PREFS_DEFINED:Mpattern)", 
799 // TODO: The ! and == could be combined into a !=. 
800 // Luckily the !! pattern doesn't occur in practice. 
801 ".if !${PREFS_DEFINED} == pattern", 
802 
803 // TODO: When taking all the ! into account, this is actually a 
804 // test for emptiness, therefore the diagnostics should suggest 
805 // the != operator instead of ==. 
806 "NOTE: filename.mk:3: PREFS_DEFINED can be "+ 
807 "compared using the simpler \"${PREFS_DEFINED} == pattern\" "+ 
808 "instead of matching against \":Mpattern\".", 
809 "AUTOFIX: filename.mk:3: Replacing \"!empty(PREFS_DEFINED:Mpattern)\" "+ 
810 "with \"${PREFS_DEFINED} == pattern\".") 
811 
812 // Simplifying the condition also works in complex expressions. 
813 testAfterPrefs(".if empty(PREFS_DEFINED:Mpattern) || 0", 
814 ".if ${PREFS_DEFINED} != pattern || 0", 
815 
816 "NOTE: filename.mk:3: PREFS_DEFINED can be "+ 
817 "compared using the simpler \"${PREFS_DEFINED} != pattern\" "+ 
818 "instead of matching against \":Mpattern\".", 
819 "AUTOFIX: filename.mk:3: Replacing \"empty(PREFS_DEFINED:Mpattern)\" "+ 
820 "with \"${PREFS_DEFINED} != pattern\".") 
821 
822 // No note in this case since there is no implicit !empty around the varUse. 
823 // There is no obvious way of writing this expression in a simpler form. 
824 testAfterPrefs( 
825 ".if ${PREFS_DEFINED:Mpattern} != ${OTHER}", 
826 ".if ${PREFS_DEFINED:Mpattern} != ${OTHER}", 
827 
828 "WARN: filename.mk:3: OTHER is used but not defined.") 
829 
830 // The condition is also simplified if it doesn't use the !empty 
831 // form but the implicit conversion to boolean. 
832 testAfterPrefs( 
833 ".if ${PREFS_DEFINED:Mpattern}", 
834 ".if ${PREFS_DEFINED} == pattern", 
835 
836 "NOTE: filename.mk:3: PREFS_DEFINED can be "+ 
837 "compared using the simpler \"${PREFS_DEFINED} == pattern\" "+ 
838 "instead of matching against \":Mpattern\".", 
839 "AUTOFIX: filename.mk:3: Replacing \"${PREFS_DEFINED:Mpattern}\" "+ 
840 "with \"${PREFS_DEFINED} == pattern\".") 
841 
842 // A single negation outside the implicit conversion is taken into 
843 // account when simplifying the condition. 
844 testAfterPrefs( 
845 ".if !${PREFS_DEFINED:Mpattern}", 
846 ".if ${PREFS_DEFINED} != pattern", 
847 
848 "NOTE: filename.mk:3: PREFS_DEFINED can be "+ 
849 "compared using the simpler \"${PREFS_DEFINED} != pattern\" "+ 
850 "instead of matching against \":Mpattern\".", 
851 "AUTOFIX: filename.mk:3: Replacing \"!${PREFS_DEFINED:Mpattern}\" "+ 
852 "with \"${PREFS_DEFINED} != pattern\".") 
853 
854 // TODO: Merge the double negation into the comparison operator. 
855 testAfterPrefs( 
856 ".if !!${PREFS_DEFINED:Mpattern}", 
857 ".if !${PREFS_DEFINED} != pattern", 
858 
859 "NOTE: filename.mk:3: PREFS_DEFINED can be "+ 
860 "compared using the simpler \"${PREFS_DEFINED} != pattern\" "+ 
861 "instead of matching against \":Mpattern\".", 
862 "AUTOFIX: filename.mk:3: Replacing \"!${PREFS_DEFINED:Mpattern}\" "+ 
863 "with \"${PREFS_DEFINED} != pattern\".") 
864 
865 // This pattern with spaces doesn't make sense at all in the :M 
866 // modifier since it can never match. 
867 // Or can it, if the PKGPATH contains quotes? 
868 // TODO: How exactly does bmake apply the matching here, 
869 // are both values unquoted first? Probably not, but who knows. 
870 testBeforeAndAfterPrefs( 
871 ".if ${IN_SCOPE_DEFINED:Mpattern with spaces}", 
872 ".if ${IN_SCOPE_DEFINED:Mpattern with spaces}", 
873 
874 nil...) 
875 // TODO: ".if ${PKGPATH} == \"pattern with spaces\"") 
876 
877 testBeforeAndAfterPrefs( 
878 ".if ${IN_SCOPE_DEFINED:M'pattern with spaces'}", 
879 ".if ${IN_SCOPE_DEFINED:M'pattern with spaces'}", 
880 
881 nil...) 
882 // TODO: ".if ${PKGPATH} == 'pattern with spaces'") 
883 
884 testBeforeAndAfterPrefs( 
885 ".if ${IN_SCOPE_DEFINED:M&&}", 
886 ".if ${IN_SCOPE_DEFINED:M&&}", 
887 
888 nil...) 
889 // TODO: ".if ${PKGPATH} == '&&'") 
890 
891 // The :N modifier involves another negation and is therefore more 
892 // difficult to understand. That's even more reason to use the 
893 // well-known == and != comparison operators instead. 
894 // 
895 // If PKGPATH is "", the condition is false. 
896 // If PKGPATH is "negative-pattern", the condition is false. 
897 // In all other cases, the condition is true. 
898 // 
899 // Therefore this condition cannot simply be transformed into 
900 // ${PKGPATH} != negative-pattern, since that would produce a 
901 // different result in the case where PKGPATH is empty. 
902 // 
903 // For system-provided variables that are guaranteed to be non-empty, 
904 // such as OPSYS or PKGPATH, this replacement is valid. 
905 // These variables are only guaranteed to be defined after bsd.prefs.mk 
906 // has been included, like everywhere else. 
907 // 
908 // TODO: This is where NonemptyIfDefined comes into play. 
909 testAfterPrefs( 
910 ".if ${PREFS_DEFINED:Nnegative-pattern}", 
911 ".if ${PREFS_DEFINED} != negative-pattern", 
912 
913 "NOTE: filename.mk:3: PREFS_DEFINED can be "+ 
914 "compared using the simpler \"${PREFS_DEFINED} != negative-pattern\" "+ 
915 "instead of matching against \":Nnegative-pattern\".", 
916 "AUTOFIX: filename.mk:3: Replacing \"${PREFS_DEFINED:Nnegative-pattern}\" "+ 
917 "with \"${PREFS_DEFINED} != negative-pattern\".") 
918 
919 // Since UNDEFINED is not a well-known variable that is 
920 // guaranteed to be non-empty (see the previous example), it is not 
921 // transformed at all. 
922 testBeforePrefs( 
923 ".if ${UNDEFINED:Nnegative-pattern}", 
924 ".if ${UNDEFINED:Nnegative-pattern}", 
925 
926 "WARN: filename.mk:3: UNDEFINED is used but not defined.") 
927 
928 testAfterPrefs( 
929 ".if ${UNDEFINED:Nnegative-pattern}", 
930 ".if ${UNDEFINED:Nnegative-pattern}", 
931 
932 "WARN: filename.mk:3: UNDEFINED is used but not defined.") 
933 
934 // A complex condition may contain several simple conditions 
935 // that are each simplified independently, in the same go. 
936 testAfterPrefs( 
937 ".if ${PREFS_DEFINED:Mpath1} || ${PREFS_DEFINED:Mpath2}", 
938 ".if ${PREFS_DEFINED} == path1 || ${PREFS_DEFINED} == path2", 
939 
940 "NOTE: filename.mk:3: PREFS_DEFINED can be "+ 
941 "compared using the simpler \"${PREFS_DEFINED} == path1\" "+ 
942 "instead of matching against \":Mpath1\".", 
943 "NOTE: filename.mk:3: PREFS_DEFINED can be "+ 
944 "compared using the simpler \"${PREFS_DEFINED} == path2\" "+ 
945 "instead of matching against \":Mpath2\".", 
946 "AUTOFIX: filename.mk:3: Replacing \"${PREFS_DEFINED:Mpath1}\" "+ 
947 "with \"${PREFS_DEFINED} == path1\".", 
948 "AUTOFIX: filename.mk:3: Replacing \"${PREFS_DEFINED:Mpath2}\" "+ 
949 "with \"${PREFS_DEFINED} == path2\".") 
950 
951 // Removing redundant parentheses may be useful one day but is 
952 // not urgent. 
953 // Simplifying the inner expression keeps all parentheses as-is. 
954 testAfterPrefs( 
955 ".if (((((${PREFS_DEFINED:Mpath})))))", 
956 ".if (((((${PREFS_DEFINED} == path)))))", 
957 
958 "NOTE: filename.mk:3: PREFS_DEFINED can be "+ 
959 "compared using the simpler \"${PREFS_DEFINED} == path\" "+ 
960 "instead of matching against \":Mpath\".", 
961 "AUTOFIX: filename.mk:3: Replacing \"${PREFS_DEFINED:Mpath}\" "+ 
962 "with \"${PREFS_DEFINED} == path\".") 
963 
964 // Several modifiers like :S and :C may change the variable value. 
965 // Whether the condition can be simplified or not only depends on the 
966 // last modifier in the chain. 
967 testAfterPrefs( 
968 ".if !empty(PREFS_DEFINED:S,NetBSD,ok,:Mok)", 
969 ".if ${PREFS_DEFINED:S,NetBSD,ok,} == ok", 
970 
971 "NOTE: filename.mk:3: PREFS_DEFINED can be "+ 
972 "compared using the simpler \"${PREFS_DEFINED:S,NetBSD,ok,} == ok\" "+ 
973 "instead of matching against \":Mok\".", 
974 "AUTOFIX: filename.mk:3: Replacing \"!empty(PREFS_DEFINED:S,NetBSD,ok,:Mok)\" "+ 
975 "with \"${PREFS_DEFINED:S,NetBSD,ok,} == ok\".") 
976 
977 testAfterPrefs( 
978 ".if empty(PREFS_DEFINED:tl:Msunos)", 
979 ".if ${PREFS_DEFINED:tl} != sunos", 
980 
981 "NOTE: filename.mk:3: PREFS_DEFINED can be "+ 
982 "compared using the simpler \"${PREFS_DEFINED:tl} != sunos\" "+ 
983 "instead of matching against \":Msunos\".", 
984 "AUTOFIX: filename.mk:3: Replacing \"empty(PREFS_DEFINED:tl:Msunos)\" "+ 
985 "with \"${PREFS_DEFINED:tl} != sunos\".") 
986 
987 // The condition can only be simplified if the :M or :N modifier 
988 // appears at the end of the chain. 
989 testAfterPrefs( 
990 ".if !empty(PREFS_DEFINED:O:MUnknown:S,a,b,)", 
991 ".if !empty(PREFS_DEFINED:O:MUnknown:S,a,b,)", 
992 
993 nil...) 
994 
995 // The dot is just an ordinary character in a pattern. 
996 // In comparisons, an unquoted 1.2 is interpreted as a number though. 
997 testAfterPrefs( 
998 ".if !empty(PREFS_DEFINED:Mpackage1.2)", 
999 ".if ${PREFS_DEFINED} == package1.2", 
1000 
1001 "NOTE: filename.mk:3: PREFS_DEFINED can be "+ 
1002 "compared using the simpler \"${PREFS_DEFINED} == package1.2\" "+ 
1003 "instead of matching against \":Mpackage1.2\".", 
1004 "AUTOFIX: filename.mk:3: Replacing \"!empty(PREFS_DEFINED:Mpackage1.2)\" "+ 
1005 "with \"${PREFS_DEFINED} == package1.2\".") 
1006 
1007 // Numbers must be enclosed in quotes, otherwise they are compared 
1008 // as numbers, not as strings. 
1009 // The :M and :N modifiers always compare strings. 
1010 testAfterPrefs( 
1011 ".if empty(PREFS:U:M64)", 
1012 ".if ${PREFS:U} != \"64\"", 
1013 
1014 "NOTE: filename.mk:3: PREFS can be "+ 
1015 "compared using the simpler \"${PREFS:U} != \"64\"\" "+ 
1016 "instead of matching against \":M64\".", 
1017 "AUTOFIX: filename.mk:3: Replacing \"empty(PREFS:U:M64)\" "+ 
1018 "with \"${PREFS:U} != \\\"64\\\"\".") 
1019 
1020 // Fractional numbers must also be enclosed in quotes. 
1021 testAfterPrefs( 
1022 ".if empty(PREFS:U:M19.12)", 
1023 ".if ${PREFS:U} != \"19.12\"", 
1024 
1025 "NOTE: filename.mk:3: PREFS can be "+ 
1026 "compared using the simpler \"${PREFS:U} != \"19.12\"\" "+ 
1027 "instead of matching against \":M19.12\".", 
1028 "AUTOFIX: filename.mk:3: Replacing \"empty(PREFS:U:M19.12)\" "+ 
1029 "with \"${PREFS:U} != \\\"19.12\\\"\".") 
1030 
1031 testAfterPrefs( 
1032 ".if !empty(LATER:Npattern)", 
1033 ".if !empty(LATER:Npattern)", 
1034 
1035 // No diagnostics about the :N modifier yet, 
1036 // see MkCondChecker.simplify.replace. 
1037 "WARN: filename.mk:3: LATER should not be used "+ 
1038 "at load time in any file.") 
1039 
1040 // TODO: Add a note that the :U is unnecessary, and explain why. 
1041 testAfterPrefs( 
1042 ".if ${PREFS_DEFINED:U:Mpattern}", 
1043 ".if ${PREFS_DEFINED:U} == pattern", 
1044 
1045 "NOTE: filename.mk:3: PREFS_DEFINED can be "+ 
1046 "compared using the simpler \"${PREFS_DEFINED:U} == pattern\" "+ 
1047 "instead of matching against \":Mpattern\".", 
1048 "AUTOFIX: filename.mk:3: "+ 
1049 "Replacing \"${PREFS_DEFINED:U:Mpattern}\" "+ 
1050 "with \"${PREFS_DEFINED:U} == pattern\".") 
1051 
1052 // Conditions without any modifiers cannot be simplified 
1053 // and are therefore skipped. 
1054 testBeforeAndAfterPrefs( 
1055 ".if ${IN_SCOPE_DEFINED}", 
1056 ".if ${IN_SCOPE_DEFINED}", 
1057 
1058 nil...) 
1059 
1060 // Special characters must be enclosed in quotes when they are 
1061 // used in string literals. 
1062 // As of December 2019, strings with special characters are not yet 
1063 // replaced automatically, see mkCondLiteralChars. 
1064 // TODO: Add tests for all characters that are special in string literals or patterns. 
1065 // TODO: Then, extend the set of characters for which the expressions are simplified. 
1066 testBeforePrefs( 
1067 ".if ${PREFS_DEFINED:M&&}", 
1068 ".if ${PREFS_DEFINED:M&&}", 
1069 
1070 "WARN: filename.mk:3: To use PREFS_DEFINED at load time, .include \"../../mk/bsd.prefs.mk\" first.") 
1071 testAfterPrefs( 
1072 ".if ${PREFS_DEFINED:M&&}", 
1073 ".if ${PREFS_DEFINED:M&&}", 
1074 
1075 nil...) 
1076 
1077 testBeforePrefs( 
1078 ".if ${PREFS:M&&}", 
1079 ".if ${PREFS:M&&}", 
1080 
1081 // TODO: Warn that the :U is missing. 
1082 "WARN: filename.mk:3: To use PREFS at load time, .include \"../../mk/bsd.prefs.mk\" first.") 
1083 testAfterPrefs( 
1084 ".if ${PREFS:M&&}", 
1085 ".if ${PREFS:M&&}", 
1086 
1087 // TODO: Warn that the :U is missing. 
1088 nil...) 
1089 
1090 // The + is contained in both mkCondStringLiteralUnquoted and 
1091 // mkCondModifierPatternLiteral, therefore it is copied verbatim. 
1092 testAfterPrefs( 
1093 ".if ${PREFS_DEFINED:Mcategory/gtk+}", 
1094 ".if ${PREFS_DEFINED} == category/gtk+", 
1095 
1096 "NOTE: filename.mk:3: PREFS_DEFINED can be "+ 
1097 "compared using the simpler \"${PREFS_DEFINED} == category/gtk+\" "+ 
1098 "instead of matching against \":Mcategory/gtk+\".", 
1099 "AUTOFIX: filename.mk:3: "+ 
1100 "Replacing \"${PREFS_DEFINED:Mcategory/gtk+}\" "+ 
1101 "with \"${PREFS_DEFINED} == category/gtk+\".") 
1102 
1103 // The characters <=> may be used unescaped in :M and :N patterns 
1104 // but not in .if conditions. There they must be enclosed in quotes. 
1105 testBeforePrefs( 
1106 ".if ${PREFS_DEFINED:M<=>}", 
1107 ".if ${PREFS_DEFINED:U} == \"<=>\"", 
1108 
1109 "NOTE: filename.mk:3: PREFS_DEFINED can be "+ 
1110 "compared using the simpler \"${PREFS_DEFINED:U} == \"<=>\"\" "+ 
1111 "instead of matching against \":M<=>\".", 
1112 "WARN: filename.mk:3: To use PREFS_DEFINED at load time, "+ 
1113 ".include \"../../mk/bsd.prefs.mk\" first.", 
1114 "AUTOFIX: filename.mk:3: "+ 
1115 "Replacing \"${PREFS_DEFINED:M<=>}\" "+ 
1116 "with \"${PREFS_DEFINED:U} == \\\"<=>\\\"\".") 
1117 testAfterPrefs( 
1118 ".if ${PREFS_DEFINED:M<=>}", 
1119 ".if ${PREFS_DEFINED} == \"<=>\"", 
1120 
1121 "NOTE: filename.mk:3: PREFS_DEFINED can be "+ 
1122 "compared using the simpler \"${PREFS_DEFINED} == \"<=>\"\" "+ 
1123 "instead of matching against \":M<=>\".", 
1124 "AUTOFIX: filename.mk:3: "+ 
1125 "Replacing \"${PREFS_DEFINED:M<=>}\" "+ 
1126 "with \"${PREFS_DEFINED} == \\\"<=>\\\"\".") 
1127 
1128 // If pkglint replaces this particular pattern, the resulting string 
1129 // literal must be escaped properly. 
1130 testBeforeAndAfterPrefs( 
1131 ".if ${IN_SCOPE_DEFINED:M\"}", 
1132 ".if ${IN_SCOPE_DEFINED:M\"}", 
1133 
1134 nil...) 
1135 
1136 testBeforeAndAfterPrefs( 
1137 ".if !empty(IN_SCOPE_DEFINED:M)", 
1138 ".if ${IN_SCOPE_DEFINED} == \"\"", 
1139 
1140 "NOTE: filename.mk:3: IN_SCOPE_DEFINED can be "+ 
1141 "compared using the simpler "+"\"${IN_SCOPE_DEFINED} == \"\"\" "+ 
1142 "instead of matching against \":M\".", 
1143 "AUTOFIX: filename.mk:3: "+ 
1144 "Replacing \"!empty(IN_SCOPE_DEFINED:M)\" "+ 
1145 "with \"${IN_SCOPE_DEFINED} == \\\"\\\"\".", 
1146 ) 
1147 
1148 testBeforeAndAfterPrefs( 
1149 ".if !empty(IN_SCOPE_DEFINED:M*.c)", 
1150 ".if ${IN_SCOPE_DEFINED:M*.c}", 
1151 
1152 "NOTE: filename.mk:3: \"!empty(IN_SCOPE_DEFINED:M*.c)\" "+ 
1153 "can be simplified to \"${IN_SCOPE_DEFINED:M*.c}\".", 
1154 "AUTOFIX: filename.mk:3: "+ 
1155 "Replacing \"!empty(IN_SCOPE_DEFINED:M*.c)\" "+ 
1156 "with \"${IN_SCOPE_DEFINED:M*.c}\".") 
1157 
1158 testBeforeAndAfterPrefs( 
1159 ".if empty(IN_SCOPE_DEFINED:M*.c)", 
1160 ".if !${IN_SCOPE_DEFINED:M*.c}", 
1161 
1162 "NOTE: filename.mk:3: \"empty(IN_SCOPE_DEFINED:M*.c)\" "+ 
1163 "can be simplified to \"!${IN_SCOPE_DEFINED:M*.c}\".", 
1164 "AUTOFIX: filename.mk:3: "+ 
1165 "Replacing \"empty(IN_SCOPE_DEFINED:M*.c)\" "+ 
1166 "with \"!${IN_SCOPE_DEFINED:M*.c}\".") 
1167} 
1168 
1169func (s *Suite) Test_MkCondChecker_simplify__defined_in_same_file(c *check.C) { 
1170 t := s.Init(c) 
1171 
1172 t.SetUpPackage("category/package") 
1173 t.Chdir("category/package") 
1174 t.FinishSetUp() 
1175 
1176 doTest := func(before string) { 
1177 mklines := t.SetUpFileMkLines("filename.mk", 
1178 MkCvsID, 
1179 "OK=\t\tok", 
1180 "OK_DIR=\t\tok", // See Pkgsrc.guessVariableType. 
1181 before, 
1182 "LATER=\t\tlater", 
1183 "LATER_DIR=\tlater", // See Pkgsrc.guessVariableType. 
1184 ".endif", 
1185 "USED=\t\t${OK} ${LATER} ${OK_DIR} ${LATER_DIR} ${USED}") 
1186 
1187 // The high-level call MkLines.Check is used here to 
1188 // get MkLines.Tools.SeenPrefs correct, which decides 
1189 // whether the :U modifier is necessary. 
1190 mklines.Check() 
1191 } 
1192 
1193 // before: the directive before the condition is simplified 
1194 // after: the directive after the condition is simplified 
1195 test := func(before, after string, diagnostics ...string) { 
1196 
1197 t.ExpectDiagnosticsAutofix( 
1198 func(bool) { doTest(before) }, 
1199 diagnostics...) 
1200 
1201 // TODO: Move this assertion above the assertion about the diagnostics. 
1202 afterMklines := LoadMk(t.File("filename.mk"), nil, MustSucceed) 
1203 t.CheckEquals(afterMklines.mklines[3].Text, after) 
1204 } 
1205 
1206 // For variables with completely unknown names, the type is nil 
1207 // and the complete check is skipped. 
1208 test( 
1209 ".if ${OK:Mpattern}", 
1210 ".if ${OK:Mpattern}", 
1211 
1212 nil...) 
1213 
1214 // For variables with completely unknown names, the type is nil 
1215 // and the complete check is skipped. 
1216 test( 
1217 ".if ${LATER:Mpattern}", 
1218 ".if ${LATER:Mpattern}", 
1219 
1220 nil...) 
1221 
1222 // OK_DIR is defined earlier than the .if condition, 
1223 // which is the correct order. 
1224 test( 
1225 ".if ${OK_DIR:Mpattern}", 
1226 ".if ${OK_DIR} == pattern", 
1227 
1228 "NOTE: filename.mk:4: OK_DIR can be "+ 
1229 "compared using the simpler \"${OK_DIR} == pattern\" "+ 
1230 "instead of matching against \":Mpattern\".", 
1231 "AUTOFIX: filename.mk:4: "+ 
1232 "Replacing \"${OK_DIR:Mpattern}\" "+ 
1233 "with \"${OK_DIR} == pattern\".") 
1234 
1235 // LATER_DIR is defined later than the .if condition, 
1236 // therefore at the time of the .if statement, it is still empty. 
1237 test( 
1238 ".if ${LATER_DIR:Mpattern}", 
1239 ".if ${LATER_DIR:U} == pattern", 
1240 
1241 // TODO: Warn that LATER_DIR is used before it is defined. 
1242 "NOTE: filename.mk:4: LATER_DIR can be "+ 
1243 "compared using the simpler \"${LATER_DIR:U} == pattern\" "+ 
1244 "instead of matching against \":Mpattern\".", 
1245 "AUTOFIX: filename.mk:4: "+ 
1246 "Replacing \"${LATER_DIR:Mpattern}\" "+ 
1247 "with \"${LATER_DIR:U} == pattern\".") 
1248} 
1249 
1250func (s *Suite) Test_MkCondChecker_checkCompare(c *check.C) { 533func (s *Suite) Test_MkCondChecker_checkCompare(c *check.C) {
1251 t := s.Init(c) 534 t := s.Init(c)
1252 535
1253 t.SetUpVartypes() 536 t.SetUpVartypes()
1254 537
1255 test := func(cond string, output ...string) { 538 test := func(cond string, output ...string) {
1256 mklines := t.NewMkLines("filename.mk", 539 mklines := t.NewMkLines("filename.mk",
1257 cond) 540 cond)
1258 mklines.ForEach(func(mkline *MkLine) { 541 mklines.ForEach(func(mkline *MkLine) {
1259 NewMkCondChecker(mkline, mklines).Check() 542 NewMkCondChecker(mkline, mklines).Check()
1260 }) 543 })
1261 t.CheckOutput(output) 544 t.CheckOutput(output)
1262 } 545 }

File Added: pkgsrc/pkgtools/pkglint/files/Attic/mkcondsimplifier.go
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()
}

File Added: pkgsrc/pkgtools/pkglint/files/Attic/mkcondsimplifier_test.go
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.")
}

cvs diff -r1.44 -r1.45 pkgsrc/pkgtools/pkglint/files/Attic/mkparser.go (expand / switch to unified diff)

--- pkgsrc/pkgtools/pkglint/files/Attic/mkparser.go 2022/07/09 06:40:55 1.44
+++ pkgsrc/pkgtools/pkglint/files/Attic/mkparser.go 2022/07/24 20:07:20 1.45
@@ -1,21 +1,21 @@ @@ -1,21 +1,21 @@
1package pkglint 1package pkglint
2 2
3import ( 3import (
4 "netbsd.org/pkglint/textproc" 4 "netbsd.org/pkglint/textproc"
5 "strings" 5 "strings"
6) 6)
7 7
8// MkParser wraps a Parser and provides methods for parsing 8// MkParser wraps a MkLexer and provides methods for parsing
9// things related to Makefiles. 9// things related to Makefiles.
10type MkParser struct { 10type MkParser struct {
11 diag Diagnoser 11 diag Diagnoser
12 mklex *MkLexer 12 mklex *MkLexer
13 lexer *textproc.Lexer 13 lexer *textproc.Lexer
14 EmitWarnings bool 14 EmitWarnings bool
15} 15}
16 16
17func (p *MkParser) EOF() bool { 17func (p *MkParser) EOF() bool {
18 return p.lexer.EOF() 18 return p.lexer.EOF()
19} 19}
20 20
21func (p *MkParser) Rest() string { 21func (p *MkParser) Rest() string {

cvs diff -r1.26 -r1.27 pkgsrc/pkgtools/pkglint/files/Attic/mktypes.go (expand / switch to unified diff)

--- pkgsrc/pkgtools/pkglint/files/Attic/mktypes.go 2020/07/01 13:17:41 1.26
+++ pkgsrc/pkgtools/pkglint/files/Attic/mktypes.go 2022/07/24 20:07:20 1.27
@@ -120,30 +120,32 @@ func (MkVarUseModifier) EvalSubst(s stri @@ -120,30 +120,32 @@ func (MkVarUseModifier) EvalSubst(s stri
120 done := false 120 done := false
121 gflag := contains(flags, "g") 121 gflag := contains(flags, "g")
122 return true, replaceAllFunc(s, re, func(match string) string { 122 return true, replaceAllFunc(s, re, func(match string) string {
123 if gflag || !done { 123 if gflag || !done {
124 done = !gflag 124 done = !gflag
125 return to 125 return to
126 } 126 }
127 return match 127 return match
128 }) 128 })
129} 129}
130 130
131// MatchMatch tries to match the modifier to a :M or a :N pattern matching. 131// MatchMatch tries to match the modifier to a :M or a :N pattern matching.
132// Examples: 132// Examples:
133// :Mpattern => true, true, "pattern", true 133// modifier => ok positive pattern exact
134// :M* => true, true, "*", false 134// ------------------------------------------------
135// :M${VAR} => true, true, "${VAR}", false 135// :Mpattern => true, true, "pattern", true
136// :Npattern => true, false, "pattern", true 136// :M* => true, true, "*", false
 137// :M${VAR} => true, true, "${VAR}", false
 138// :Npattern => true, false, "pattern", true
137// :X => false 139// :X => false
138func (m MkVarUseModifier) MatchMatch() (ok bool, positive bool, pattern string, exact bool) { 140func (m MkVarUseModifier) MatchMatch() (ok bool, positive bool, pattern string, exact bool) {
139 if m.HasPrefix("M") || m.HasPrefix("N") { 141 if m.HasPrefix("M") || m.HasPrefix("N") {
140 str := m.String() 142 str := m.String()
141 // See devel/bmake/files/str.c:^Str_Match 143 // See devel/bmake/files/str.c:^Str_Match
142 exact := !strings.ContainsAny(str[1:], "*?[\\$") 144 exact := !strings.ContainsAny(str[1:], "*?[\\$")
143 return true, str[0] == 'M', str[1:], exact 145 return true, str[0] == 'M', str[1:], exact
144 } 146 }
145 return false, false, "", false 147 return false, false, "", false
146} 148}
147 149
148func (m MkVarUseModifier) IsToLower() bool { return m == "tl" } 150func (m MkVarUseModifier) IsToLower() bool { return m == "tl" }
149 151

cvs diff -r1.90 -r1.91 pkgsrc/pkgtools/pkglint/files/Attic/package_test.go (expand / switch to unified diff)

--- pkgsrc/pkgtools/pkglint/files/Attic/package_test.go 2022/03/11 00:33:12 1.90
+++ pkgsrc/pkgtools/pkglint/files/Attic/package_test.go 2022/07/24 20:07:20 1.91
@@ -1331,26 +1331,39 @@ func (s *Suite) Test_Package_check__patc @@ -1331,26 +1331,39 @@ func (s *Suite) Test_Package_check__patc
1331 1331
1332 t.SetUpPackage("category/package") 1332 t.SetUpPackage("category/package")
1333 t.CreateFileLines("category/package/patches/Makefile", 1333 t.CreateFileLines("category/package/patches/Makefile",
1334 "This file may contain anything.") 1334 "This file may contain anything.")
1335 1335
1336 t.Main("category/package") 1336 t.Main("category/package")
1337 1337
1338 t.CheckOutputLines( 1338 t.CheckOutputLines(
1339 "WARN: ~/category/package/patches/Makefile: Patch files should be "+ 1339 "WARN: ~/category/package/patches/Makefile: Patch files should be "+
1340 "named \"patch-\", followed by letters, '-', '_', '.', and digits only.", 1340 "named \"patch-\", followed by letters, '-', '_', '.', and digits only.",
1341 "1 warning found.") 1341 "1 warning found.")
1342} 1342}
1343 1343
 1344func (s *Suite) Test_Package_check__redundant_WRKSRC(c *check.C) {
 1345 t := s.Init(c)
 1346
 1347 t.SetUpPackage("category/package",
 1348 "WRKSRC=\t${WRKDIR}/package-1.0")
 1349
 1350 t.Main("-q", "category/package")
 1351
 1352 t.CheckOutputLines(
 1353 "NOTE: ~/category/package/Makefile:20: " +
 1354 "Setting WRKSRC to \"${WRKDIR}/package-1.0\" is redundant.")
 1355}
 1356
1344func (s *Suite) Test_Package_checkDescr__DESCR_SRC(c *check.C) { 1357func (s *Suite) Test_Package_checkDescr__DESCR_SRC(c *check.C) {
1345 t := s.Init(c) 1358 t := s.Init(c)
1346 1359
1347 t.SetUpPackage("other/package") 1360 t.SetUpPackage("other/package")
1348 t.SetUpPackage("category/package", 1361 t.SetUpPackage("category/package",
1349 "DESCR_SRC=\t../../other/package/DESCR") 1362 "DESCR_SRC=\t../../other/package/DESCR")
1350 t.Remove("category/package/DESCR") 1363 t.Remove("category/package/DESCR")
1351 t.FinishSetUp() 1364 t.FinishSetUp()
1352 1365
1353 G.Check(t.File("category/package")) 1366 G.Check(t.File("category/package"))
1354 1367
1355 t.CheckOutputEmpty() 1368 t.CheckOutputEmpty()
1356} 1369}

cvs diff -r1.104 -r1.105 pkgsrc/pkgtools/pkglint/files/Attic/vardefs.go (expand / switch to unified diff)

--- pkgsrc/pkgtools/pkglint/files/Attic/vardefs.go 2022/01/01 12:44:25 1.104
+++ pkgsrc/pkgtools/pkglint/files/Attic/vardefs.go 2022/07/24 20:07:20 1.105
@@ -273,27 +273,27 @@ func (reg *VarTypeRegistry) cmdline(varn @@ -273,27 +273,27 @@ func (reg *VarTypeRegistry) cmdline(varn
273func (reg *VarTypeRegistry) infralist(varname string, basicType *BasicType) { 273func (reg *VarTypeRegistry) infralist(varname string, basicType *BasicType) {
274 reg.DefineName(varname, basicType, List, "infralist") 274 reg.DefineName(varname, basicType, List, "infralist")
275} 275}
276 276
277// compilerLanguages reads the available languages that are typically 277// compilerLanguages reads the available languages that are typically
278// bundled in a single compiler framework, such as GCC or Clang. 278// bundled in a single compiler framework, such as GCC or Clang.
279func (reg *VarTypeRegistry) compilerLanguages(src *Pkgsrc) *BasicType { 279func (reg *VarTypeRegistry) compilerLanguages(src *Pkgsrc) *BasicType {
280 mklines := src.LoadMkExisting("mk/compiler.mk") 280 mklines := src.LoadMkExisting("mk/compiler.mk")
281 281
282 languages := make(map[string]bool) 282 languages := make(map[string]bool)
283 if mklines != nil { 283 if mklines != nil {
284 for _, mkline := range mklines.mklines { 284 for _, mkline := range mklines.mklines {
285 285
286 if mkline.IsVarassign() && mkline.Varname() == "_CXX_STD_VERSIONS" { 286 if mkline.IsVarassign() && hasSuffix(mkline.Varname(), "_STD_VERSIONS") {
287 words := mkline.ValueFields(mkline.Value()) 287 words := mkline.ValueFields(mkline.Value())
288 for _, word := range words { 288 for _, word := range words {
289 languages[intern(word)] = true 289 languages[intern(word)] = true
290 } 290 }
291 } 291 }
292 292
293 if mkline.IsDirective() && mkline.NeedsCond() && mkline.Cond() != nil { 293 if mkline.IsDirective() && mkline.NeedsCond() && mkline.Cond() != nil {
294 mkline.Cond().Walk(&MkCondCallback{ 294 mkline.Cond().Walk(&MkCondCallback{
295 VarUse: func(varuse *MkVarUse) { 295 VarUse: func(varuse *MkVarUse) {
296 if varuse.varname == "USE_LANGUAGES" && len(varuse.modifiers) == 1 { 296 if varuse.varname == "USE_LANGUAGES" && len(varuse.modifiers) == 1 {
297 ok, _, pattern, exact := varuse.modifiers[0].MatchMatch() 297 ok, _, pattern, exact := varuse.modifiers[0].MatchMatch()
298 if ok && exact { 298 if ok && exact {
299 languages[intern(pattern)] = true 299 languages[intern(pattern)] = true
@@ -1078,26 +1078,27 @@ func (reg *VarTypeRegistry) Init(src *Pk @@ -1078,26 +1078,27 @@ func (reg *VarTypeRegistry) Init(src *Pk
1078 reg.pkglist("CHECK_HEADERS_SKIP", BtPathPattern) 1078 reg.pkglist("CHECK_HEADERS_SKIP", BtPathPattern)
1079 reg.usr("CHECK_INTERPRETER", BtYesNo) 1079 reg.usr("CHECK_INTERPRETER", BtYesNo)
1080 reg.pkglist("CHECK_INTERPRETER_SKIP", BtPathPattern) 1080 reg.pkglist("CHECK_INTERPRETER_SKIP", BtPathPattern)
1081 reg.usr("CHECK_PERMS", BtYesNo) 1081 reg.usr("CHECK_PERMS", BtYesNo)
1082 reg.pkglist("CHECK_PERMS_SKIP", BtPathPattern) 1082 reg.pkglist("CHECK_PERMS_SKIP", BtPathPattern)
1083 reg.usr("CHECK_PORTABILITY", BtYesNo) 1083 reg.usr("CHECK_PORTABILITY", BtYesNo)
1084 reg.pkglist("CHECK_PORTABILITY_SKIP", BtPathPattern) 1084 reg.pkglist("CHECK_PORTABILITY_SKIP", BtPathPattern)
1085 reg.usr("CHECK_RELRO", BtYesNo) 1085 reg.usr("CHECK_RELRO", BtYesNo)
1086 reg.pkglist("CHECK_RELRO_SKIP", BtPathPattern) 1086 reg.pkglist("CHECK_RELRO_SKIP", BtPathPattern)
1087 reg.pkg("CHECK_RELRO_SUPPORTED", BtYesNo) 1087 reg.pkg("CHECK_RELRO_SUPPORTED", BtYesNo)
1088 reg.pkg("CHECK_SHLIBS", BtYesNo) 1088 reg.pkg("CHECK_SHLIBS", BtYesNo)
1089 reg.pkglist("CHECK_SHLIBS_SKIP", BtPathPattern) 1089 reg.pkglist("CHECK_SHLIBS_SKIP", BtPathPattern)
1090 reg.pkg("CHECK_SHLIBS_SUPPORTED", BtYesNo) 1090 reg.pkg("CHECK_SHLIBS_SUPPORTED", BtYesNo)
 1091 reg.usrlist("CHECK_WRKREF", enum("tools wrappers home wrksrc work wrkobjdir pkgsrc buildlink extra"))
1091 reg.pkglist("CHECK_WRKREF_SKIP", BtPathPattern) 1092 reg.pkglist("CHECK_WRKREF_SKIP", BtPathPattern)
1092 reg.pkg("CMAKE_ARG_PATH", BtPathname) 1093 reg.pkg("CMAKE_ARG_PATH", BtPathname)
1093 reg.pkglist("CMAKE_ARGS", BtShellWord) 1094 reg.pkglist("CMAKE_ARGS", BtShellWord)
1094 reg.pkglist("CMAKE_ARGS.*", BtShellWord) 1095 reg.pkglist("CMAKE_ARGS.*", BtShellWord)
1095 reg.pkglist("CMAKE_DEPENDENCIES_REWRITE", BtWrksrcPathPattern) 1096 reg.pkglist("CMAKE_DEPENDENCIES_REWRITE", BtWrksrcPathPattern)
1096 reg.pkglist("CMAKE_MODULE_PATH_OVERRIDE", BtWrksrcPathPattern) 1097 reg.pkglist("CMAKE_MODULE_PATH_OVERRIDE", BtWrksrcPathPattern)
1097 reg.pkg("CMAKE_PKGSRC_BUILD_FLAGS", BtYesNo) 1098 reg.pkg("CMAKE_PKGSRC_BUILD_FLAGS", BtYesNo)
1098 reg.pkglist("CMAKE_PREFIX_PATH", BtPathPattern) 1099 reg.pkglist("CMAKE_PREFIX_PATH", BtPathPattern)
1099 reg.pkg("CMAKE_USE_GNU_INSTALL_DIRS", BtYesNo) 1100 reg.pkg("CMAKE_USE_GNU_INSTALL_DIRS", BtYesNo)
1100 reg.pkg("CMAKE_INSTALL_PREFIX", BtPathname) // The default is ${PREFIX}. 1101 reg.pkg("CMAKE_INSTALL_PREFIX", BtPathname) // The default is ${PREFIX}.
1101 reg.pkgappend("COMMENT", BtComment) 1102 reg.pkgappend("COMMENT", BtComment)
1102 reg.sys("COMPILE.*", BtShellCommand) 1103 reg.sys("COMPILE.*", BtShellCommand)
1103 reg.sys("COMPILER_RPATH_FLAG", enum("-Wl,-rpath")) 1104 reg.sys("COMPILER_RPATH_FLAG", enum("-Wl,-rpath"))

cvs diff -r1.100 -r1.101 pkgsrc/pkgtools/pkglint/files/Attic/vartypecheck.go (expand / switch to unified diff)

--- pkgsrc/pkgtools/pkglint/files/Attic/vartypecheck.go 2022/01/01 12:44:25 1.100
+++ pkgsrc/pkgtools/pkglint/files/Attic/vartypecheck.go 2022/07/24 20:07:20 1.101
@@ -1596,26 +1596,42 @@ func (cv *VartypeCheck) WrapperTransform @@ -1596,26 +1596,42 @@ func (cv *VartypeCheck) WrapperTransform
1596 matches(cmd, `^(R|l|rpath):([^:]+):(.+)$`), 1596 matches(cmd, `^(R|l|rpath):([^:]+):(.+)$`),
1597 matches(cmd, `^'?(opt|rename|rm-optarg|rmdir):.*$`), 1597 matches(cmd, `^'?(opt|rename|rm-optarg|rmdir):.*$`),
1598 cmd == "-e", 1598 cmd == "-e",
1599 matches(cmd, `^["']?s[|:,]`): 1599 matches(cmd, `^["']?s[|:,]`):
1600 break 1600 break
1601 1601
1602 default: 1602 default:
1603 cv.Warnf("Unknown wrapper transform command %q.", cmd) 1603 cv.Warnf("Unknown wrapper transform command %q.", cmd)
1604 } 1604 }
1605} 1605}
1606 1606
1607func (cv *VartypeCheck) WrkdirSubdirectory() { 1607func (cv *VartypeCheck) WrkdirSubdirectory() {
1608 cv.Pathname() 1608 cv.Pathname()
 1609
 1610 if hasPrefix(cv.Value, "${WRKDIR}/") && cv.MkLines.pkg != nil {
 1611 distname := cv.MkLines.pkg.redundant.get("DISTNAME").vari
 1612 if distname.IsConstant() && cv.Value[10:] == distname.ConstantValue() {
 1613 fix := cv.Autofix()
 1614 fix.Notef("Setting WRKSRC to %q is redundant.", cv.Value)
 1615 fix.Delete()
 1616 fix.Apply()
 1617
 1618 // XXX: Resetting the Line and MkLine should be handled universally
 1619 // by Autofix. Without this hack, pkglint would crash later when
 1620 // aligning the variable assignments.
 1621 cv.MkLine.Line.Text = ""
 1622 cv.MkLine.data = mkLineEmpty{}
 1623 }
 1624 }
1609} 1625}
1610 1626
1611// WrksrcPathPattern is a shell pattern for existing pathnames, possibly including slashes. 1627// WrksrcPathPattern is a shell pattern for existing pathnames, possibly including slashes.
1612func (cv *VartypeCheck) WrksrcPathPattern() { 1628func (cv *VartypeCheck) WrksrcPathPattern() {
1613 cv.PathPattern() 1629 cv.PathPattern()
1614 1630
1615 if hasPrefix(cv.Value, "${WRKSRC}/") { 1631 if hasPrefix(cv.Value, "${WRKSRC}/") {
1616 fix := cv.Autofix() 1632 fix := cv.Autofix()
1617 fix.Notef("The pathname patterns in %s don't need to mention ${WRKSRC}.", cv.Varname) 1633 fix.Notef("The pathname patterns in %s don't need to mention ${WRKSRC}.", cv.Varname)
1618 fix.Explain( 1634 fix.Explain(
1619 "These pathname patters are interpreted relative to ${WRKSRC} by definition.", 1635 "These pathname patters are interpreted relative to ${WRKSRC} by definition.",
1620 "Therefore that part can be left out.") 1636 "Therefore that part can be left out.")
1621 fix.Replace("${WRKSRC}/", "") 1637 fix.Replace("${WRKSRC}/", "")

cvs diff -r1.92 -r1.93 pkgsrc/pkgtools/pkglint/files/Attic/vartypecheck_test.go (expand / switch to unified diff)

--- pkgsrc/pkgtools/pkglint/files/Attic/vartypecheck_test.go 2022/06/24 07:16:23 1.92
+++ pkgsrc/pkgtools/pkglint/files/Attic/vartypecheck_test.go 2022/07/24 20:07:20 1.93
@@ -2399,47 +2399,64 @@ func (s *Suite) Test_VartypeCheck_Wrappe @@ -2399,47 +2399,64 @@ func (s *Suite) Test_VartypeCheck_Wrappe
2399 "rename:src:dst", 2399 "rename:src:dst",
2400 "rm-optarg:-option", 2400 "rm-optarg:-option",
2401 "rmdir:/usr/include", 2401 "rmdir:/usr/include",
2402 "rpath:/usr/lib:/usr/pkg/lib", 2402 "rpath:/usr/lib:/usr/pkg/lib",
2403 "rpath:/usr/lib", 2403 "rpath:/usr/lib",
2404 "unknown", 2404 "unknown",
2405 "-e 's,-Wall,-Wall -Wextra,'") 2405 "-e 's,-Wall,-Wall -Wextra,'")
2406 vt.Output( 2406 vt.Output(
2407 "WARN: filename.mk:7: Unknown wrapper transform command \"rpath:/usr/lib\".", 2407 "WARN: filename.mk:7: Unknown wrapper transform command \"rpath:/usr/lib\".",
2408 "WARN: filename.mk:8: Unknown wrapper transform command \"unknown\".") 2408 "WARN: filename.mk:8: Unknown wrapper transform command \"unknown\".")
2409} 2409}
2410 2410
2411func (s *Suite) Test_VartypeCheck_WrkdirSubdirectory(c *check.C) { 2411func (s *Suite) Test_VartypeCheck_WrkdirSubdirectory(c *check.C) {
2412 vt := NewVartypeCheckTester(s.Init(c), BtWrkdirSubdirectory) 2412 t := s.Init(c)
 2413 pkg := NewPackage(t.SetUpPackage("category/package"))
 2414 t.FinishSetUp()
 2415 vt := NewVartypeCheckTester(t, BtWrkdirSubdirectory)
 2416 pkg.Check() // To initialize pkg.redundant.
2413 2417
 2418 vt.Package(pkg)
2414 vt.Varname("WRKSRC") 2419 vt.Varname("WRKSRC")
2415 vt.Op(opAssign) 2420 vt.Op(opAssign)
2416 vt.Values( 2421 vt.Values(
2417 "${WRKDIR}", 2422 "${WRKDIR}",
2418 "${WRKDIR}/", 2423 "${WRKDIR}/",
2419 "${WRKDIR}/.", 2424 "${WRKDIR}/.",
2420 "${WRKDIR}/subdir", 2425 "${WRKDIR}/subdir",
2421 ".", 2426 ".",
2422 "${DISTNAME}", 2427 "${DISTNAME}",
2423 "${PKGNAME_NOREV}", 2428 "${PKGNAME_NOREV}",
2424 "two words", 2429 "two words",
2425 "../other", 2430 "../other",
2426 "${WRKSRC}", // Recursive definition. 2431 "${WRKSRC}", // Recursive definition.
2427 "${PKGDIR}/files") 2432 "${PKGDIR}/files")
2428 2433
2429 // XXX: Many more consistency checks are possible here. 2434 // XXX: Many more consistency checks are possible here.
2430 vt.Output( 2435 vt.Output(
2431 "WARN: filename.mk:8: The pathname \"two words\" " + 2436 "WARN: filename.mk:8: The pathname \"two words\" " +
2432 "contains the invalid character \" \".") 2437 "contains the invalid character \" \".")
 2438
 2439 vt.Values(
 2440 // TODO: Note the redundant definition.
 2441 "${WRKDIR}/package-1.0",
 2442 "${WRKDIR}/pkg-1.0", // different package base
 2443 "${WRKDIR}/package-1.000", // different version string
 2444 "${WRKDIR}/package-1.1", // different version
 2445 )
 2446
 2447 vt.Output(
 2448 "NOTE: filename.mk:21: " +
 2449 "Setting WRKSRC to \"${WRKDIR}/package-1.0\" is redundant.")
2433} 2450}
2434 2451
2435func (s *Suite) Test_VartypeCheck_WrksrcPathPattern(c *check.C) { 2452func (s *Suite) Test_VartypeCheck_WrksrcPathPattern(c *check.C) {
2436 t := s.Init(c) 2453 t := s.Init(c)
2437 vt := NewVartypeCheckTester(t, BtWrksrcPathPattern) 2454 vt := NewVartypeCheckTester(t, BtWrksrcPathPattern)
2438 2455
2439 vt.Varname("SUBST_FILES.class") 2456 vt.Varname("SUBST_FILES.class")
2440 vt.Op(opAssign) 2457 vt.Op(opAssign)
2441 vt.Values( 2458 vt.Values(
2442 "relative/*.sh", 2459 "relative/*.sh",
2443 "${WRKSRC}/relative/*.sh") 2460 "${WRKSRC}/relative/*.sh")
2444 2461
2445 vt.Output( 2462 vt.Output(
@@ -2611,37 +2628,48 @@ type VartypeCheckTester struct { @@ -2611,37 +2628,48 @@ type VartypeCheckTester struct {
2611// NewVartypeCheckTester starts the test with a filename of "filename", at line 1, 2628// NewVartypeCheckTester starts the test with a filename of "filename", at line 1,
2612// with "=" as the operator. The variable has to be initialized explicitly. 2629// with "=" as the operator. The variable has to be initialized explicitly.
2613func NewVartypeCheckTester(t *Tester, basicType *BasicType) *VartypeCheckTester { 2630func NewVartypeCheckTester(t *Tester, basicType *BasicType) *VartypeCheckTester {
2614 2631
2615 // This is necessary to know whether the variable name is a list type 2632 // This is necessary to know whether the variable name is a list type
2616 // since in such a case each value is split into the list elements. 2633 // since in such a case each value is split into the list elements.
2617 if G.Pkgsrc.VariableType(nil, "USE_CWRAPPERS") == nil { 2634 if G.Pkgsrc.VariableType(nil, "USE_CWRAPPERS") == nil {
2618 t.SetUpVartypes() 2635 t.SetUpVartypes()
2619 } 2636 }
2620 2637
2621 return &VartypeCheckTester{t, basicType, "filename.mk", 1, "", opAssign, nil} 2638 return &VartypeCheckTester{t, basicType, "filename.mk", 1, "", opAssign, nil}
2622} 2639}
2623 2640
 2641// Package sets the package that gives context to the MkLines that are
 2642// temporarily created in all following calls to Values.
 2643//
 2644// Depending on the test case at hand, it may be enough to have a bare
 2645// package created by NewPackage, in other cases the package data needs to be
 2646// loaded using Package.load.
2624func (vt *VartypeCheckTester) Package(pkg *Package) { vt.pkg = pkg } 2647func (vt *VartypeCheckTester) Package(pkg *Package) { vt.pkg = pkg }
2625 2648
 2649// Varname sets the variable name that will be used in all following calls to
 2650// Values.
2626func (vt *VartypeCheckTester) Varname(varname string) { 2651func (vt *VartypeCheckTester) Varname(varname string) {
2627 vartype := G.Pkgsrc.VariableType(nil, varname) 2652 vartype := G.Pkgsrc.VariableType(nil, varname)
2628 assertNotNil(vartype) 2653 assertNotNil(vartype)
2629 assert(vartype.basicType == vt.basicType) 2654 assert(vartype.basicType == vt.basicType)
2630 2655
2631 vt.varname = varname 2656 vt.varname = varname
2632 vt.nextSection() 2657 vt.nextSection()
2633} 2658}
2634 2659
 2660// File sets the filename that will be used in all following calls to Values.
 2661// This is useful when testing the permissions of the variable, see
 2662// VarTypeRegistry.
2635func (vt *VartypeCheckTester) File(filename CurrPath) { 2663func (vt *VartypeCheckTester) File(filename CurrPath) {
2636 vt.filename = filename 2664 vt.filename = filename
2637 vt.lineno = 1 2665 vt.lineno = 1
2638} 2666}
2639 2667
2640// Op sets the operator for the following tests. 2668// Op sets the operator for the following tests.
2641// The line number is advanced to the next number ending in 1, e.g. 11, 21, 31. 2669// The line number is advanced to the next number ending in 1, e.g. 11, 21, 31.
2642func (vt *VartypeCheckTester) Op(op MkOperator) { 2670func (vt *VartypeCheckTester) Op(op MkOperator) {
2643 vt.op = op 2671 vt.op = op
2644 vt.nextSection() 2672 vt.nextSection()
2645} 2673}
2646 2674
2647// Values feeds each of the values to the actual check. 2675// Values feeds each of the values to the actual check.