Annotation of pkgsrc/pkgtools/pkglint/files/autofix_test.go, Revision 1.28
1.14 rillig 1: package pkglint
1.1 rillig 2:
1.5 rillig 3: import (
4: "gopkg.in/check.v1"
1.10 rillig 5: "os"
6: "runtime"
1.5 rillig 7: "strings"
8: )
1.1 rillig 9:
1.12 rillig 10: func (s *Suite) Test_Autofix_Warnf__duplicate(c *check.C) {
11: t := s.Init(c)
12:
13: line := t.NewLine("DESCR", 1, "Description of the package")
14:
15: fix := line.Autofix()
16: fix.Warnf("Warning 1.")
1.25 rillig 17: t.ExpectAssert(func() { fix.Warnf("Warning 2.") })
1.12 rillig 18: }
19:
20: func (s *Suite) Test_Autofix__default_leaves_line_unchanged(c *check.C) {
21: t := s.Init(c)
22:
1.16 rillig 23: t.SetUpCommandLine("--source")
24: mklines := t.SetUpFileMkLines("Makefile",
1.12 rillig 25: "# row 1 \\",
26: "continuation of row 1")
27: line := mklines.lines.Lines[0]
28:
29: fix := line.Autofix()
30: fix.Warnf("Row should be replaced with line.")
31: fix.Replace("row", "line")
32: fix.ReplaceRegex(`row \d+`, "the above line", -1)
33: fix.InsertBefore("above")
34: fix.InsertAfter("below")
35: fix.Delete()
36: fix.Apply()
37:
1.27 rillig 38: t.CheckEquals(fix.RawText(), ""+
1.12 rillig 39: "# row 1 \\\n"+
40: "continuation of row 1\n")
41: t.CheckOutputLines(
42: ">\t# row 1 \\",
43: ">\tcontinuation of row 1",
44: "WARN: ~/Makefile:1--2: Row should be replaced with line.")
1.27 rillig 45: t.CheckEquals(fix.modified, true)
1.12 rillig 46: }
47:
48: func (s *Suite) Test_Autofix__show_autofix_modifies_line(c *check.C) {
49: t := s.Init(c)
50:
1.16 rillig 51: t.SetUpCommandLine("--source", "--show-autofix")
52: mklines := t.SetUpFileMkLines("Makefile",
1.12 rillig 53: "# row 1 \\",
54: "continuation of row 1")
55: line := mklines.lines.Lines[0]
56:
57: fix := line.Autofix()
58: fix.Warnf("Row should be replaced with line.")
1.25 rillig 59: fix.ReplaceAfter("", "# row", "# line")
1.12 rillig 60: fix.ReplaceRegex(`row \d+`, "the above line", -1)
61: fix.InsertBefore("above")
62: fix.InsertAfter("below")
63: fix.Delete()
64: fix.Apply()
65:
1.27 rillig 66: t.CheckEquals(fix.RawText(), ""+
1.12 rillig 67: "above\n"+
68: "below\n")
69: t.CheckOutputLines(
70: "WARN: ~/Makefile:1--2: Row should be replaced with line.",
1.25 rillig 71: "AUTOFIX: ~/Makefile:1: Replacing \"# row\" with \"# line\".",
1.12 rillig 72: "AUTOFIX: ~/Makefile:2: Replacing \"row 1\" with \"the above line\".",
73: "AUTOFIX: ~/Makefile:1: Inserting a line \"above\" before this line.",
74: "AUTOFIX: ~/Makefile:2: Inserting a line \"below\" after this line.",
75: "AUTOFIX: ~/Makefile:1: Deleting this line.",
76: "AUTOFIX: ~/Makefile:2: Deleting this line.",
77: "+\tabove",
78: "-\t# row 1 \\",
79: "-\tcontinuation of row 1",
80: "+\tbelow")
1.27 rillig 81: t.CheckEquals(fix.modified, true)
1.12 rillig 82: }
83:
1.25 rillig 84: func (s *Suite) Test_Autofix_ReplaceAfter__autofix_in_continuation_line(c *check.C) {
1.12 rillig 85: t := s.Init(c)
86:
1.16 rillig 87: t.SetUpCommandLine("--autofix", "--source")
88: mklines := t.SetUpFileMkLines("Makefile",
1.12 rillig 89: "# line 1 \\",
90: "continuation 1 \\",
91: "continuation 2")
92:
93: fix := mklines.lines.Lines[0].Autofix()
1.25 rillig 94: fix.Warnf("Line should be replaced with Row.")
95: fix.ReplaceAfter("", "line", "row")
1.12 rillig 96: fix.Apply()
97:
98: t.CheckOutputLines(
1.25 rillig 99: "AUTOFIX: ~/Makefile:1: Replacing \"line\" with \"row\".",
1.12 rillig 100: "-\t# line 1 \\",
1.25 rillig 101: "+\t# row 1 \\",
1.13 rillig 102: "\tcontinuation 1 \\",
103: "\tcontinuation 2")
1.12 rillig 104: }
105:
1.25 rillig 106: func (s *Suite) Test_Autofix_ReplaceAfter__autofix_several_times_in_continuation_line(c *check.C) {
107: t := s.Init(c)
108:
109: t.SetUpCommandLine("--autofix", "--source")
110: mklines := t.SetUpFileMkLines("Makefile",
111: "# line 1 \\",
112: "continuation 1 \\",
113: "continuation 2")
114:
115: fix := mklines.lines.Lines[0].Autofix()
116: fix.Warnf("N should be replaced with V.")
117: fix.ReplaceAfter("", "n", "v")
118: fix.Apply()
119:
120: // Nothing is logged or fixed because the "n" appears more than once,
121: // and as of June 2019, pkglint doesn't know which occurrence is the
122: // correct one.
123: t.CheckOutputEmpty()
124: }
125:
126: func (s *Suite) Test_Autofix_ReplaceAfter__autofix_one_time(c *check.C) {
127: t := s.Init(c)
128:
129: t.SetUpCommandLine("--autofix", "--source")
130: mklines := t.SetUpFileMkLines("Makefile",
131: MkCvsID,
132: "VAR=\t$$(var) $(var)")
133:
134: mklines.Check()
135:
136: // Nothing is replaced since, as of June 2019, pkglint doesn't
137: // know which of the two "$(var)" should be replaced.
138: t.CheckOutputEmpty()
139: }
140:
1.11 rillig 141: func (s *Suite) Test_Autofix_ReplaceRegex__show_autofix(c *check.C) {
1.2 rillig 142: t := s.Init(c)
143:
1.16 rillig 144: t.SetUpCommandLine("--show-autofix")
145: lines := t.SetUpFileLines("Makefile",
1.2 rillig 146: "line1",
147: "line2",
148: "line3")
1.1 rillig 149:
1.12 rillig 150: fix := lines.Lines[1].Autofix()
1.1 rillig 151: fix.Warnf("Something's wrong here.")
1.3 rillig 152: fix.ReplaceRegex(`.`, "X", -1)
1.1 rillig 153: fix.Apply()
154: SaveAutofixChanges(lines)
155:
1.27 rillig 156: t.CheckEquals(lines.Lines[1].raw[0].textnl, "XXXXX\n")
1.2 rillig 157: t.CheckFileLines("Makefile",
158: "line1",
159: "line2",
160: "line3")
161: t.CheckOutputLines(
1.1 rillig 162: "WARN: ~/Makefile:2: Something's wrong here.",
1.3 rillig 163: "AUTOFIX: ~/Makefile:2: Replacing \"l\" with \"X\".",
164: "AUTOFIX: ~/Makefile:2: Replacing \"i\" with \"X\".",
165: "AUTOFIX: ~/Makefile:2: Replacing \"n\" with \"X\".",
166: "AUTOFIX: ~/Makefile:2: Replacing \"e\" with \"X\".",
167: "AUTOFIX: ~/Makefile:2: Replacing \"2\" with \"X\".")
1.1 rillig 168: }
169:
1.11 rillig 170: func (s *Suite) Test_Autofix_ReplaceRegex__autofix(c *check.C) {
1.2 rillig 171: t := s.Init(c)
172:
1.16 rillig 173: t.SetUpCommandLine("--autofix", "--source")
174: lines := t.SetUpFileLines("Makefile",
1.2 rillig 175: "line1",
176: "line2",
177: "line3")
1.1 rillig 178:
1.12 rillig 179: fix := lines.Lines[1].Autofix()
1.1 rillig 180: fix.Warnf("Something's wrong here.")
1.3 rillig 181: fix.ReplaceRegex(`.`, "X", 3)
1.1 rillig 182: fix.Apply()
183:
1.3 rillig 184: t.CheckOutputLines(
185: "AUTOFIX: ~/Makefile:2: Replacing \"l\" with \"X\".",
186: "AUTOFIX: ~/Makefile:2: Replacing \"i\" with \"X\".",
187: "AUTOFIX: ~/Makefile:2: Replacing \"n\" with \"X\".",
188: "-\tline2",
189: "+\tXXXe2")
190:
1.12 rillig 191: // After calling fix.Apply above, the autofix is ready to be used again.
1.1 rillig 192: fix.Warnf("Use Y instead of X.")
1.25 rillig 193: fix.Replace("XXX", "YYY")
1.1 rillig 194: fix.Apply()
195:
1.3 rillig 196: t.CheckOutputLines(
1.25 rillig 197: "AUTOFIX: ~/Makefile:2: Replacing \"XXX\" with \"YYY\".",
1.3 rillig 198: "-\tline2",
1.25 rillig 199: "+\tYYYe2")
1.3 rillig 200:
1.1 rillig 201: SaveAutofixChanges(lines)
202:
1.2 rillig 203: t.CheckFileLines("Makefile",
204: "line1",
1.25 rillig 205: "YYYe2",
1.2 rillig 206: "line3")
1.1 rillig 207: }
208:
1.11 rillig 209: func (s *Suite) Test_Autofix_ReplaceRegex__show_autofix_and_source(c *check.C) {
1.2 rillig 210: t := s.Init(c)
211:
1.16 rillig 212: t.SetUpCommandLine("--show-autofix", "--source")
213: lines := t.SetUpFileLines("Makefile",
1.2 rillig 214: "line1",
215: "line2",
216: "line3")
1.1 rillig 217:
1.12 rillig 218: fix := lines.Lines[1].Autofix()
1.1 rillig 219: fix.Warnf("Something's wrong here.")
1.3 rillig 220: fix.ReplaceRegex(`.`, "X", -1)
1.1 rillig 221: fix.Apply()
222:
223: fix.Warnf("Use Y instead of X.")
1.25 rillig 224: fix.Replace("XXXXX", "YYYYY")
1.1 rillig 225: fix.Apply()
226:
227: SaveAutofixChanges(lines)
228:
1.2 rillig 229: t.CheckOutputLines(
1.1 rillig 230: "WARN: ~/Makefile:2: Something's wrong here.",
1.3 rillig 231: "AUTOFIX: ~/Makefile:2: Replacing \"l\" with \"X\".",
232: "AUTOFIX: ~/Makefile:2: Replacing \"i\" with \"X\".",
233: "AUTOFIX: ~/Makefile:2: Replacing \"n\" with \"X\".",
234: "AUTOFIX: ~/Makefile:2: Replacing \"e\" with \"X\".",
235: "AUTOFIX: ~/Makefile:2: Replacing \"2\" with \"X\".",
1.2 rillig 236: "-\tline2",
237: "+\tXXXXX",
1.1 rillig 238: "",
239: "WARN: ~/Makefile:2: Use Y instead of X.",
1.25 rillig 240: "AUTOFIX: ~/Makefile:2: Replacing \"XXXXX\" with \"YYYYY\".",
1.2 rillig 241: "-\tline2",
1.25 rillig 242: "+\tYYYYY")
1.1 rillig 243: }
244:
1.18 rillig 245: // When an autofix replaces text, it does not touch those
246: // lines that have been inserted before since these are
247: // usually already correct.
248: func (s *Suite) Test_Autofix_ReplaceAfter__after_inserting_a_line(c *check.C) {
249: t := s.Init(c)
250:
251: t.SetUpCommandLine("--show-autofix")
252: line := t.NewLine("filename", 5, "initial text")
253:
254: fix := line.Autofix()
255: fix.Notef("Inserting a line.")
256: fix.InsertBefore("line before")
257: fix.InsertAfter("line after")
258: fix.Apply()
259:
260: fix.Notef("Replacing text.")
261: fix.Replace("l", "L")
262: fix.ReplaceRegex(`i`, "I", 1)
263: fix.Apply()
264:
265: t.CheckOutputLines(
266: "NOTE: filename:5: Inserting a line.",
267: "AUTOFIX: filename:5: Inserting a line \"line before\" before this line.",
268: "AUTOFIX: filename:5: Inserting a line \"line after\" after this line.",
269: "NOTE: filename:5: Replacing text.",
270: "AUTOFIX: filename:5: Replacing \"l\" with \"L\".",
271: "AUTOFIX: filename:5: Replacing \"i\" with \"I\".")
272: }
273:
1.27 rillig 274: func (s *Suite) Test_Autofix_ReplaceAt(c *check.C) {
275: t := s.Init(c)
276:
277: lines := func(lines ...string) []string { return lines }
278: diagnostics := lines
279: autofixes := lines
280: test := func(texts []string, rawIndex int, column int, from, to string, diagnostics []string, autofixes []string) {
281:
282: mainPart := func() {
283: mklines := t.NewMkLines("filename.mk", texts...)
284: assert(len(mklines.mklines) == 1)
285: mkline := mklines.mklines[0]
286:
287: fix := mkline.Autofix()
288: fix.Notef("Should be appended instead of assigned.")
289: fix.ReplaceAt(rawIndex, column, from, to)
290: fix.Anyway()
291: fix.Apply()
292: }
293:
294: t.SetUpCommandLine("-Wall")
295: mainPart()
296: t.CheckOutput(diagnostics)
297:
298: t.SetUpCommandLine("-Wall", "--autofix")
299: mainPart()
300: t.CheckOutput(autofixes)
301: }
302:
303: test(
304: lines(
305: "VAR=value1 \\",
306: "\tvalue2"),
307: 0, 3, "=", "+=",
308: diagnostics(
309: "NOTE: filename.mk:1: Should be appended instead of assigned."),
310: autofixes(
311: "AUTOFIX: filename.mk:1: Replacing \"=\" with \"+=\"."))
312:
313: // If the text at the precisely given position does not match,
314: // the note is still printed because of the fix.Anyway(), but
315: // nothing is replaced automatically.
316: test(
317: lines(
318: "VAR=value1 \\",
319: "\tvalue2"),
320: 0, 3, "?", "+=",
321: diagnostics(
322: "NOTE: filename.mk:1--2: Should be appended instead of assigned."),
323: autofixes(
324: nil...))
325:
326: // Getting the line number wrong is a strange programming error, and
327: // there does not need to be any code checking for this in the main code.
328: t.ExpectPanicMatches(
329: func() { test(lines("VAR=value"), 10, 3, "from", "to", nil, nil) },
330: `runtime error: index out of range.*`)
331:
332: // It is a programming error to replace a string with itself, since that
333: // would produce confusing diagnostics.
334: t.ExpectAssert(
335: func() { test(lines("VAR=value"), 0, 4, "value", "value", nil, nil) })
336:
337: // Getting the column number wrong may happen when a previous replacement
338: // has made the string shorter than before, therefore no panic in this case.
339: test(
340: lines(
341: "VAR=value1 \\",
342: "\tvalue2"),
343: 0, 20, "?", "+=",
344: diagnostics(
345: "NOTE: filename.mk:1--2: Should be appended instead of assigned."),
346: autofixes(
347: nil...))
348: }
349:
1.11 rillig 350: func (s *Suite) Test_SaveAutofixChanges(c *check.C) {
1.2 rillig 351: t := s.Init(c)
352:
1.16 rillig 353: t.SetUpCommandLine("--autofix")
354: lines := t.SetUpFileLines("example.txt",
1.2 rillig 355: "line1 := value1",
356: "line2 := value2",
357: "line3 := value3")
1.1 rillig 358:
1.12 rillig 359: fix := lines.Lines[1].Autofix()
1.1 rillig 360: fix.Warnf("Something's wrong here.")
1.12 rillig 361: fix.ReplaceRegex(`...`, "XXX", 2)
1.3 rillig 362: fix.Apply()
363:
1.12 rillig 364: SaveAutofixChanges(lines)
1.1 rillig 365:
1.3 rillig 366: t.CheckOutputLines(
1.12 rillig 367: "AUTOFIX: ~/example.txt:2: Replacing \"lin\" with \"XXX\".",
368: "AUTOFIX: ~/example.txt:2: Replacing \"e2 \" with \"XXX\".")
369: t.CheckFileLines("example.txt",
1.2 rillig 370: "line1 := value1",
1.12 rillig 371: "XXXXXX:= value2",
372: "line3 := value3")
1.1 rillig 373: }
374:
1.11 rillig 375: func (s *Suite) Test_SaveAutofixChanges__no_changes_necessary(c *check.C) {
376: t := s.Init(c)
377:
1.16 rillig 378: t.SetUpCommandLine("--autofix")
379: lines := t.SetUpFileLines("DESCR",
1.11 rillig 380: "Line 1",
381: "Line 2")
382:
1.12 rillig 383: fix := lines.Lines[0].Autofix()
1.11 rillig 384: fix.Warnf("Dummy warning.")
385: fix.Replace("X", "Y")
386: fix.Apply()
387:
388: // Since nothing has been effectively changed,
389: // nothing needs to be saved.
390: SaveAutofixChanges(lines)
391:
392: // And therefore, no AUTOFIX action must appear in the log.
393: t.CheckOutputEmpty()
394: }
395:
1.12 rillig 396: func (s *Suite) Test_Autofix__multiple_fixes(c *check.C) {
1.2 rillig 397: t := s.Init(c)
398:
1.16 rillig 399: t.SetUpCommandLine("--show-autofix", "--explain")
1.1 rillig 400:
1.13 rillig 401: line := t.NewLine("filename", 1, "original")
1.1 rillig 402:
403: c.Check(line.autofix, check.IsNil)
1.27 rillig 404: t.CheckDeepEquals(line.raw, t.NewRawLines(1, "original\n"))
1.1 rillig 405:
406: {
407: fix := line.Autofix()
1.12 rillig 408: fix.Warnf(SilentAutofixFormat)
1.3 rillig 409: fix.ReplaceRegex(`(.)(.*)(.)`, "lriginao", 1) // XXX: the replacement should be "$3$2$1"
1.1 rillig 410: fix.Apply()
411: }
412:
413: c.Check(line.autofix, check.NotNil)
1.27 rillig 414: t.CheckDeepEquals(line.raw, t.NewRawLines(1, "original\n", "lriginao\n"))
1.2 rillig 415: t.CheckOutputLines(
1.13 rillig 416: "AUTOFIX: filename:1: Replacing \"original\" with \"lriginao\".")
1.1 rillig 417:
418: {
419: fix := line.Autofix()
1.12 rillig 420: fix.Warnf(SilentAutofixFormat)
1.25 rillig 421: fix.Replace("ig", "ug")
1.1 rillig 422: fix.Apply()
423: }
424:
425: c.Check(line.autofix, check.NotNil)
1.27 rillig 426: t.CheckDeepEquals(line.raw, t.NewRawLines(1, "original\n", "lruginao\n"))
427: t.CheckEquals(line.raw[0].textnl, "lruginao\n")
1.2 rillig 428: t.CheckOutputLines(
1.25 rillig 429: "AUTOFIX: filename:1: Replacing \"ig\" with \"ug\".")
1.1 rillig 430:
431: {
432: fix := line.Autofix()
1.12 rillig 433: fix.Warnf(SilentAutofixFormat)
1.1 rillig 434: fix.Replace("lruginao", "middle")
435: fix.Apply()
436: }
437:
438: c.Check(line.autofix, check.NotNil)
1.27 rillig 439: t.CheckDeepEquals(line.raw, t.NewRawLines(1, "original\n", "middle\n"))
440: t.CheckEquals(line.raw[0].textnl, "middle\n")
1.2 rillig 441: t.CheckOutputLines(
1.13 rillig 442: "AUTOFIX: filename:1: Replacing \"lruginao\" with \"middle\".")
1.12 rillig 443:
1.27 rillig 444: t.CheckEquals(line.raw[0].textnl, "middle\n")
1.12 rillig 445: t.CheckOutputEmpty()
1.1 rillig 446:
447: {
448: fix := line.Autofix()
1.12 rillig 449: fix.Warnf(SilentAutofixFormat)
450: fix.Delete()
1.1 rillig 451: fix.Apply()
1.12 rillig 452: }
453:
1.27 rillig 454: t.CheckEquals(line.Autofix().RawText(), "")
1.12 rillig 455: t.CheckOutputLines(
1.13 rillig 456: "AUTOFIX: filename:1: Deleting this line.")
1.12 rillig 457: }
458:
459: func (s *Suite) Test_Autofix_Explain__without_explain_option(c *check.C) {
460: t := s.Init(c)
461:
462: line := t.NewLine("Makefile", 74, "line1")
1.1 rillig 463:
1.12 rillig 464: fix := line.Autofix()
465: fix.Warnf("Please write row instead of line.")
466: fix.Replace("line", "row")
467: fix.Explain("Explanation")
468: fix.Apply()
469:
470: t.CheckOutputLines(
471: "WARN: Makefile:74: Please write row instead of line.")
1.27 rillig 472: t.CheckEquals(G.Logger.explanationsAvailable, true)
1.12 rillig 473: }
474:
475: func (s *Suite) Test_Autofix_Explain__default(c *check.C) {
476: t := s.Init(c)
477:
1.16 rillig 478: t.SetUpCommandLine("--explain")
1.12 rillig 479: line := t.NewLine("Makefile", 74, "line1")
480:
481: fix := line.Autofix()
482: fix.Warnf("Please write row instead of line.")
483: fix.Replace("line", "row")
484: fix.Explain("Explanation")
485: fix.Apply()
486:
487: t.CheckOutputLines(
488: "WARN: Makefile:74: Please write row instead of line.",
489: "",
490: "\tExplanation",
491: "")
1.27 rillig 492: t.CheckEquals(G.Logger.explanationsAvailable, true)
1.12 rillig 493: }
494:
495: func (s *Suite) Test_Autofix_Explain__show_autofix(c *check.C) {
496: t := s.Init(c)
1.1 rillig 497:
1.16 rillig 498: t.SetUpCommandLine("--show-autofix", "--explain")
1.12 rillig 499: line := t.NewLine("Makefile", 74, "line1")
1.1 rillig 500:
1.12 rillig 501: fix := line.Autofix()
502: fix.Warnf("Please write row instead of line.")
503: fix.Replace("line", "row")
504: fix.Explain("Explanation")
505: fix.Apply()
1.1 rillig 506:
1.12 rillig 507: t.CheckOutputLines(
508: "WARN: Makefile:74: Please write row instead of line.",
509: "AUTOFIX: Makefile:74: Replacing \"line\" with \"row\".",
1.1 rillig 510: "",
1.12 rillig 511: "\tExplanation",
1.1 rillig 512: "")
1.27 rillig 513: t.CheckEquals(G.Logger.explanationsAvailable, true)
1.12 rillig 514: }
515:
516: func (s *Suite) Test_Autofix_Explain__autofix(c *check.C) {
517: t := s.Init(c)
518:
1.16 rillig 519: t.SetUpCommandLine("--autofix", "--explain")
1.12 rillig 520: line := t.NewLine("Makefile", 74, "line1")
521:
522: fix := line.Autofix()
523: fix.Warnf("Please write row instead of line.")
524: fix.Replace("line", "row")
525: fix.Explain("Explanation")
526: fix.Apply()
1.1 rillig 527:
1.12 rillig 528: t.CheckOutputLines(
529: "AUTOFIX: Makefile:74: Replacing \"line\" with \"row\".")
1.27 rillig 530: t.CheckEquals(G.Logger.explanationsAvailable, false) // Not necessary.
1.12 rillig 531: }
532:
533: func (s *Suite) Test_Autofix_Explain__SilentAutofixFormat(c *check.C) {
534: t := s.Init(c)
535:
1.16 rillig 536: t.SetUpCommandLine("--explain")
1.12 rillig 537: line := t.NewLine("example.txt", 1, "Text")
538:
539: fix := line.Autofix()
540: fix.Warnf(SilentAutofixFormat)
1.25 rillig 541: t.ExpectAssert(func() { fix.Explain("Explanation for inserting a line before.") })
1.12 rillig 542: }
543:
544: // To combine a silent diagnostic with an explanation, two separate autofixes
545: // are necessary.
546: func (s *Suite) Test_Autofix_Explain__silent_with_diagnostic(c *check.C) {
547: t := s.Init(c)
548:
1.16 rillig 549: t.SetUpCommandLine("--explain")
1.12 rillig 550: line := t.NewLine("example.txt", 1, "Text")
551:
552: fix := line.Autofix()
553: fix.Warnf(SilentAutofixFormat)
554: fix.InsertBefore("before")
555: fix.Apply()
556:
557: fix.Notef("This diagnostic is necessary for the following explanation.")
558: fix.Explain(
559: "When inserting multiple lines, Apply must be called in-between.",
560: "Otherwise the changes are not described to the human reader.")
561: fix.InsertAfter("after")
562: fix.Apply()
1.1 rillig 563:
1.2 rillig 564: t.CheckOutputLines(
1.12 rillig 565: "NOTE: example.txt:1: This diagnostic is necessary for the following explanation.",
566: "",
567: "\tWhen inserting multiple lines, Apply must be called in-between.",
568: "\tOtherwise the changes are not described to the human reader.",
569: "")
1.27 rillig 570: t.CheckEquals(fix.RawText(), "Text\n")
1.1 rillig 571: }
572:
1.12 rillig 573: func (s *Suite) Test_Autofix__show_autofix_and_source_continuation_line(c *check.C) {
1.2 rillig 574: t := s.Init(c)
575:
1.16 rillig 576: t.SetUpCommandLine("--show-autofix", "--source")
577: mklines := t.SetUpFileMkLines("Makefile",
1.25 rillig 578: MkCvsID,
1.9 rillig 579: "# before \\",
1.2 rillig 580: "The old song \\",
581: "after")
1.12 rillig 582: line := mklines.lines.Lines[1]
1.1 rillig 583:
1.13 rillig 584: fix := line.Autofix()
585: fix.Warnf("Using \"old\" is deprecated.")
586: fix.Replace("old", "new")
587: fix.Apply()
1.1 rillig 588:
1.13 rillig 589: // Using a tab for indentation preserves the exact layout in the output
590: // since in pkgsrc Makefiles, tabs are also used in the middle of the line
591: // to align the variable values. Using a single space for indentation would
592: // make some of the lines appear misaligned in the pkglint output although
593: // they are correct in the Makefiles.
1.2 rillig 594: t.CheckOutputLines(
1.27 rillig 595: "WARN: ~/Makefile:3: Using \"old\" is deprecated.",
1.2 rillig 596: "AUTOFIX: ~/Makefile:3: Replacing \"old\" with \"new\".",
1.13 rillig 597: "\t# before \\",
1.2 rillig 598: "-\tThe old song \\",
599: "+\tThe new song \\",
1.13 rillig 600: "\tafter")
1.1 rillig 601: }
602:
603: func (s *Suite) Test_Autofix_InsertBefore(c *check.C) {
1.2 rillig 604: t := s.Init(c)
605:
1.16 rillig 606: t.SetUpCommandLine("--show-autofix", "--source")
1.2 rillig 607: line := t.NewLine("Makefile", 30, "original")
1.1 rillig 608:
609: fix := line.Autofix()
1.6 rillig 610: fix.Warnf("Dummy.")
1.1 rillig 611: fix.InsertBefore("inserted")
612: fix.Apply()
613:
1.2 rillig 614: t.CheckOutputLines(
1.6 rillig 615: "WARN: Makefile:30: Dummy.",
1.1 rillig 616: "AUTOFIX: Makefile:30: Inserting a line \"inserted\" before this line.",
1.2 rillig 617: "+\tinserted",
618: ">\toriginal")
1.1 rillig 619: }
620:
621: func (s *Suite) Test_Autofix_Delete(c *check.C) {
1.2 rillig 622: t := s.Init(c)
623:
1.16 rillig 624: t.SetUpCommandLine("--show-autofix", "--source")
1.2 rillig 625: line := t.NewLine("Makefile", 30, "to be deleted")
1.1 rillig 626:
627: fix := line.Autofix()
1.6 rillig 628: fix.Warnf("Dummy.")
1.1 rillig 629: fix.Delete()
630: fix.Apply()
631:
1.2 rillig 632: t.CheckOutputLines(
1.6 rillig 633: "WARN: Makefile:30: Dummy.",
1.1 rillig 634: "AUTOFIX: Makefile:30: Deleting this line.",
1.2 rillig 635: "-\tto be deleted")
1.1 rillig 636: }
637:
1.12 rillig 638: // Deleting a line from a Makefile also deletes its continuation lines.
639: func (s *Suite) Test_Autofix_Delete__continuation_line(c *check.C) {
640: t := s.Init(c)
641:
1.16 rillig 642: t.SetUpCommandLine("--show-autofix", "--source")
643: mklines := t.SetUpFileMkLines("Makefile",
1.25 rillig 644: MkCvsID,
1.12 rillig 645: "# line 1 \\",
646: "continued")
647: line := mklines.lines.Lines[1]
648:
649: fix := line.Autofix()
650: fix.Warnf("Dummy warning.")
651: fix.Delete()
652: fix.Apply()
653:
654: t.CheckOutputLines(
655: "WARN: ~/Makefile:2--3: Dummy warning.",
656: "AUTOFIX: ~/Makefile:2: Deleting this line.",
657: "AUTOFIX: ~/Makefile:3: Deleting this line.",
658: "-\t# line 1 \\",
659: "-\tcontinued")
660: }
661:
662: func (s *Suite) Test_Autofix_Delete__combined_with_insert(c *check.C) {
663: t := s.Init(c)
664:
1.16 rillig 665: t.SetUpCommandLine("--show-autofix", "--source")
1.12 rillig 666: line := t.NewLine("Makefile", 30, "to be deleted")
667:
668: fix := line.Autofix()
669: fix.Warnf("This line should be replaced completely.")
670: fix.Delete()
671: fix.InsertAfter("below")
672: fix.InsertBefore("above")
673: fix.Apply()
674:
675: t.CheckOutputLines(
676: "WARN: Makefile:30: This line should be replaced completely.",
677: "AUTOFIX: Makefile:30: Deleting this line.",
678: "AUTOFIX: Makefile:30: Inserting a line \"below\" after this line.",
679: "AUTOFIX: Makefile:30: Inserting a line \"above\" before this line.",
680: "+\tabove",
681: "-\tto be deleted",
682: "+\tbelow")
683: }
684:
1.17 rillig 685: // Demonstrates that without the --show-autofix option, diagnostics are
686: // shown even when they cannot be autofixed.
687: //
688: // This is typical when an autofix is provided for simple scenarios,
689: // but the code actually found is a little more complicated.
690: func (s *Suite) Test_Autofix__show_unfixable_diagnostics_in_default_mode(c *check.C) {
691: t := s.Init(c)
692:
693: t.SetUpCommandLine("--source")
694: lines := t.NewLines("Makefile",
695: "line1",
696: "line2",
697: "line3")
698:
699: lines.Lines[0].Warnf("This warning is shown since the --show-autofix option is not given.")
700:
701: fix := lines.Lines[1].Autofix()
702: fix.Warnf("This warning cannot be fixed and is therefore not shown.")
703: fix.Replace("XXX", "TODO")
704: fix.Apply()
705:
706: fix.Warnf("This warning cannot be fixed automatically but should be shown anyway.")
707: fix.Replace("XXX", "TODO")
708: fix.Anyway()
709: fix.Apply()
710:
711: // If this warning should ever appear it is probably because fix.anyway is not reset properly.
712: fix.Warnf("This warning cannot be fixed and is therefore not shown.")
713: fix.Replace("XXX", "TODO")
714: fix.Apply()
715:
716: lines.Lines[2].Warnf("This warning is also shown.")
717:
718: t.CheckOutputLines(
719: ">\tline1",
720: "WARN: Makefile:1: This warning is shown since the --show-autofix option is not given.",
721: "",
722: ">\tline2",
723: "WARN: Makefile:2: This warning cannot be fixed automatically but should be shown anyway.",
724: "",
725: ">\tline3",
726: "WARN: Makefile:3: This warning is also shown.")
727: }
728:
1.1 rillig 729: // Demonstrates that the --show-autofix option only shows those diagnostics
730: // that would be fixed.
1.17 rillig 731: func (s *Suite) Test_Autofix__suppress_unfixable_warnings_with_show_autofix(c *check.C) {
1.2 rillig 732: t := s.Init(c)
733:
1.16 rillig 734: t.SetUpCommandLine("--show-autofix", "--source")
1.2 rillig 735: lines := t.NewLines("Makefile",
1.1 rillig 736: "line1",
737: "line2",
738: "line3")
739:
1.12 rillig 740: lines.Lines[0].Warnf("This warning is not shown since it is not part of a fix.")
1.1 rillig 741:
1.12 rillig 742: fix := lines.Lines[1].Autofix()
1.1 rillig 743: fix.Warnf("Something's wrong here.")
1.12 rillig 744: fix.ReplaceRegex(`.....`, "XXX", 1)
1.1 rillig 745: fix.Apply()
746:
1.11 rillig 747: fix.Warnf("Since XXX marks are usually not fixed, use TODO instead to draw attention.")
1.1 rillig 748: fix.Replace("XXX", "TODO")
749: fix.Apply()
750:
1.12 rillig 751: lines.Lines[2].Warnf("Neither is this warning shown.")
1.1 rillig 752:
1.2 rillig 753: t.CheckOutputLines(
1.1 rillig 754: "WARN: Makefile:2: Something's wrong here.",
1.12 rillig 755: "AUTOFIX: Makefile:2: Replacing \"line2\" with \"XXX\".",
1.2 rillig 756: "-\tline2",
1.12 rillig 757: "+\tXXX",
1.1 rillig 758: "",
1.11 rillig 759: "WARN: Makefile:2: Since XXX marks are usually not fixed, use TODO instead to draw attention.",
1.1 rillig 760: "AUTOFIX: Makefile:2: Replacing \"XXX\" with \"TODO\".",
1.2 rillig 761: "-\tline2",
1.12 rillig 762: "+\tTODO")
1.2 rillig 763: }
764:
1.18 rillig 765: // With the default command line options, this warning is printed.
766: // With the --show-autofix option this warning is NOT printed since it
767: // cannot be fixed automatically.
768: func (s *Suite) Test_Autofix_Anyway__options(c *check.C) {
769: t := s.Init(c)
770:
771: t.SetUpCommandLine("--show-autofix")
772:
773: line := t.NewLine("filename", 3, "")
774: fix := line.Autofix()
775:
776: fix.Warnf("This autofix doesn't match.")
777: fix.Replace("doesn't match", "")
778: fix.Anyway()
779: fix.Apply()
780:
781: t.CheckOutputEmpty()
782:
783: t.SetUpCommandLine()
784:
785: fix.Warnf("This autofix doesn't match.")
786: fix.Replace("doesn't match", "")
787: fix.Anyway()
788: fix.Apply()
789:
790: t.CheckOutputLines(
791: "WARN: filename:3: This autofix doesn't match.")
792: }
793:
1.12 rillig 794: // If an Autofix doesn't do anything, it must not log any diagnostics.
1.11 rillig 795: func (s *Suite) Test_Autofix__noop_replace(c *check.C) {
1.2 rillig 796: t := s.Init(c)
797:
798: line := t.NewLine("Makefile", 14, "Original text")
799:
800: fix := line.Autofix()
801: fix.Warnf("All-uppercase words should not be used at all.")
1.3 rillig 802: fix.ReplaceRegex(`\b[A-Z]{3,}\b`, "---censored---", -1)
1.2 rillig 803: fix.Apply()
804:
805: // No output since there was no all-uppercase word in the text.
806: t.CheckOutputEmpty()
1.1 rillig 807: }
808:
1.12 rillig 809: // When using Autofix.Custom, it is tricky to get all the details right.
1.11 rillig 810: // For best results, see the existing examples and the documentation.
1.12 rillig 811: //
812: // Since this custom fix only operates on the text of the current line,
813: // it can handle both the --show-autofix and the --autofix cases using
814: // the same code.
815: func (s *Suite) Test_Autofix_Custom__in_memory(c *check.C) {
1.5 rillig 816: t := s.Init(c)
817:
818: lines := t.NewLines("Makefile",
819: "line1",
820: "line2",
821: "line3")
822:
1.25 rillig 823: doFix := func(line *Line) {
1.5 rillig 824: fix := line.Autofix()
1.6 rillig 825: fix.Warnf("Please write in ALL-UPPERCASE.")
1.12 rillig 826: fix.Custom(func(showAutofix, autofix bool) {
1.5 rillig 827: fix.Describef(int(line.firstLine), "Converting to uppercase")
1.12 rillig 828: if showAutofix || autofix {
1.5 rillig 829: line.Text = strings.ToUpper(line.Text)
830: }
831: })
832: fix.Apply()
833: }
834:
1.12 rillig 835: doFix(lines.Lines[0])
1.5 rillig 836:
837: t.CheckOutputLines(
1.6 rillig 838: "WARN: Makefile:1: Please write in ALL-UPPERCASE.")
1.5 rillig 839:
1.16 rillig 840: t.SetUpCommandLine("--show-autofix")
1.5 rillig 841:
1.12 rillig 842: doFix(lines.Lines[1])
1.5 rillig 843:
844: t.CheckOutputLines(
1.6 rillig 845: "WARN: Makefile:2: Please write in ALL-UPPERCASE.",
1.5 rillig 846: "AUTOFIX: Makefile:2: Converting to uppercase")
1.27 rillig 847: t.CheckEquals(lines.Lines[1].Text, "LINE2")
1.5 rillig 848:
1.16 rillig 849: t.SetUpCommandLine("--autofix")
1.5 rillig 850:
1.12 rillig 851: doFix(lines.Lines[2])
1.5 rillig 852:
853: t.CheckOutputLines(
854: "AUTOFIX: Makefile:3: Converting to uppercase")
1.27 rillig 855: t.CheckEquals(lines.Lines[2].Text, "LINE3")
1.6 rillig 856: }
1.8 rillig 857:
858: // Since the diagnostic doesn't contain the string "few", nothing happens.
1.12 rillig 859: func (s *Suite) Test_Autofix_skip(c *check.C) {
1.8 rillig 860: t := s.Init(c)
861:
1.16 rillig 862: t.SetUpCommandLine("--only", "few", "--autofix")
1.8 rillig 863:
1.16 rillig 864: mklines := t.SetUpFileMkLines("filename",
1.12 rillig 865: "VAR=\t111 222 333 444 555 \\",
866: "666")
867: lines := mklines.lines
1.8 rillig 868:
1.12 rillig 869: fix := lines.Lines[0].Autofix()
1.8 rillig 870: fix.Warnf("Many.")
871: fix.Explain(
872: "Explanation.")
1.12 rillig 873:
874: // None of the following actions has any effect because of the --only option above.
1.8 rillig 875: fix.Replace("111", "___")
876: fix.ReplaceAfter(" ", "222", "___")
1.27 rillig 877: fix.ReplaceAt(0, 0, "VAR", "NEW")
1.8 rillig 878: fix.ReplaceRegex(`\d+`, "___", 1)
879: fix.InsertBefore("before")
880: fix.InsertAfter("after")
881: fix.Delete()
1.12 rillig 882: fix.Custom(func(showAutofix, autofix bool) {})
883:
1.8 rillig 884: fix.Apply()
885:
886: SaveAutofixChanges(lines)
887:
888: t.CheckOutputEmpty()
1.13 rillig 889: t.CheckFileLines("filename",
1.12 rillig 890: "VAR=\t111 222 333 444 555 \\",
891: "666")
1.27 rillig 892: t.CheckEquals(fix.RawText(), ""+
1.12 rillig 893: "VAR=\t111 222 333 444 555 \\\n"+
894: "666\n")
895: }
896:
897: // Demonstrates how to filter log messages.
898: // The --autofix option can restrict the fixes to exactly one group or topic.
899: func (s *Suite) Test_Autofix_Apply__only(c *check.C) {
900: t := s.Init(c)
901:
1.16 rillig 902: t.SetUpCommandLine("--autofix", "--source", "--only", "interesting")
1.12 rillig 903: line := t.NewLine("Makefile", 27, "The old song")
904:
905: // Is completely ignored, including any autofixes.
906: fix := line.Autofix()
907: fix.Warnf("Using \"old\" is deprecated.")
908: fix.Replace("old", "new1")
909: fix.Apply()
910:
911: fix.Warnf("Using \"old\" is interesting.")
912: fix.Replace("old", "new2")
913: fix.Apply()
914:
915: t.CheckOutputLines(
916: "AUTOFIX: Makefile:27: Replacing \"old\" with \"new2\".",
917: "-\tThe old song",
918: "+\tThe new2 song")
1.8 rillig 919: }
1.10 rillig 920:
921: func (s *Suite) Test_Autofix_Apply__panic(c *check.C) {
922: t := s.Init(c)
923:
1.13 rillig 924: line := t.NewLine("filename", 123, "text")
1.12 rillig 925:
1.25 rillig 926: t.ExpectAssert(
1.12 rillig 927: func() {
928: fix := line.Autofix()
929: fix.Apply()
1.25 rillig 930: })
1.12 rillig 931:
1.25 rillig 932: t.ExpectAssert(
1.12 rillig 933: func() {
934: fix := line.Autofix()
935: fix.Replace("from", "to")
936: fix.Apply()
1.25 rillig 937: })
1.12 rillig 938:
939: t.ExpectPanic(
940: func() {
941: fix := line.Autofix()
942: fix.Warnf("Warning without period")
943: fix.Apply()
944: },
945: "Pkglint internal error: Autofix: format \"Warning without period\" must end with a period.")
946: }
1.10 rillig 947:
1.12 rillig 948: // Ensures that empty lines are logged between the diagnostics,
949: // even when combining normal warnings and autofix warnings.
950: //
951: // Up to 2018-10-27, pkglint didn't insert the required empty line in this case.
952: func (s *Suite) Test_Autofix_Apply__explanation_followed_by_note(c *check.C) {
953: t := s.Init(c)
954:
1.16 rillig 955: t.SetUpCommandLine("--source")
1.12 rillig 956: line := t.NewLine("README.txt", 123, "text")
957:
958: fix := line.Autofix()
959: fix.Warnf("A warning with autofix.")
960: fix.Explain("Explanation.")
961: fix.Replace("text", "Text")
962: fix.Apply()
1.10 rillig 963:
1.12 rillig 964: line.Notef("A note without fix.")
1.10 rillig 965:
1.12 rillig 966: t.CheckOutputLines(
967: ">\ttext",
968: "WARN: README.txt:123: A warning with autofix.",
969: "",
970: ">\ttext",
971: "NOTE: README.txt:123: A note without fix.")
1.10 rillig 972: }
973:
1.18 rillig 974: func (s *Suite) Test_Autofix_Anyway__autofix_option(c *check.C) {
975: t := s.Init(c)
976:
977: t.SetUpCommandLine("--autofix")
978: line := t.NewLine("filename", 5, "text")
979:
980: fix := line.Autofix()
981: fix.Notef("This line is quite short.")
982: fix.Replace("not found", "needle")
983: fix.Anyway()
984: fix.Apply()
985:
986: // The replacement text is not found, therefore the note should not be logged.
987: // Because of fix.Anyway, the note should be logged anyway.
988: // But because of the --autofix option, the note should not be logged.
989: // Therefore, in the end nothing is shown in this case.
990: t.CheckOutputEmpty()
991: }
992:
993: func (s *Suite) Test_Autofix_Anyway__autofix_and_show_autofix_options(c *check.C) {
994: t := s.Init(c)
995:
996: t.SetUpCommandLine("--autofix", "--show-autofix")
997: line := t.NewLine("filename", 5, "text")
998:
999: fix := line.Autofix()
1000: fix.Notef("This line is quite short.")
1001: fix.Replace("not found", "needle")
1002: fix.Anyway()
1003: fix.Apply()
1004:
1.21 rillig 1005: // The text to be replaced is not found. Because nothing is fixed here,
1006: // there's no need to log anything.
1007: //
1008: // But fix.Anyway is set, therefore the diagnostic should be logged even
1009: // though it cannot be fixed automatically. This comes handy in situations
1010: // where simple cases can be fixed automatically and more complex cases
1011: // (often involving special characters that need to be escaped properly)
1012: // should nevertheless result in a diagnostics.
1013: //
1014: // The --autofix option is set, which means that the diagnostics don't
1015: // get logged, only the actual fixes do.
1016: //
1017: // But then there's also the --show-autofix option, which logs the
1018: // corresponding diagnostic for each autofix that actually changes
1019: // something. But this autofix doesn't change anything, therefore even
1020: // the --show-autofix doesn't have an effect.
1021: //
1022: // Therefore, in the end nothing is logged in this case.
1.18 rillig 1023: t.CheckOutputEmpty()
1024: }
1025:
1026: // The --autofix option normally suppresses the diagnostics and just logs
1027: // the actual fixes. Adding the --show-autofix option logs both.
1028: func (s *Suite) Test_Autofix_Apply__autofix_and_show_autofix_options(c *check.C) {
1029: t := s.Init(c)
1030:
1031: t.SetUpCommandLine("--autofix", "--show-autofix")
1032: line := t.NewLine("filename", 5, "text")
1033:
1034: fix := line.Autofix()
1035: fix.Notef("This line is quite short.")
1036: fix.Replace("text", "replacement")
1037: fix.Apply()
1038:
1039: t.CheckOutputLines(
1040: "NOTE: filename:5: This line is quite short.",
1041: "AUTOFIX: filename:5: Replacing \"text\" with \"replacement\".")
1042: }
1043:
1.25 rillig 1044: // In --autofix mode or --show-autofix mode, the fix.Anyway doesn't
1045: // have any effect, therefore the errors from such autofixes are
1046: // not counted, and the exitcode stays at 0.
1047: func (s *Suite) Test_Autofix_Apply__anyway_error(c *check.C) {
1048: t := s.Init(c)
1049:
1050: t.SetUpCommandLine("--autofix")
1051: mklines := t.SetUpFileMkLines("filename.mk",
1052: MkCvsID,
1053: "VAR=\tvalue")
1054:
1055: fix := mklines.mklines[1].Autofix()
1056: fix.Errorf("From must be To.")
1057: fix.Replace("from", "to")
1058: fix.Anyway()
1059: fix.Apply()
1060:
1061: mklines.SaveAutofixChanges()
1062:
1.27 rillig 1063: t.CheckEquals(G.Logger.errors, 0)
1.25 rillig 1064: t.CheckOutputEmpty()
1065: }
1066:
1.26 rillig 1067: func (s *Suite) Test_Autofix_Apply__source_autofix_no_change(c *check.C) {
1068: t := s.Init(c)
1069:
1070: t.SetUpCommandLine("--autofix", "--source")
1071: lines := t.SetUpFileLines("filename",
1072: "word word word")
1073:
1074: fix := lines.Lines[0].Autofix()
1075: fix.Notef("Word should be replaced, but pkglint is not sure which one.")
1076: fix.Replace("word", "replacement")
1077: fix.Anyway()
1078: fix.Apply()
1079:
1080: lines.SaveAutofixChanges()
1081:
1082: // Nothing is replaced since, as of June 2019, pkglint doesn't
1083: // know which of the three "word" should be replaced.
1084: //
1085: // The note is not logged since fix.Anyway only applies when neither
1086: // --show-autofix nor --autofix is given in the command line.
1087: t.CheckOutputEmpty()
1088: t.CheckFileLines("filename",
1089: "word word word")
1090: }
1091:
1.18 rillig 1092: // Ensures that without explanations, the separator between the individual
1093: // diagnostics are generated.
1094: func (s *Suite) Test_Autofix_Apply__source_without_explain(c *check.C) {
1095: t := s.Init(c)
1096:
1097: t.SetUpCommandLine("--source", "--explain", "--show-autofix")
1098: line := t.NewLine("filename", 5, "text")
1099:
1100: fix := line.Autofix()
1101: fix.Notef("This line is quite short.")
1102: fix.Replace("text", "replacement")
1103: fix.Apply()
1104:
1105: fix.Warnf("Follow-up warning, separated.")
1106: fix.Replace("replacement", "text again")
1107: fix.Apply()
1108:
1109: t.CheckOutputLines(
1110: "NOTE: filename:5: This line is quite short.",
1111: "AUTOFIX: filename:5: Replacing \"text\" with \"replacement\".",
1112: "-\ttext",
1113: "+\treplacement",
1114: "",
1115: "WARN: filename:5: Follow-up warning, separated.",
1116: "AUTOFIX: filename:5: Replacing \"replacement\" with \"text again\".",
1117: "-\ttext",
1118: "+\ttext again")
1119: }
1120:
1.24 rillig 1121: // After fixing part of a line, the whole line needs to be parsed again.
1122: //
1123: // As of May 2019, this is not done yet.
1124: func (s *Suite) Test_Autofix_Apply__text_after_replacing_string(c *check.C) {
1125: t := s.Init(c)
1126:
1127: t.SetUpCommandLine("-Wall", "--autofix")
1128: mkline := t.NewMkLine("filename.mk", 123, "VAR=\tvalue")
1129:
1130: fix := mkline.Autofix()
1131: fix.Notef("Just a demo.")
1132: fix.Replace("value", "new value")
1133: fix.Apply()
1134:
1135: t.CheckOutputLines(
1136: "AUTOFIX: filename.mk:123: Replacing \"value\" with \"new value\".")
1137:
1.27 rillig 1138: t.CheckEquals(mkline.raw[0].textnl, "VAR=\tnew value\n")
1139: t.CheckEquals(mkline.raw[0].orignl, "VAR=\tvalue\n")
1140: t.CheckEquals(mkline.Text, "VAR=\tnew value")
1141: // TODO: should be updated as well.
1142: t.CheckEquals(mkline.Value(), "value")
1.24 rillig 1143: }
1144:
1145: // After fixing part of a line, the whole line needs to be parsed again.
1146: //
1147: // As of May 2019, this is not done yet.
1148: func (s *Suite) Test_Autofix_Apply__text_after_replacing_regex(c *check.C) {
1149: t := s.Init(c)
1150:
1151: t.SetUpCommandLine("-Wall", "--autofix")
1152: mkline := t.NewMkLine("filename.mk", 123, "VAR=\tvalue")
1153:
1154: fix := mkline.Autofix()
1155: fix.Notef("Just a demo.")
1156: fix.ReplaceRegex(`va...`, "new value", -1)
1157: fix.Apply()
1158:
1159: t.CheckOutputLines(
1160: "AUTOFIX: filename.mk:123: Replacing \"value\" with \"new value\".")
1161:
1.27 rillig 1162: t.CheckEquals(mkline.raw[0].textnl, "VAR=\tnew value\n")
1163: t.CheckEquals(mkline.raw[0].orignl, "VAR=\tvalue\n")
1164: t.CheckEquals(mkline.Text, "VAR=\tnew value")
1165: // TODO: should be updated as well.
1166: t.CheckEquals(mkline.Value(), "value")
1.18 rillig 1167: }
1168:
1.28 ! rillig 1169: // Pkglint tries to order the diagnostics from top to bottom.
! 1170: // Still, it could be possible that in a multiline the second line
! 1171: // gets a diagnostic before the first line. This only happens when
! 1172: // both replacements happen in the same autofix block, which doesn't
! 1173: // happen often.
! 1174: //
! 1175: // This covers the "action.lineno < first" condition.
! 1176: func (s *Suite) Test_Autofix_affectedLinenos__reverse(c *check.C) {
! 1177: t := s.Init(c)
! 1178:
! 1179: test := func(diagnostics ...string) {
! 1180: mklines := t.NewMkLines("filename.mk",
! 1181: "VAR=\tline 1 \\",
! 1182: "\tline 2")
! 1183: mkline := mklines.mklines[0]
! 1184:
! 1185: fix := mkline.Autofix()
! 1186: fix.Warnf("Replacements from bottom to top.")
! 1187: fix.Replace("line 2", "bbb")
! 1188: fix.Replace("line 1", "aaa")
! 1189: fix.Apply()
! 1190:
! 1191: t.CheckOutput(diagnostics)
! 1192: }
! 1193:
! 1194: t.SetUpCommandLine("--source")
! 1195: test(
! 1196: ">\tVAR=\tline 1 \\",
! 1197: ">\t\tline 2",
! 1198: "WARN: filename.mk:1--2: Replacements from bottom to top.")
! 1199:
! 1200: t.SetUpCommandLine("--source", "--show-autofix")
! 1201: test(
! 1202: "WARN: filename.mk:1--2: Replacements from bottom to top.",
! 1203: "AUTOFIX: filename.mk:2: Replacing \"line 2\" with \"bbb\".",
! 1204: "AUTOFIX: filename.mk:1: Replacing \"line 1\" with \"aaa\".",
! 1205: "-\tVAR=\tline 1 \\",
! 1206: "+\tVAR=\taaa \\",
! 1207: "-\t\tline 2",
! 1208: "+\t\tbbb")
! 1209: }
! 1210:
1.18 rillig 1211: // Just for branch coverage.
1212: func (s *Suite) Test_Autofix_setDiag__no_testing_mode(c *check.C) {
1213: t := s.Init(c)
1214:
1215: line := t.NewLine("file.mk", 123, "text")
1216:
1217: G.Testing = false
1218:
1219: fix := line.Autofix()
1220: fix.Notef("Note.")
1221: fix.Replace("from", "to")
1222: fix.Apply()
1223:
1224: t.CheckOutputEmpty()
1225: }
1226:
1227: func (s *Suite) Test_Autofix_setDiag__bad_call_sequence(c *check.C) {
1228: t := s.Init(c)
1229:
1230: line := t.NewLine("file.mk", 123, "text")
1231: fix := line.Autofix()
1232: fix.Notef("Note.")
1233:
1.25 rillig 1234: t.ExpectAssert(func() { fix.Notef("Note 2.") })
1.18 rillig 1235:
1236: fix.level = nil // To cover the second assertion.
1.25 rillig 1237: t.ExpectAssert(func() { fix.Notef("Note 2.") })
1.18 rillig 1238: }
1239:
1240: func (s *Suite) Test_Autofix_assertRealLine(c *check.C) {
1241: t := s.Init(c)
1242:
1243: line := NewLineEOF("filename.mk")
1244: fix := line.Autofix()
1245: fix.Warnf("Warning.")
1246:
1.25 rillig 1247: t.ExpectAssert(func() { fix.Replace("from", "to") })
1.18 rillig 1248: }
1249:
1.12 rillig 1250: func (s *Suite) Test_SaveAutofixChanges__file_removed(c *check.C) {
1.10 rillig 1251: t := s.Init(c)
1252:
1.16 rillig 1253: t.SetUpCommandLine("--autofix")
1254: lines := t.SetUpFileLines("subdir/file.txt",
1.10 rillig 1255: "line 1")
1.12 rillig 1256: _ = os.RemoveAll(t.File("subdir"))
1.10 rillig 1257:
1.12 rillig 1258: fix := lines.Lines[0].Autofix()
1.10 rillig 1259: fix.Warnf("Should start with an uppercase letter.")
1260: fix.Replace("line", "Line")
1261: fix.Apply()
1262:
1263: SaveAutofixChanges(lines)
1264:
1.22 rillig 1265: t.CheckOutputMatches(
1266: "AUTOFIX: ~/subdir/file.txt:1: Replacing \"line\" with \"Line\".",
1267: `ERROR: ~/subdir/file.txt.pkglint.tmp: Cannot write: .*`)
1.10 rillig 1268: }
1269:
1.12 rillig 1270: func (s *Suite) Test_SaveAutofixChanges__file_busy_Windows(c *check.C) {
1.10 rillig 1271: t := s.Init(c)
1272:
1273: if runtime.GOOS != "windows" {
1274: return
1275: }
1276:
1.16 rillig 1277: t.SetUpCommandLine("--autofix")
1278: lines := t.SetUpFileLines("subdir/file.txt",
1.10 rillig 1279: "line 1")
1280:
1281: // As long as the file is kept open, it cannot be overwritten or deleted.
1282: openFile, err := os.OpenFile(t.File("subdir/file.txt"), 0, 0666)
1.25 rillig 1283: defer func() { assertNil(openFile.Close(), "") }()
1.10 rillig 1284: c.Check(err, check.IsNil)
1285:
1.12 rillig 1286: fix := lines.Lines[0].Autofix()
1.10 rillig 1287: fix.Warnf("Should start with an uppercase letter.")
1288: fix.Replace("line", "Line")
1289: fix.Apply()
1290:
1291: SaveAutofixChanges(lines)
1292:
1.22 rillig 1293: t.CheckOutputMatches(
1294: "AUTOFIX: ~/subdir/file.txt:1: Replacing \"line\" with \"Line\".",
1295: `ERROR: ~/subdir/file.txt.pkglint.tmp: Cannot overwrite with autofixed content: .*`)
1.10 rillig 1296: }
1297:
1.12 rillig 1298: // This test covers the highly unlikely situation in which a file is loaded
1.10 rillig 1299: // by pkglint, and just before writing the autofixed content back, another
1300: // process takes the file and replaces it with a directory of the same name.
1301: //
1302: // 100% code coverage sometimes requires creativity. :)
1.12 rillig 1303: func (s *Suite) Test_SaveAutofixChanges__cannot_overwrite(c *check.C) {
1.10 rillig 1304: t := s.Init(c)
1305:
1.16 rillig 1306: t.SetUpCommandLine("--autofix")
1307: lines := t.SetUpFileLines("file.txt",
1.10 rillig 1308: "line 1")
1309:
1310: c.Check(os.RemoveAll(t.File("file.txt")), check.IsNil)
1311: c.Check(os.MkdirAll(t.File("file.txt"), 0777), check.IsNil)
1312:
1.12 rillig 1313: fix := lines.Lines[0].Autofix()
1.10 rillig 1314: fix.Warnf("Should start with an uppercase letter.")
1315: fix.Replace("line", "Line")
1316: fix.Apply()
1317:
1318: SaveAutofixChanges(lines)
1319:
1.22 rillig 1320: t.CheckOutputMatches(
1321: "AUTOFIX: ~/file.txt:1: Replacing \"line\" with \"Line\".",
1322: `ERROR: ~/file.txt.pkglint.tmp: Cannot overwrite with autofixed content: .*`)
1.12 rillig 1323: }
1324:
1.13 rillig 1325: // Up to 2018-11-25, pkglint in some cases logged only the source without
1326: // a corresponding warning.
1327: func (s *Suite) Test_Autofix__lonely_source(c *check.C) {
1328: t := s.Init(c)
1329:
1.16 rillig 1330: t.SetUpCommandLine("-Wall", "--source")
1.13 rillig 1331: G.Logger.Opts.LogVerbose = false // For realistic conditions; otherwise all diagnostics are logged.
1332:
1.16 rillig 1333: t.SetUpPackage("x11/xorg-cf-files",
1.13 rillig 1334: ".include \"../../x11/xorgproto/buildlink3.mk\"")
1.16 rillig 1335: t.SetUpPackage("x11/xorgproto",
1.13 rillig 1336: "DISTNAME=\txorgproto-1.0")
1337: t.CreateFileDummyBuildlink3("x11/xorgproto/buildlink3.mk")
1338: t.CreateFileLines("x11/xorgproto/builtin.mk",
1.25 rillig 1339: MkCvsID,
1.13 rillig 1340: "",
1341: "BUILTIN_PKG:=\txorgproto",
1342: "",
1343: "PRE_XORGPROTO_LIST_MISSING =\tapplewmproto",
1344: "",
1345: ".for id in ${PRE_XORGPROTO_LIST_MISSING}",
1346: ".endfor")
1347: t.Chdir(".")
1.20 rillig 1348: t.FinishSetUp()
1.13 rillig 1349:
1.15 rillig 1350: G.Check("x11/xorg-cf-files")
1351: G.Check("x11/xorgproto")
1.13 rillig 1352:
1353: t.CheckOutputLines(
1354: ">\tPRE_XORGPROTO_LIST_MISSING =\tapplewmproto",
1355: "NOTE: x11/xorgproto/builtin.mk:5: Unnecessary space after variable name \"PRE_XORGPROTO_LIST_MISSING\".")
1356: }
1357:
1358: // Up to 2018-11-26, pkglint in some cases logged only the source without
1359: // a corresponding warning.
1360: func (s *Suite) Test_Autofix__lonely_source_2(c *check.C) {
1361: t := s.Init(c)
1362:
1.16 rillig 1363: t.SetUpCommandLine("-Wall", "--source", "--explain")
1.13 rillig 1364: G.Logger.Opts.LogVerbose = false // For realistic conditions; otherwise all diagnostics are logged.
1365:
1.16 rillig 1366: t.SetUpPackage("print/tex-bibtex8",
1.13 rillig 1367: "MAKE_FLAGS+=\tCFLAGS=${CFLAGS.${PKGSRC_COMPILER}}")
1368: t.Chdir(".")
1.20 rillig 1369: t.FinishSetUp()
1.13 rillig 1370:
1.15 rillig 1371: G.Check("print/tex-bibtex8")
1.13 rillig 1372:
1373: t.CheckOutputLines(
1374: ">\tMAKE_FLAGS+=\tCFLAGS=${CFLAGS.${PKGSRC_COMPILER}}",
1375: "WARN: print/tex-bibtex8/Makefile:20: Please use ${CFLAGS.${PKGSRC_COMPILER}:Q} instead of ${CFLAGS.${PKGSRC_COMPILER}}.",
1376: "",
1377: "\tSee the pkgsrc guide, section \"Echoing a string exactly as-is\":",
1378: "\thttps://www.NetBSD.org/docs/pkgsrc/pkgsrc.html#echo-literal",
1379: "",
1380: ">\tMAKE_FLAGS+=\tCFLAGS=${CFLAGS.${PKGSRC_COMPILER}}",
1381: "WARN: print/tex-bibtex8/Makefile:20: The list variable PKGSRC_COMPILER should not be embedded in a word.",
1382: "",
1383: "\tWhen a list variable has multiple elements, this expression expands",
1384: "\tto something unexpected:",
1385: "",
1386: "\tExample: ${MASTER_SITE_SOURCEFORGE}directory/ expands to",
1387: "",
1388: "\t\thttps://mirror1.sf.net/ https://mirror2.sf.net/directory/",
1389: "",
1.14 rillig 1390: "\tThe first URL is missing the directory. To fix this, write",
1.13 rillig 1391: "\t\t${MASTER_SITE_SOURCEFORGE:=directory/}.",
1392: "",
1393: "\tExample: -l${LIBS} expands to",
1394: "",
1395: "\t\t-llib1 lib2",
1396: "",
1.14 rillig 1397: "\tThe second library is missing the -l. To fix this, write",
1398: "\t${LIBS:S,^,-l,}.",
1.13 rillig 1399: "")
1400: }
1401:
1.12 rillig 1402: // RawText returns the raw text of the fixed line, including line ends.
1403: // This may differ from the original text when the --show-autofix
1404: // or --autofix options are enabled.
1405: func (fix *Autofix) RawText() string {
1406: var text strings.Builder
1407: for _, lineBefore := range fix.linesBefore {
1408: text.WriteString(lineBefore)
1409: }
1410: for _, raw := range fix.line.raw {
1411: text.WriteString(raw.textnl)
1412: }
1413: for _, lineAfter := range fix.linesAfter {
1414: text.WriteString(lineAfter)
1415: }
1416: return text.String()
1.10 rillig 1417: }
CVSweb <webmaster@jp.NetBSD.org>