[BACK]Return to autofix_test.go CVS log [TXT][DIR] Up to [cvs.NetBSD.org] / pkgsrc / pkgtools / pkglint / files

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>