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

Annotation of pkgsrc/pkgtools/pkglint/files/pkglint.go, Revision 1.26

1.1       rillig      1: package main
                      2:
                      3: import (
1.17      rillig      4:        "fmt"
                      5:        "netbsd.org/pkglint/getopt"
1.18      rillig      6:        "netbsd.org/pkglint/histogram"
                      7:        "netbsd.org/pkglint/regex"
                      8:        "netbsd.org/pkglint/trace"
1.1       rillig      9:        "os"
1.17      rillig     10:        "os/user"
1.1       rillig     11:        "path"
1.17      rillig     12:        "path/filepath"
                     13:        "runtime/pprof"
1.1       rillig     14:        "strings"
                     15: )
                     16:
1.17      rillig     17: const confMake = "@BMAKE@"
                     18: const confVersion = "@VERSION@"
                     19:
                     20: func main() {
1.25      rillig     21:        G.logOut = NewSeparatorWriter(os.Stdout)
                     22:        G.logErr = NewSeparatorWriter(os.Stderr)
                     23:        trace.Out = os.Stdout
1.17      rillig     24:        os.Exit(new(Pkglint).Main(os.Args...))
                     25: }
                     26:
                     27: type Pkglint struct{}
                     28:
                     29: func (pkglint *Pkglint) Main(args ...string) (exitcode int) {
                     30:        defer func() {
                     31:                if r := recover(); r != nil {
                     32:                        if _, ok := r.(pkglintFatal); ok {
                     33:                                exitcode = 1
                     34:                        } else {
                     35:                                panic(r)
                     36:                        }
                     37:                }
                     38:        }()
                     39:
                     40:        if exitcode := pkglint.ParseCommandLine(args); exitcode != nil {
                     41:                return *exitcode
                     42:        }
                     43:
                     44:        if G.opts.Profiling {
                     45:                f, err := os.Create("pkglint.pprof")
                     46:                if err != nil {
                     47:                        dummyLine.Fatalf("Cannot create profiling file: %s", err)
                     48:                }
                     49:                pprof.StartCPUProfile(f)
                     50:                defer pprof.StopCPUProfile()
                     51:
1.18      rillig     52:                regex.Profiling = true
                     53:                G.loghisto = histogram.New()
                     54:                defer func() {
1.24      rillig     55:                        G.logOut.Write("")
                     56:                        G.loghisto.PrintStats("loghisto", G.logOut.out, 0)
1.18      rillig     57:                        regex.PrintStats()
                     58:                }()
1.17      rillig     59:        }
                     60:
                     61:        for _, arg := range G.opts.args {
                     62:                G.Todo = append(G.Todo, filepath.ToSlash(arg))
                     63:        }
                     64:        if len(G.Todo) == 0 {
                     65:                G.Todo = []string{"."}
                     66:        }
                     67:
                     68:        G.globalData.Initialize()
                     69:
                     70:        currentUser, err := user.Current()
                     71:        if err == nil {
                     72:                // On Windows, this is `Computername\Username`.
1.18      rillig     73:                G.CurrentUsername = regex.Compile(`^.*\\`).ReplaceAllString(currentUser.Username, "")
1.17      rillig     74:        }
                     75:
                     76:        for len(G.Todo) != 0 {
                     77:                item := G.Todo[0]
                     78:                G.Todo = G.Todo[1:]
                     79:                pkglint.CheckDirent(item)
                     80:        }
                     81:
                     82:        checkToplevelUnusedLicenses()
                     83:        pkglint.PrintSummary()
                     84:        if G.errors != 0 {
                     85:                return 1
                     86:        }
                     87:        return 0
                     88: }
                     89:
                     90: func (pkglint *Pkglint) ParseCommandLine(args []string) *int {
                     91:        gopts := &G.opts
                     92:        opts := getopt.NewOptions()
                     93:
                     94:        check := opts.AddFlagGroup('C', "check", "check,...", "enable or disable specific checks")
1.18      rillig     95:        opts.AddFlagVar('d', "debug", &trace.Tracing, false, "log verbose call traces for debugging")
1.17      rillig     96:        opts.AddFlagVar('e', "explain", &gopts.Explain, false, "explain the diagnostics or give further help")
                     97:        opts.AddFlagVar('f', "show-autofix", &gopts.PrintAutofix, false, "show what pkglint can fix automatically")
                     98:        opts.AddFlagVar('F', "autofix", &gopts.Autofix, false, "try to automatically fix some errors (experimental)")
                     99:        opts.AddFlagVar('g', "gcc-output-format", &gopts.GccOutput, false, "mimic the gcc output format")
                    100:        opts.AddFlagVar('h', "help", &gopts.PrintHelp, false, "print a detailed usage message")
                    101:        opts.AddFlagVar('I', "dumpmakefile", &gopts.DumpMakefile, false, "dump the Makefile after parsing")
                    102:        opts.AddFlagVar('i', "import", &gopts.Import, false, "prepare the import of a wip package")
                    103:        opts.AddFlagVar('m', "log-verbose", &gopts.LogVerbose, false, "allow the same log message more than once")
1.24      rillig    104:        opts.AddStrList('o', "only", &gopts.LogOnly, "only log messages containing the given text")
1.17      rillig    105:        opts.AddFlagVar('p', "profiling", &gopts.Profiling, false, "profile the executing program")
                    106:        opts.AddFlagVar('q', "quiet", &gopts.Quiet, false, "don't print a summary line when finishing")
                    107:        opts.AddFlagVar('r', "recursive", &gopts.Recursive, false, "check subdirectories, too")
                    108:        opts.AddFlagVar('s', "source", &gopts.PrintSource, false, "show the source lines together with diagnostics")
                    109:        opts.AddFlagVar('V', "version", &gopts.PrintVersion, false, "print the version number of pkglint")
                    110:        warn := opts.AddFlagGroup('W', "warning", "warning,...", "enable or disable groups of warnings")
                    111:
                    112:        check.AddFlagVar("ALTERNATIVES", &gopts.CheckAlternatives, true, "check ALTERNATIVES files")
                    113:        check.AddFlagVar("bl3", &gopts.CheckBuildlink3, true, "check buildlink3.mk files")
                    114:        check.AddFlagVar("DESCR", &gopts.CheckDescr, true, "check DESCR file")
                    115:        check.AddFlagVar("distinfo", &gopts.CheckDistinfo, true, "check distinfo file")
                    116:        check.AddFlagVar("extra", &gopts.CheckExtra, false, "check various additional files")
                    117:        check.AddFlagVar("global", &gopts.CheckGlobal, false, "inter-package checks")
                    118:        check.AddFlagVar("INSTALL", &gopts.CheckInstall, true, "check INSTALL and DEINSTALL scripts")
                    119:        check.AddFlagVar("Makefile", &gopts.CheckMakefile, true, "check Makefiles")
                    120:        check.AddFlagVar("MESSAGE", &gopts.CheckMessage, true, "check MESSAGE file")
                    121:        check.AddFlagVar("mk", &gopts.CheckMk, true, "check other .mk files")
                    122:        check.AddFlagVar("patches", &gopts.CheckPatches, true, "check patches")
                    123:        check.AddFlagVar("PLIST", &gopts.CheckPlist, true, "check PLIST files")
                    124:
                    125:        warn.AddFlagVar("absname", &gopts.WarnAbsname, true, "warn about use of absolute file names")
                    126:        warn.AddFlagVar("directcmd", &gopts.WarnDirectcmd, true, "warn about use of direct command names instead of Make variables")
                    127:        warn.AddFlagVar("extra", &gopts.WarnExtra, false, "enable some extra warnings")
                    128:        warn.AddFlagVar("order", &gopts.WarnOrder, false, "warn if Makefile entries are unordered")
                    129:        warn.AddFlagVar("perm", &gopts.WarnPerm, false, "warn about unforeseen variable definition and use")
                    130:        warn.AddFlagVar("plist-depr", &gopts.WarnPlistDepr, false, "warn about deprecated paths in PLISTs")
                    131:        warn.AddFlagVar("plist-sort", &gopts.WarnPlistSort, false, "warn about unsorted entries in PLISTs")
                    132:        warn.AddFlagVar("quoting", &gopts.WarnQuoting, false, "warn about quoting issues")
                    133:        warn.AddFlagVar("space", &gopts.WarnSpace, false, "warn about inconsistent use of white-space")
                    134:        warn.AddFlagVar("style", &gopts.WarnStyle, false, "warn about stylistic issues")
                    135:        warn.AddFlagVar("types", &gopts.WarnTypes, true, "do some simple type checking in Makefiles")
                    136:
                    137:        remainingArgs, err := opts.Parse(args)
                    138:        if err != nil {
1.24      rillig    139:                fmt.Fprintf(G.logErr.out, "%s\n\n", err)
                    140:                opts.Help(G.logErr.out, "pkglint [options] dir...")
1.17      rillig    141:                exitcode := 1
                    142:                return &exitcode
                    143:        }
                    144:        gopts.args = remainingArgs
                    145:
                    146:        if gopts.PrintHelp {
1.24      rillig    147:                opts.Help(G.logOut.out, "pkglint [options] dir...")
1.17      rillig    148:                exitcode := 0
                    149:                return &exitcode
                    150:        }
                    151:
                    152:        if G.opts.PrintVersion {
1.24      rillig    153:                fmt.Fprintf(G.logOut.out, "%s\n", confVersion)
1.17      rillig    154:                exitcode := 0
                    155:                return &exitcode
                    156:        }
                    157:
                    158:        return nil
                    159: }
                    160:
                    161: func (pkglint *Pkglint) PrintSummary() {
1.25      rillig    162:        if !G.opts.Quiet && !G.opts.Autofix {
1.17      rillig    163:                if G.errors != 0 || G.warnings != 0 {
1.24      rillig    164:                        G.logOut.Printf("%d %s and %d %s found.\n",
1.17      rillig    165:                                G.errors, ifelseStr(G.errors == 1, "error", "errors"),
                    166:                                G.warnings, ifelseStr(G.warnings == 1, "warning", "warnings"))
                    167:                } else {
1.24      rillig    168:                        G.logOut.WriteLine("Looks fine.")
1.17      rillig    169:                }
                    170:                if G.explanationsAvailable && !G.opts.Explain {
1.24      rillig    171:                        G.logOut.WriteLine("(Run \"pkglint -e\" to show explanations.)")
1.17      rillig    172:                }
1.25      rillig    173:                if G.autofixAvailable && !G.opts.PrintAutofix {
1.24      rillig    174:                        G.logOut.WriteLine("(Run \"pkglint -fs\" to show what can be fixed automatically.)")
1.17      rillig    175:                }
                    176:                if G.autofixAvailable && !G.opts.Autofix {
1.24      rillig    177:                        G.logOut.WriteLine("(Run \"pkglint -F\" to automatically fix some issues.)")
1.17      rillig    178:                }
                    179:        }
                    180: }
                    181:
                    182: func (pkglint *Pkglint) CheckDirent(fname string) {
1.18      rillig    183:        if trace.Tracing {
                    184:                defer trace.Call1(fname)()
1.17      rillig    185:        }
                    186:
                    187:        st, err := os.Lstat(fname)
                    188:        if err != nil || !st.Mode().IsDir() && !st.Mode().IsRegular() {
                    189:                NewLineWhole(fname).Errorf("No such file or directory.")
                    190:                return
                    191:        }
                    192:        isDir := st.Mode().IsDir()
                    193:        isReg := st.Mode().IsRegular()
                    194:
                    195:        G.CurrentDir = ifelseStr(isReg, path.Dir(fname), fname)
                    196:        absCurrentDir := abspath(G.CurrentDir)
                    197:        G.Wip = !G.opts.Import && matches(absCurrentDir, `/wip/|/wip$`)
                    198:        G.Infrastructure = matches(absCurrentDir, `/mk/|/mk$`)
                    199:        G.CurPkgsrcdir = findPkgsrcTopdir(G.CurrentDir)
                    200:        if G.CurPkgsrcdir == "" {
                    201:                NewLineWhole(fname).Errorf("Cannot determine the pkgsrc root directory for %q.", G.CurrentDir)
                    202:                return
                    203:        }
                    204:
                    205:        switch {
                    206:        case isDir && isEmptyDir(fname):
                    207:                return
                    208:        case isReg:
                    209:                Checkfile(fname)
                    210:                return
                    211:        }
                    212:
                    213:        switch G.CurPkgsrcdir {
                    214:        case "../..":
                    215:                checkdirPackage(relpath(G.globalData.Pkgsrcdir, G.CurrentDir))
                    216:        case "..":
                    217:                CheckdirCategory()
                    218:        case ".":
                    219:                CheckdirToplevel()
                    220:        default:
                    221:                NewLineWhole(fname).Errorf("Cannot check directories outside a pkgsrc tree.")
                    222:        }
                    223: }
