[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.4

1.1       rillig      1: package main
                      2:
                      3: // based on pkglint.pl 1.896
                      4:
                      5: import (
                      6:        "fmt"
                      7:        "os"
                      8:        "path"
                      9:        "strings"
                     10: )
                     11:
                     12: const (
                     13:        reDependencyCmp      = `^((?:\$\{[\w_]+\}|[\w_\.+]|-[^\d])+)[<>]=?(\d[^-*?\[\]]*)$`
                     14:        reDependencyWildcard = `^((?:\$\{[\w_]+\}|[\w_\.+]|-[^\d\[])+)-(?:\[0-9\]\*|\d[^-]*)$`
                     15:        reMkCond             = `^\.(\s*)(if|ifdef|ifndef|else|elif|endif|for|endfor|undef)(?:\s+([^\s#][^#]*?))?\s*(?:#.*)?$`
                     16:        reMkInclude          = `^\.\s*(s?include)\s+\"([^\"]+)\"\s*(?:#.*)?$`
                     17:        reVarassign          = `^ *([-*+A-Z_a-z0-9.${}\[]+?)\s*([!+:?]?=)\s*((?:\\#|[^#])*?)(?:\s*(#.*))?$`
                     18:        rePkgname            = `^([\w\-.+]+)-(\d(?:\w|\.\d)*)$`
                     19:        rePkgbase            = `(?:[+.\w]|-[A-Z_a-z])+`
                     20:        rePkgversion         = `\d(?:\w|\.\d)*`
                     21: )
                     22:
                     23: func explainRelativeDirs(line *Line) {
                     24:        line.explain(
                     25:                "Directories in the form \"../../category/package\" make it easier to",
                     26:                "move a package around in pkgsrc, for example from pkgsrc-wip to the",
                     27:                "main pkgsrc repository.")
                     28: }
                     29:
                     30: // Returns the pkgsrc top-level directory, relative to the given file or directory.
                     31: func findPkgsrcTopdir(fname string) string {
                     32:        for _, dir := range []string{".", "..", "../..", "../../.."} {
                     33:                if fileExists(fname + "/" + dir + "/mk/bsd.pkg.mk") {
                     34:                        return dir
                     35:                }
                     36:        }
                     37:        return ""
                     38: }
                     39:
                     40: func loadPackageMakefile(fname string) []*Line {
                     41:        defer tracecall("loadPackageMakefile", fname)()
                     42:
                     43:        var mainLines, allLines []*Line
                     44:        if !readMakefile(fname, &mainLines, &allLines) {
1.3       rillig     45:                errorf(fname, noLines, "Cannot be read.")
1.1       rillig     46:                return nil
                     47:        }
                     48:
                     49:        if G.opts.DumpMakefile {
1.3       rillig     50:                debugf(G.currentDir, noLines, "Whole Makefile (with all included files) follows:")
1.1       rillig     51:                for _, line := range allLines {
                     52:                        fmt.Printf("%s\n", line.String())
                     53:                }
                     54:        }
                     55:
                     56:        determineUsedVariables(allLines)
                     57:
                     58:        G.pkgContext.pkgdir = expandVariableWithDefault("PKGDIR", ".")
                     59:        G.pkgContext.distinfoFile = expandVariableWithDefault("DISTINFO_FILE", "distinfo")
                     60:        G.pkgContext.filesdir = expandVariableWithDefault("FILESDIR", "files")
                     61:        G.pkgContext.patchdir = expandVariableWithDefault("PATCHDIR", "patches")
                     62:
                     63:        if varIsDefined("PHPEXT_MK") {
                     64:                if !varIsDefined("USE_PHP_EXT_PATCHES") {
                     65:                        G.pkgContext.patchdir = "patches"
                     66:                }
                     67:                if varIsDefined("PECL_VERSION") {
                     68:                        G.pkgContext.distinfoFile = "distinfo"
                     69:                }
                     70:        }
                     71:
                     72:        _ = G.opts.DebugMisc &&
                     73:                dummyLine.debugf("DISTINFO_FILE=%s", G.pkgContext.distinfoFile) &&
                     74:                dummyLine.debugf("FILESDIR=%s", G.pkgContext.filesdir) &&
                     75:                dummyLine.debugf("PATCHDIR=%s", G.pkgContext.patchdir) &&
                     76:                dummyLine.debugf("PKGDIR=%s", G.pkgContext.pkgdir)
                     77:
                     78:        return mainLines
                     79: }
                     80:
                     81: func determineUsedVariables(lines []*Line) {
                     82:        re := regcomp(`(?:\$\{|\$\(|defined\(|empty\()([0-9+.A-Z_a-z]+)[:})]`)
                     83:        for _, line := range lines {
                     84:                rest := line.text
                     85:                for {
                     86:                        m := re.FindStringSubmatchIndex(rest)
                     87:                        if m == nil {
                     88:                                break
                     89:                        }
                     90:                        varname := rest[m[2]:m[3]]
                     91:                        useVar(line, varname)
                     92:                        rest = rest[:m[0]] + rest[m[1]:]
                     93:                }
                     94:        }
                     95: }
                     96:
                     97: func extractUsedVariables(line *Line, text string) []string {
                     98:        re := regcomp(`^(?:[^\$]+|\$[\$*<>?@]|\$\{([.0-9A-Z_a-z]+)(?::(?:[^\${}]|\$[^{])+)?\})`)
                     99:        rest := text
                    100:        var result []string
                    101:        for {
                    102:                m := re.FindStringSubmatchIndex(rest)
                    103:                if m == nil {
                    104:                        break
                    105:                }
                    106:                varname := rest[negToZero(m[2]):negToZero(m[3])]
                    107:                rest = rest[:m[0]] + rest[m[1]:]
                    108:                if varname != "" {
                    109:                        result = append(result, varname)
                    110:                }
                    111:        }
                    112:
                    113:        if rest != "" {
                    114:                _ = G.opts.DebugMisc && line.debugf("extractUsedVariables: rest=%q", rest)
                    115:        }
                    116:        return result
                    117: }
                    118:
                    119: // Returns the type of the variable (maybe guessed based on the variable name),
                    120: // or nil if the type cannot even be guessed.
                    121: func getVariableType(line *Line, varname string) *Vartype {
                    122:
                    123:        if vartype := G.globalData.vartypes[varname]; vartype != nil {
                    124:                return vartype
                    125:        }
                    126:        if vartype := G.globalData.vartypes[varnameCanon(varname)]; vartype != nil {
                    127:                return vartype
                    128:        }
                    129:
                    130:        if G.globalData.varnameToToolname[varname] != "" {
1.3       rillig    131:                return &Vartype{lkNone, CheckvarShellCommand, []AclEntry{{"*", "u"}}, guNotGuessed}
1.1       rillig    132:        }
                    133:
                    134:        if m, toolvarname := match1(varname, `^TOOLS_(.*)`); m && G.globalData.varnameToToolname[toolvarname] != "" {
1.3       rillig    135:                return &Vartype{lkNone, CheckvarPathname, []AclEntry{{"*", "u"}}, guNotGuessed}
1.1       rillig    136:        }
                    137:
                    138:        allowAll := []AclEntry{{"*", "adpsu"}}
                    139:        allowRuntime := []AclEntry{{"*", "adsu"}}
                    140:
                    141:        // Guess the datatype of the variable based on naming conventions.
                    142:        var gtype *Vartype
                    143:        switch {
                    144:        case hasSuffix(varname, "DIRS"):
1.3       rillig    145:                gtype = &Vartype{lkShell, CheckvarPathmask, allowRuntime, guGuessed}
1.1       rillig    146:        case hasSuffix(varname, "DIR"), hasSuffix(varname, "_HOME"):
1.3       rillig    147:                gtype = &Vartype{lkNone, CheckvarPathname, allowRuntime, guGuessed}
1.1       rillig    148:        case hasSuffix(varname, "FILES"):
1.3       rillig    149:                gtype = &Vartype{lkShell, CheckvarPathmask, allowRuntime, guGuessed}
1.1       rillig    150:        case hasSuffix(varname, "FILE"):
1.3       rillig    151:                gtype = &Vartype{lkNone, CheckvarPathname, allowRuntime, guGuessed}
1.1       rillig    152:        case hasSuffix(varname, "PATH"):
1.3       rillig    153:                gtype = &Vartype{lkNone, CheckvarPathlist, allowRuntime, guGuessed}
1.1       rillig    154:        case hasSuffix(varname, "PATHS"):
1.3       rillig    155:                gtype = &Vartype{lkShell, CheckvarPathname, allowRuntime, guGuessed}
1.1       rillig    156:        case hasSuffix(varname, "_USER"):
1.3       rillig    157:                gtype = &Vartype{lkNone, CheckvarUserGroupName, allowAll, guGuessed}
1.1       rillig    158:        case hasSuffix(varname, "_GROUP"):
1.3       rillig    159:                gtype = &Vartype{lkNone, CheckvarUserGroupName, allowAll, guGuessed}
1.1       rillig    160:        case hasSuffix(varname, "_ENV"):
1.3       rillig    161:                gtype = &Vartype{lkShell, CheckvarShellWord, allowRuntime, guGuessed}
1.1       rillig    162:        case hasSuffix(varname, "_CMD"):
1.3       rillig    163:                gtype = &Vartype{lkNone, CheckvarShellCommand, allowRuntime, guGuessed}
1.1       rillig    164:        case hasSuffix(varname, "_ARGS"):
1.3       rillig    165:                gtype = &Vartype{lkShell, CheckvarShellWord, allowRuntime, guGuessed}
1.1       rillig    166:        case hasSuffix(varname, "_CFLAGS"), hasSuffix(varname, "_CPPFLAGS"), hasSuffix(varname, "_CXXFLAGS"), hasSuffix(varname, "_LDFLAGS"):
1.3       rillig    167:                gtype = &Vartype{lkShell, CheckvarShellWord, allowRuntime, guGuessed}
1.1       rillig    168:        case hasSuffix(varname, "_MK"):
1.3       rillig    169:                gtype = &Vartype{lkNone, CheckvarUnchecked, allowAll, guGuessed}
1.1       rillig    170:        case hasPrefix(varname, "PLIST."):
1.3       rillig    171:                gtype = &Vartype{lkNone, CheckvarYes, allowAll, guGuessed}
1.1       rillig    172:        }
                    173:
                    174:        if gtype != nil {
                    175:                _ = G.opts.DebugVartypes && line.debugf("The guessed type of %q is %v.", varname, gtype)
                    176:        } else {
                    177:                _ = G.opts.DebugVartypes && line.debugf("No type definition found for %q.", varname)
                    178:        }
                    179:        return gtype
                    180: }
                    181:
                    182: func resolveVariableRefs(text string) string {
                    183:        defer tracecall("resolveVariableRefs", text)()
                    184:
                    185:        visited := make(map[string]bool) // To prevent endless loops
                    186:
                    187:        str := text
                    188:        for {
                    189:                replaced := regcomp(`\$\{([\w.]+)\}`).ReplaceAllStringFunc(str, func(m string) string {
                    190:                        varname := m[2 : len(m)-1]
                    191:                        if !visited[varname] {
                    192:                                visited[varname] = true
                    193:                                if ctx := G.pkgContext; ctx != nil {
                    194:                                        if value, ok := ctx.varValue(varname); ok {
                    195:                                                return value
                    196:                                        }
                    197:                                }
                    198:                                if ctx := G.mkContext; ctx != nil {
                    199:                                        if value, ok := ctx.varValue(varname); ok {
                    200:                                                return value
                    201:                                        }
                    202:                                }
                    203:                        }
                    204:                        return sprintf("${%s}", varname)
                    205:                })
                    206:                if replaced == str {
                    207:                        return replaced
                    208:                }
                    209:                str = replaced
                    210:        }
                    211: }
                    212:
                    213: func expandVariableWithDefault(varname, defaultValue string) string {
                    214:        line := G.pkgContext.vardef[varname]
                    215:        if line == nil {
                    216:                return defaultValue
                    217:        }
                    218:
                    219:        value := line.extra["value"].(string)
                    220:        value = resolveVarsInRelativePath(value, true)
                    221:        if containsVarRef(value) {
                    222:                value = resolveVariableRefs(value)
                    223:        }
                    224:        _ = G.opts.DebugMisc && line.debugf("Expanded %q to %q", varname, value)
                    225:        return value
                    226: }
                    227:
                    228: func getVariablePermissions(line *Line, varname string) string {
                    229:        if vartype := getVariableType(line, varname); vartype != nil {
                    230:                return vartype.effectivePermissions(line.fname)
                    231:        }
                    232:
                    233:        _ = G.opts.DebugMisc && line.debugf("No type definition found for %q.", varname)
                    234:        return "adpsu"
                    235: }
                    236:
                    237: func checklineLength(line *Line, maxlength int) {
                    238:        if len(line.text) > maxlength {
                    239:                line.warnf("Line too long (should be no more than %d characters).", maxlength)
                    240:                line.explain(
                    241:                        "Back in the old time, terminals with 80x25 characters were common.",
                    242:                        "And this is still the default size of many terminal emulators.",
                    243:                        "Moderately short lines also make reading easier.")
                    244:        }
                    245: }
                    246:
                    247: func checklineValidCharacters(line *Line, reChar string) {
                    248:        rest := regcomp(reChar).ReplaceAllString(line.text, "")
                    249:        if rest != "" {
                    250:                uni := ""
                    251:                for _, c := range rest {
                    252:                        uni += sprintf(" %U", c)
                    253:                }
                    254:                line.warnf("Line contains invalid characters (%s).", uni[1:])
                    255:        }
                    256: }
                    257:
                    258: func checklineValidCharactersInValue(line *Line, reValid string) {
                    259:        varname := line.extra["varname"].(string)
                    260:        value := line.extra["value"].(string)
                    261:        rest := regcomp(reValid).ReplaceAllString(value, "")
                    262:        if rest != "" {
                    263:                uni := ""
                    264:                for _, c := range rest {
                    265:                        uni += sprintf(" %U", c)
                    266:                }
                    267:                line.warnf("%s contains invalid characters (%s).", varname, uni[1:])
                    268:        }
                    269: }
                    270:
                    271: func checklineTrailingWhitespace(line *Line) {
                    272:        if hasSuffix(line.text, " ") || hasSuffix(line.text, "\t") {
                    273:                line.notef("Trailing white-space.")
                    274:                line.explain(
                    275:                        "When a line ends with some white-space, that space is in most cases",
                    276:                        "irrelevant and can be removed.")
                    277:                line.replaceRegex(`\s+\n$`, "\n")
                    278:        }
                    279: }
                    280:
                    281: func checklineRcsid(line *Line, prefixRe, suggestedPrefix string) bool {
                    282:        defer tracecall("checklineRcsid", prefixRe, suggestedPrefix)()
                    283:
1.4     ! rillig    284:        if !matches(line.text, `^`+prefixRe+`\$NetBSD(?::[^\$]+)?\$$`) {
1.1       rillig    285:                line.errorf("Expected %q.", suggestedPrefix+"$"+"NetBSD$")
                    286:                line.explain(
                    287:                        "Several files in pkgsrc must contain the CVS Id, so that their current",
                    288:                        "version can be traced back later from a binary package. This is to",
1.4     ! rillig    289:                        "ensure reproducible builds, for example for finding bugs.")
        !           290:                line.insertBefore(suggestedPrefix + "$" + "NetBSD$")
1.1       rillig    291:                return false
                    292:        }
                    293:        return true
                    294: }
                    295:
                    296: func checklineRelativePath(line *Line, path string, mustExist bool) {
                    297:        if !G.isWip && contains(path, "/wip/") {
                    298:                line.errorf("A main pkgsrc package must not depend on a pkgsrc-wip package.")
                    299:        }
                    300:
                    301:        resolvedPath := resolveVarsInRelativePath(path, true)
                    302:        if containsVarRef(resolvedPath) {
                    303:                return
                    304:        }
                    305:
                    306:        abs := ifelseStr(hasPrefix(resolvedPath, "/"), "", G.currentDir+"/") + resolvedPath
                    307:        if _, err := os.Stat(abs); err != nil {
                    308:                if mustExist {
                    309:                        line.errorf("%q does not exist.", resolvedPath)
                    310:                }
                    311:                return
                    312:        }
                    313:
                    314:        switch {
                    315:        case matches(path, `^\.\./\.\./[^/]+/[^/]`):
                    316:        case hasPrefix(path, "../../mk/"):
                    317:                // There need not be two directory levels for mk/ files.
                    318:        case matches(path, `^\.\./mk/`) && G.curPkgsrcdir == "..":
                    319:                // That's fine for category Makefiles.
                    320:        case matches(path, `^\.\.`):
                    321:                line.warnf("Invalid relative path %q.", path)
                    322:        }
                    323: }
                    324:
                    325: func checkfileExtra(fname string) {
                    326:        defer tracecall("checkfileExtra", fname)()
                    327:
                    328:        if lines := LoadNonemptyLines(fname, false); lines != nil {
                    329:                checklinesTrailingEmptyLines(lines)
                    330:        }
                    331: }
                    332:
                    333: func checklinesMessage(lines []*Line) {
                    334:        defer tracecall("checklinesMessage", lines[0].fname)()
                    335:
                    336:        explanation := []string{
                    337:                "A MESSAGE file should consist of a header line, having 75 \"=\"",
                    338:                "characters, followed by a line containing only the RCS Id, then an",
                    339:                "empty line, your text and finally the footer line, which is the",
                    340:                "same as the header line."}
                    341:
                    342:        if len(lines) < 3 {
                    343:                lastLine := lines[len(lines)-1]
                    344:                lastLine.warnf("File too short.")
                    345:                lastLine.explain(explanation...)
                    346:                return
                    347:        }
                    348:
                    349:        hline := strings.Repeat("=", 75)
                    350:        if line := lines[0]; line.text != hline {
                    351:                line.warnf("Expected a line of exactly 75 \"=\" characters.")
                    352:                line.explain(explanation...)
                    353:        }
                    354:        checklineRcsid(lines[1], ``, "")
                    355:        for _, line := range lines {
                    356:                checklineLength(line, 80)
                    357:                checklineTrailingWhitespace(line)
                    358:                checklineValidCharacters(line, `[\t -~]`)
                    359:        }
                    360:        if lastLine := lines[len(lines)-1]; lastLine.text != hline {
                    361:                lastLine.warnf("Expected a line of exactly 75 \"=\" characters.")
                    362:                lastLine.explain(explanation...)
                    363:        }
                    364:        checklinesTrailingEmptyLines(lines)
                    365: }
                    366:
                    367: func checklineRelativePkgdir(line *Line, pkgdir string) {
                    368:        checklineRelativePath(line, pkgdir, true)
                    369:        pkgdir = resolveVarsInRelativePath(pkgdir, false)
                    370:
                    371:        if m, otherpkgpath := match1(pkgdir, `^(?:\./)?\.\./\.\./([^/]+/[^/]+)$`); m {
                    372:                if !fileExists(G.globalData.pkgsrcdir + "/" + otherpkgpath + "/Makefile") {
                    373:                        line.errorf("There is no package in %q.", otherpkgpath)
                    374:                }
                    375:
                    376:        } else {
                    377:                line.warnf("%q is not a valid relative package directory.", pkgdir)
                    378:                line.explain(
                    379:                        "A relative pathname always starts with \"../../\", followed",
                    380:                        "by a category, a slash and a the directory name of the package.",
                    381:                        "For example, \"../../misc/screen\" is a valid relative pathname.")
                    382:        }
                    383: }
                    384:
                    385: func checkfileMk(fname string) {
                    386:        defer tracecall("checkfileMk", fname)()
                    387:
                    388:        lines := LoadNonemptyLines(fname, true)
                    389:        if lines == nil {
                    390:                return
                    391:        }
                    392:
                    393:        ParselinesMk(lines)
                    394:        ChecklinesMk(lines)
                    395:        saveAutofixChanges(lines)
                    396: }
                    397:
                    398: func checkfile(fname string) {
                    399:        defer tracecall("checkfile", fname)()
                    400:
                    401:        basename := path.Base(fname)
                    402:        if matches(basename, `^(?:work.*|.*~|.*\.orig|.*\.rej)$`) {
                    403:                if G.opts.Import {
1.3       rillig    404:                        errorf(fname, noLines, "Must be cleaned up before committing the package.")
1.1       rillig    405:                }
                    406:                return
                    407:        }
                    408:
                    409:        st, err := os.Lstat(fname)
                    410:        if err != nil {
1.3       rillig    411:                errorf(fname, noLines, "%s", err)
1.1       rillig    412:                return
                    413:        }
                    414:
                    415:        if st.Mode().IsRegular() && st.Mode().Perm()&0111 != 0 && !isCommitted(fname) {
1.3       rillig    416:                line := NewLine(fname, noLines, "", nil)
1.1       rillig    417:                line.warnf("Should not be executable.")
                    418:                line.explain(
                    419:                        "No package file should ever be executable. Even the INSTALL and",
                    420:                        "DEINSTALL scripts are usually not usable in the form they have in the",
                    421:                        "package, as the pathnames get adjusted during installation. So there is",
                    422:                        "no need to have any file executable.")
                    423:        }
                    424:
                    425:        switch {
                    426:        case st.Mode().IsDir():
                    427:                switch {
                    428:                case basename == "files" || basename == "patches" || basename == "CVS":
                    429:                        // Ok
                    430:                case matches(fname, `(?:^|/)files/[^/]*$`):
                    431:                        // Ok
                    432:                case !isEmptyDir(fname):
1.3       rillig    433:                        warnf(fname, noLines, "Unknown directory name.")
1.1       rillig    434:                }
                    435:
                    436:        case st.Mode()&os.ModeSymlink != 0:
                    437:                if !matches(basename, `^work`) {
1.3       rillig    438:                        warnf(fname, noLines, "Unknown symlink name.")
1.1       rillig    439:                }
                    440:
                    441:        case !st.Mode().IsRegular():
1.3       rillig    442:                errorf(fname, noLines, "Only files and directories are allowed in pkgsrc.")
1.1       rillig    443:
                    444:        case basename == "ALTERNATIVES":
                    445:                if G.opts.CheckAlternatives {
                    446:                        checkfileExtra(fname)
                    447:                }
                    448:
                    449:        case basename == "buildlink3.mk":
                    450:                if G.opts.CheckBuildlink3 {
                    451:                        if lines := LoadNonemptyLines(fname, true); lines != nil {
                    452:                                checklinesBuildlink3Mk(lines)
                    453:                        }
                    454:                }
                    455:
                    456:        case hasPrefix(basename, "DESCR"):
                    457:                if G.opts.CheckDescr {
                    458:                        if lines := LoadNonemptyLines(fname, false); lines != nil {
                    459:                                checklinesDescr(lines)
                    460:                        }
                    461:                }
                    462:
                    463:        case hasPrefix(basename, "distinfo"):
                    464:                if G.opts.CheckDistinfo {
                    465:                        if lines := LoadNonemptyLines(fname, false); lines != nil {
                    466:                                checklinesDistinfo(lines)
                    467:                        }
                    468:                }
                    469:
                    470:        case basename == "DEINSTALL" || basename == "INSTALL":
                    471:                if G.opts.CheckInstall {
                    472:                        checkfileExtra(fname)
                    473:                }
                    474:
                    475:        case hasPrefix(basename, "MESSAGE"):
                    476:                if G.opts.CheckMessage {
                    477:                        if lines := LoadNonemptyLines(fname, false); lines != nil {
                    478:                                checklinesMessage(lines)
                    479:                        }
                    480:                }
                    481:
                    482:        case matches(basename, `^patch-[-A-Za-z0-9_.~+]*[A-Za-z0-9_]$`):
                    483:                if G.opts.CheckPatches {
                    484:                        if lines := LoadNonemptyLines(fname, false); lines != nil {
                    485:                                checklinesPatch(lines)
                    486:                        }
                    487:                }
                    488:
                    489:        case matches(fname, `(?:^|/)patches/manual[^/]*$`):
                    490:                if G.opts.DebugUnchecked {
1.3       rillig    491:                        debugf(fname, noLines, "Unchecked file %q.", fname)
1.1       rillig    492:                }
                    493:
                    494:        case matches(fname, `(?:^|/)patches/[^/]*$`):
1.3       rillig    495:                warnf(fname, noLines, "Patch files should be named \"patch-\", followed by letters, '-', '_', '.', and digits only.")
1.1       rillig    496:
                    497:        case matches(basename, `^(?:.*\.mk|Makefile.*)$`) && !matches(fname, `files/`) && !matches(fname, `patches/`):
                    498:                if G.opts.CheckMk {
                    499:                        checkfileMk(fname)
                    500:                }
                    501:
                    502:        case hasPrefix(basename, "PLIST"):
                    503:                if G.opts.CheckPlist {
                    504:                        if lines := LoadNonemptyLines(fname, false); lines != nil {
                    505:                                checklinesPlist(lines)
                    506:                        }
                    507:                }
                    508:
                    509:        case basename == "TODO" || basename == "README":
                    510:                // Ok
                    511:
                    512:        case hasPrefix(basename, "CHANGES-"):
                    513:                // This only checks the file, but doesn’t register the changes globally.
                    514:                G.globalData.loadDocChangesFromFile(fname)
                    515:
                    516:        case matches(fname, `(?:^|/)files/[^/]*$`):
                    517:                // Skip
                    518:
                    519:        default:
1.3       rillig    520:                warnf(fname, noLines, "Unexpected file found.")
1.1       rillig    521:                if G.opts.CheckExtra {
                    522:                        checkfileExtra(fname)
                    523:                }
                    524:        }
                    525: }
                    526:
                    527: func checklinesTrailingEmptyLines(lines []*Line) {
                    528:        max := len(lines)
                    529:        last := max
                    530:        for last > 1 && lines[last-1].text == "" {
                    531:                last--
                    532:        }
                    533:        if last != max {
                    534:                lines[last].notef("Trailing empty lines.")
                    535:        }
                    536: }
                    537:
                    538: func matchVarassign(text string) (m bool, varname, op, value, comment string) {
                    539:        if contains(text, "=") {
                    540:                m, varname, op, value, comment = match4(text, reVarassign)
                    541:        }
                    542:        return
                    543: }

CVSweb <webmaster@jp.NetBSD.org>