1.1       rillig    224:
                    225: // Returns the pkgsrc top-level directory, relative to the given file or directory.
                    226: func findPkgsrcTopdir(fname string) string {
1.18      rillig    227:        for _, dir := range [...]string{".", "..", "../..", "../../.."} {
1.1       rillig    228:                if fileExists(fname + "/" + dir + "/mk/bsd.pkg.mk") {
                    229:                        return dir
                    230:                }
                    231:        }
                    232:        return ""
                    233: }
                    234:
1.6       rillig    235: func resolveVariableRefs(text string) string {
1.18      rillig    236:        if trace.Tracing {
                    237:                defer trace.Call1(text)()
1.1       rillig    238:        }
                    239:
                    240:        visited := make(map[string]bool) // To prevent endless loops
                    241:
                    242:        str := text
                    243:        for {
1.18      rillig    244:                replaced := regex.Compile(`\$\{([\w.]+)\}`).ReplaceAllStringFunc(str, func(m string) string {
1.1       rillig    245:                        varname := m[2 : len(m)-1]
                    246:                        if !visited[varname] {
                    247:                                visited[varname] = true
1.6       rillig    248:                                if G.Pkg != nil {
                    249:                                        if value, ok := G.Pkg.varValue(varname); ok {
1.1       rillig    250:                                                return value
                    251:                                        }
                    252:                                }
1.6       rillig    253:                                if G.Mk != nil {
                    254:                                        if value, ok := G.Mk.VarValue(varname); ok {
1.1       rillig    255:                                                return value
                    256:                                        }
                    257:                                }
                    258:                        }
1.6       rillig    259:                        return "${" + varname + "}"
1.1       rillig    260:                })
                    261:                if replaced == str {
                    262:                        return replaced
                    263:                }
                    264:                str = replaced
                    265:        }
                    266: }
                    267:
1.6       rillig    268: func CheckfileExtra(fname string) {
1.18      rillig    269:        if trace.Tracing {
                    270:                defer trace.Call1(fname)()
1.1       rillig    271:        }
                    272:
1.6       rillig    273:        if lines := LoadNonemptyLines(fname, false); lines != nil {
                    274:                ChecklinesTrailingEmptyLines(lines)
                    275:        }
1.1       rillig    276: }
                    277:
1.22      rillig    278: func ChecklinesDescr(lines []Line) {
1.18      rillig    279:        if trace.Tracing {
1.22      rillig    280:                defer trace.Call1(lines[0].Filename)()
1.1       rillig    281:        }
                    282:
1.6       rillig    283:        for _, line := range lines {
1.22      rillig    284:                CheckLineLength(line, 80)
                    285:                CheckLineTrailingWhitespace(line)
                    286:                CheckLineValidCharacters(line, `[\t -~]`)
                    287:                if contains(line.Text, "${") {
1.15      rillig    288:                        line.Notef("Variables are not expanded in the DESCR file.")
1.1       rillig    289:                }
                    290:        }
1.6       rillig    291:        ChecklinesTrailingEmptyLines(lines)
1.1       rillig    292:
1.6       rillig    293:        if maxlines := 24; len(lines) > maxlines {
                    294:                line := lines[maxlines]
1.1       rillig    295:
1.6       rillig    296:                line.Warnf("File too long (should be no more than %d lines).", maxlines)
1.15      rillig    297:                Explain(
1.6       rillig    298:                        "The DESCR file should fit on a traditional terminal of 80x25",
                    299:                        "characters.  It is also intended to give a _brief_ summary about",
                    300:                        "the package's contents.")
1.1       rillig    301:        }
                    302:
1.6       rillig    303:        SaveAutofixChanges(lines)
1.1       rillig    304: }
                    305:
1.22      rillig    306: func ChecklinesMessage(lines []Line) {
1.18      rillig    307:        if trace.Tracing {
1.22      rillig    308:                defer trace.Call1(lines[0].Filename)()
1.1       rillig    309:        }
                    310:
1.26    ! rillig    311:        explanation := []string{
        !           312:                "A MESSAGE file should consist of a header line, having 75 \"=\"",
        !           313:                "characters, followed by a line containing only the RCS Id, then an",
        !           314:                "empty line, your text and finally the footer line, which is the",
        !           315:                "same as the header line."}
1.1       rillig    316:
                    317:        if len(lines) < 3 {
                    318:                lastLine := lines[len(lines)-1]
1.15      rillig    319:                lastLine.Warnf("File too short.")
1.26    ! rillig    320:                Explain(explanation...)
1.1       rillig    321:                return
                    322:        }
                    323:
                    324:        hline := strings.Repeat("=", 75)
1.22      rillig    325:        if line := lines[0]; line.Text != hline {
1.26    ! rillig    326:                fix := line.Autofix()
        !           327:                fix.Warnf("Expected a line of exactly 75 \"=\" characters.")
        !           328:                fix.Explain(explanation...)
        !           329:                fix.InsertBefore(hline)
        !           330:                fix.Apply()
        !           331:                CheckLineRcsid(lines[0], ``, "")
        !           332:        } else if 1 < len(lines) {
        !           333:                CheckLineRcsid(lines[1], ``, "")
1.1       rillig    334:        }
                    335:        for _, line := range lines {
1.22      rillig    336:                CheckLineLength(line, 80)
                    337:                CheckLineTrailingWhitespace(line)
                    338:                CheckLineValidCharacters(line, `[\t -~]`)
1.1       rillig    339:        }
1.22      rillig    340:        if lastLine := lines[len(lines)-1]; lastLine.Text != hline {
1.26    ! rillig    341:                fix := lastLine.Autofix()
        !           342:                fix.Warnf("Expected a line of exactly 75 \"=\" characters.")
        !           343:                fix.Explain(explanation...)
        !           344:                fix.InsertAfter(hline)
        !           345:                fix.Apply()
1.1       rillig    346:        }
1.6       rillig    347:        ChecklinesTrailingEmptyLines(lines)
1.26    ! rillig    348:
        !           349:        SaveAutofixChanges(lines)
1.1       rillig    350: }
                    351:
1.6       rillig    352: func CheckfileMk(fname string) {
1.18      rillig    353:        if trace.Tracing {
                    354:                defer trace.Call1(fname)()
1.1       rillig    355:        }
                    356:
                    357:        lines := LoadNonemptyLines(fname, true)
                    358:        if lines == nil {
                    359:                return
                    360:        }
                    361:
1.6       rillig    362:        NewMkLines(lines).Check()
                    363:        SaveAutofixChanges(lines)
1.1       rillig    364: }
                    365:
1.6       rillig    366: func Checkfile(fname string) {
1.18      rillig    367:        if trace.Tracing {
                    368:                defer trace.Call1(fname)()
1.6       rillig    369:        }
1.1       rillig    370:
                    371:        basename := path.Base(fname)
1.6       rillig    372:        if hasPrefix(basename, "work") || hasSuffix(basename, "~") || hasSuffix(basename, ".orig") || hasSuffix(basename, ".rej") {
1.1       rillig    373:                if G.opts.Import {
1.15      rillig    374:                        NewLineWhole(fname).Errorf("Must be cleaned up before committing the package.")
1.1       rillig    375:                }
                    376:                return
                    377:        }
                    378:
                    379:        st, err := os.Lstat(fname)
                    380:        if err != nil {
1.8       rillig    381:                NewLineWhole(fname).Errorf("%s", err)
1.1       rillig    382:                return
                    383:        }
                    384:
                    385:        if st.Mode().IsRegular() && st.Mode().Perm()&0111 != 0 && !isCommitted(fname) {
1.6       rillig    386:                line := NewLine(fname, 0, "", nil)
1.15      rillig    387:                line.Warnf("Should not be executable.")
                    388:                Explain(
1.6       rillig    389:                        "No package file should ever be executable.  Even the INSTALL and",
                    390:                        "DEINSTALL scripts are usually not usable in the form they have in",
                    391:                        "the package, as the pathnames get adjusted during installation.",
                    392:                        "So there is no need to have any file executable.")
1.1       rillig    393:        }
                    394:
                    395:        switch {
                    396:        case st.Mode().IsDir():
                    397:                switch {
1.23      rillig    398:                case basename == "files" || basename == "patches" || isIgnoredFilename(basename):
1.1       rillig    399:                        // Ok
                    400:                case matches(fname, `(?:^|/)files/[^/]*$`):
                    401:                        // Ok
                    402:                case !isEmptyDir(fname):
1.15      rillig    403:                        NewLineWhole(fname).Warnf("Unknown directory name.")
1.1       rillig    404:                }
                    405:
                    406:        case st.Mode()&os.ModeSymlink != 0:
1.9       rillig    407:                if !hasPrefix(basename, "work") {
1.15      rillig    408:                        NewLineWhole(fname).Warnf("Unknown symlink name.")
1.1       rillig    409:                }
                    410:
                    411:        case !st.Mode().IsRegular():
1.15      rillig    412:                NewLineWhole(fname).Errorf("Only files and directories are allowed in pkgsrc.")
1.1       rillig    413:
                    414:        case basename == "ALTERNATIVES":
                    415:                if G.opts.CheckAlternatives {
1.6       rillig    416:                        CheckfileExtra(fname)
1.1       rillig    417:                }
                    418:
                    419:        case basename == "buildlink3.mk":
                    420:                if G.opts.CheckBuildlink3 {
                    421:                        if lines := LoadNonemptyLines(fname, true); lines != nil {
1.6       rillig    422:                                ChecklinesBuildlink3Mk(NewMkLines(lines))
1.1       rillig    423:                        }
                    424:                }
                    425:
                    426:        case hasPrefix(basename, "DESCR"):
                    427:                if G.opts.CheckDescr {
                    428:                        if lines := LoadNonemptyLines(fname, false); lines != nil {
1.6       rillig    429:                                ChecklinesDescr(lines)
1.1       rillig    430:                        }
                    431:                }
                    432:
1.6       rillig    433:        case basename == "distinfo":
1.1       rillig    434:                if G.opts.CheckDistinfo {
                    435:                        if lines := LoadNonemptyLines(fname, false); lines != nil {
1.6       rillig    436:                                ChecklinesDistinfo(lines)
1.1       rillig    437:                        }
                    438:                }
                    439:
                    440:        case basename == "DEINSTALL" || basename == "INSTALL":
                    441:                if G.opts.CheckInstall {
1.6       rillig    442:                        CheckfileExtra(fname)
1.1       rillig    443:                }
                    444:
                    445:        case hasPrefix(basename, "MESSAGE"):
                    446:                if G.opts.CheckMessage {
                    447:                        if lines := LoadNonemptyLines(fname, false); lines != nil {
1.6       rillig    448:                                ChecklinesMessage(lines)
1.1       rillig    449:                        }
                    450:                }
                    451:
                    452:        case matches(basename, `^patch-[-A-Za-z0-9_.~+]*[A-Za-z0-9_]$`):
                    453:                if G.opts.CheckPatches {
                    454:                        if lines := LoadNonemptyLines(fname, false); lines != nil {
1.6       rillig    455:                                ChecklinesPatch(lines)
1.1       rillig    456:                        }
                    457:                }
                    458:
                    459:        case matches(fname, `(?:^|/)patches/manual[^/]*$`):
1.18      rillig    460:                if trace.Tracing {
                    461:                        trace.Step1("Unchecked file %q.", fname)
1.1       rillig    462:                }
                    463:
                    464:        case matches(fname, `(?:^|/)patches/[^/]*$`):
1.15      rillig    465:                NewLineWhole(fname).Warnf("Patch files should be named \"patch-\", followed by letters, '-', '_', '.', and digits only.")
1.1       rillig    466:
                    467:        case matches(basename, `^(?:.*\.mk|Makefile.*)$`) && !matches(fname, `files/`) && !matches(fname, `patches/`):
                    468:                if G.opts.CheckMk {
1.6       rillig    469:                        CheckfileMk(fname)
1.1       rillig    470:                }
                    471:
                    472:        case hasPrefix(basename, "PLIST"):
                    473:                if G.opts.CheckPlist {
                    474:                        if lines := LoadNonemptyLines(fname, false); lines != nil {
1.6       rillig    475:                                ChecklinesPlist(lines)
1.1       rillig    476:                        }
                    477:                }
                    478:
                    479:        case basename == "TODO" || basename == "README":
                    480:                // Ok
                    481:
                    482:        case hasPrefix(basename, "CHANGES-"):
1.17      rillig    483:                // This only checks the file, but doesn't register the changes globally.
1.1       rillig    484:                G.globalData.loadDocChangesFromFile(fname)
                    485:
                    486:        case matches(fname, `(?:^|/)files/[^/]*$`):
                    487:                // Skip
                    488:
1.9       rillig    489:        case basename == "spec":
                    490:                // Ok in regression tests
                    491:
1.1       rillig    492:        default:
1.15      rillig    493:                NewLineWhole(fname).Warnf("Unexpected file found.")
1.1       rillig    494:                if G.opts.CheckExtra {
1.6       rillig    495:                        CheckfileExtra(fname)
1.1       rillig    496:                }
                    497:        }
                    498: }
                    499:
1.22      rillig    500: func ChecklinesTrailingEmptyLines(lines []Line) {
1.1       rillig    501:        max := len(lines)
                    502:        last := max
1.22      rillig    503:        for last > 1 && lines[last-1].Text == "" {
1.1       rillig    504:                last--
                    505:        }
                    506:        if last != max {
1.15      rillig    507:                lines[last].Notef("Trailing empty lines.")
1.1       rillig    508:        }
                    509: }

CVSweb <webmaster@jp.NetBSD.org>