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

Annotation of pkgsrc/pkgtools/pkglint/files/line.go, Revision 1.11

1.1       rillig      1: package main
                      2:
                      3: // When files are read in by pkglint, they are interpreted in terms of
                      4: // lines. For Makefiles, line continuations are handled properly, allowing
                      5: // multiple raw lines to end in a single logical line. For other files
                      6: // there is a 1:1 translation.
                      7: //
                      8: // A difference between the raw and the logical lines is that the
                      9: // raw lines include the line end sequence, whereas the logical lines
                     10: // do not.
                     11: //
                     12: // Some methods allow modification of the raw lines contained in the
                     13: // logical line, but leave the “text” field untouched. These methods are
                     14: // used in the --autofix mode.
                     15:
                     16: import (
                     17:        "fmt"
                     18:        "io"
1.9       rillig     19:        "path"
1.8       rillig     20:        "strconv"
1.1       rillig     21:        "strings"
                     22: )
                     23:
                     24: type RawLine struct {
1.8       rillig     25:        Lineno int
                     26:        orignl string
1.1       rillig     27:        textnl string
                     28: }
                     29:
                     30: func (rline *RawLine) String() string {
1.8       rillig     31:        return strconv.Itoa(rline.Lineno) + ":" + rline.textnl
1.1       rillig     32: }
                     33:
                     34: type Line struct {
1.8       rillig     35:        Fname          string
                     36:        firstLine      int32 // Zero means not applicable, -1 means EOF
                     37:        lastLine       int32 // Usually the same as firstLine, may differ in Makefiles
                     38:        Text           string
                     39:        raw            []*RawLine
                     40:        changed        bool
1.10      rillig     41:        before         []string
                     42:        after          []string
1.11    ! rillig     43:        autofixMessage string
1.1       rillig     44: }
                     45:
1.8       rillig     46: func NewLine(fname string, lineno int, text string, rawLines []*RawLine) *Line {
                     47:        return NewLineMulti(fname, lineno, lineno, text, rawLines)
1.1       rillig     48: }
                     49:
1.8       rillig     50: // NewLineMulti is for logical Makefile lines that end with backslash.
                     51: func NewLineMulti(fname string, firstLine, lastLine int, text string, rawLines []*RawLine) *Line {
1.11    ! rillig     52:        return &Line{fname, int32(firstLine), int32(lastLine), text, rawLines, false, nil, nil, ""}
1.1       rillig     53: }
1.2       rillig     54:
1.8       rillig     55: // NewLineEOF creates a dummy line for logging, with the “line number” EOF.
                     56: func NewLineEOF(fname string) *Line {
                     57:        return NewLineMulti(fname, -1, 0, "", nil)
                     58: }
                     59:
1.10      rillig     60: // NewLineWhole creates a dummy line for logging messages that affect a file as a whole.
                     61: func NewLineWhole(fname string) *Line {
                     62:        return NewLine(fname, 0, "", nil)
                     63: }
                     64:
                     65: func (line *Line) modifiedLines() []string {
                     66:        var result []string
                     67:        result = append(result, line.before...)
                     68:        for _, raw := range line.raw {
                     69:                result = append(result, raw.textnl)
1.1       rillig     70:        }
1.10      rillig     71:        result = append(result, line.after...)
                     72:        return result
1.1       rillig     73: }
1.2       rillig     74:
1.8       rillig     75: func (line *Line) linenos() string {
                     76:        switch {
                     77:        case line.firstLine == -1:
                     78:                return "EOF"
                     79:        case line.firstLine == 0:
                     80:                return ""
                     81:        case line.firstLine == line.lastLine:
                     82:                return strconv.Itoa(int(line.firstLine))
                     83:        default:
                     84:                return strconv.Itoa(int(line.firstLine)) + "--" + strconv.Itoa(int(line.lastLine))
                     85:        }
1.1       rillig     86: }
1.8       rillig     87:
                     88: func (line *Line) ReferenceFrom(other *Line) string {
                     89:        if line.Fname != other.Fname {
1.9       rillig     90:                return cleanpath(relpath(path.Dir(other.Fname), line.Fname)) + ":" + line.linenos()
1.8       rillig     91:        }
                     92:        return "line " + line.linenos()
1.1       rillig     93: }
1.8       rillig     94:
                     95: func (line *Line) IsMultiline() bool {
                     96:        return line.firstLine > 0 && line.firstLine != line.lastLine
1.1       rillig     97: }
1.2       rillig     98:
1.8       rillig     99: func (line *Line) printSource(out io.Writer) {
                    100:        if G.opts.PrintSource {
                    101:                io.WriteString(out, "\n")
1.10      rillig    102:                for _, before := range line.before {
                    103:                        io.WriteString(out, "+ "+before)
                    104:                }
                    105:                for _, rawLine := range line.raw {
1.8       rillig    106:                        if rawLine.textnl != rawLine.orignl {
                    107:                                if rawLine.orignl != "" {
                    108:                                        io.WriteString(out, "- "+rawLine.orignl)
                    109:                                }
                    110:                                if rawLine.textnl != "" {
                    111:                                        io.WriteString(out, "+ "+rawLine.textnl)
                    112:                                }
                    113:                        } else {
                    114:                                io.WriteString(out, "> "+rawLine.orignl)
                    115:                        }
1.1       rillig    116:                }
1.10      rillig    117:                for _, after := range line.after {
                    118:                        io.WriteString(out, "+ "+after)
                    119:                }
1.1       rillig    120:        }
                    121: }
1.2       rillig    122:
1.8       rillig    123: func (line *Line) Fatalf(format string, args ...interface{}) {
                    124:        line.printSource(G.logErr)
1.10      rillig    125:        logs(llFatal, line.Fname, line.linenos(), format, fmt.Sprintf(format, args...))
1.8       rillig    126: }
                    127:
                    128: func (line *Line) Errorf(format string, args ...interface{}) {
                    129:        line.printSource(G.logOut)
1.10      rillig    130:        logs(llError, line.Fname, line.linenos(), format, fmt.Sprintf(format, args...))
1.8       rillig    131:        line.logAutofix()
                    132: }
                    133: func (line *Line) Error0(format string)             { line.Errorf(format) }
                    134: func (line *Line) Error1(format, arg1 string)       { line.Errorf(format, arg1) }
                    135: func (line *Line) Error2(format, arg1, arg2 string) { line.Errorf(format, arg1, arg2) }
                    136:
                    137: func (line *Line) Warnf(format string, args ...interface{}) {
                    138:        line.printSource(G.logOut)
1.10      rillig    139:        logs(llWarn, line.Fname, line.linenos(), format, fmt.Sprintf(format, args...))
1.8       rillig    140:        line.logAutofix()
                    141: }
                    142: func (line *Line) Warn0(format string)             { line.Warnf(format) }
                    143: func (line *Line) Warn1(format, arg1 string)       { line.Warnf(format, arg1) }
                    144: func (line *Line) Warn2(format, arg1, arg2 string) { line.Warnf(format, arg1, arg2) }
                    145:
                    146: func (line *Line) Notef(format string, args ...interface{}) {
                    147:        line.printSource(G.logOut)
1.10      rillig    148:        logs(llNote, line.Fname, line.linenos(), format, fmt.Sprintf(format, args...))
1.8       rillig    149:        line.logAutofix()
                    150: }
                    151: func (line *Line) Note0(format string)             { line.Notef(format) }
                    152: func (line *Line) Note1(format, arg1 string)       { line.Notef(format, arg1) }
                    153: func (line *Line) Note2(format, arg1, arg2 string) { line.Notef(format, arg1, arg2) }
                    154:
                    155: func (line *Line) String() string {
                    156:        return line.Fname + ":" + line.linenos() + ": " + line.Text
                    157: }
                    158:
                    159: func (line *Line) logAutofix() {
1.11    ! rillig    160:        if line.autofixMessage != "" {
        !           161:                logs(llAutofix, line.Fname, line.linenos(), "%s", line.autofixMessage)
        !           162:                line.autofixMessage = ""
1.8       rillig    163:        }
1.1       rillig    164: }
                    165:
1.8       rillig    166: func (line *Line) AutofixInsertBefore(text string) bool {
                    167:        if G.opts.PrintAutofix || G.opts.Autofix {
1.10      rillig    168:                line.before = append(line.before, text+"\n")
1.8       rillig    169:        }
                    170:        return line.RememberAutofix("Inserting a line %q before this line.", text)
1.1       rillig    171: }
1.2       rillig    172:
1.8       rillig    173: func (line *Line) AutofixInsertAfter(text string) bool {
                    174:        if G.opts.PrintAutofix || G.opts.Autofix {
1.10      rillig    175:                line.after = append(line.after, text+"\n")
1.8       rillig    176:        }
                    177:        return line.RememberAutofix("Inserting a line %q after this line.", text)
1.1       rillig    178: }
1.2       rillig    179:
1.8       rillig    180: func (line *Line) AutofixDelete() bool {
                    181:        if G.opts.PrintAutofix || G.opts.Autofix {
                    182:                for _, rawLine := range line.raw {
                    183:                        rawLine.textnl = ""
                    184:                }
                    185:        }
                    186:        return line.RememberAutofix("Deleting this line.")
1.1       rillig    187: }
1.2       rillig    188:
1.8       rillig    189: func (line *Line) AutofixReplace(from, to string) bool {
                    190:        for _, rawLine := range line.raw {
                    191:                if rawLine.Lineno != 0 {
1.1       rillig    192:                        if replaced := strings.Replace(rawLine.textnl, from, to, 1); replaced != rawLine.textnl {
1.8       rillig    193:                                if G.opts.PrintAutofix || G.opts.Autofix {
                    194:                                        rawLine.textnl = replaced
                    195:                                }
                    196:                                return line.RememberAutofix("Replacing %q with %q.", from, to)
1.1       rillig    197:                        }
                    198:                }
                    199:        }
1.8       rillig    200:        return false
1.1       rillig    201: }
1.8       rillig    202:
1.11    ! rillig    203: func (line *Line) AutofixReplaceRegexp(from RegexPattern, to string) bool {
1.8       rillig    204:        for _, rawLine := range line.raw {
                    205:                if rawLine.Lineno != 0 {
1.1       rillig    206:                        if replaced := regcomp(from).ReplaceAllString(rawLine.textnl, to); replaced != rawLine.textnl {
1.8       rillig    207:                                if G.opts.PrintAutofix || G.opts.Autofix {
                    208:                                        rawLine.textnl = replaced
                    209:                                }
                    210:                                return line.RememberAutofix("Replacing regular expression %q with %q.", from, to)
1.1       rillig    211:                        }
                    212:                }
                    213:        }
1.8       rillig    214:        return false
1.1       rillig    215: }
1.2       rillig    216:
1.8       rillig    217: func (line *Line) RememberAutofix(format string, args ...interface{}) (hasBeenFixed bool) {
                    218:        if line.firstLine < 1 {
                    219:                return false
                    220:        }
                    221:        line.changed = true
                    222:        if G.opts.Autofix {
1.10      rillig    223:                logs(llAutofix, line.Fname, line.linenos(), format, fmt.Sprintf(format, args...))
1.8       rillig    224:                return true
                    225:        }
                    226:        if G.opts.PrintAutofix {
1.11    ! rillig    227:                line.autofixMessage = fmt.Sprintf(format, args...)
1.2       rillig    228:        }
1.8       rillig    229:        return false
1.2       rillig    230: }
1.7       rillig    231:
1.8       rillig    232: func (line *Line) CheckAbsolutePathname(text string) {
1.10      rillig    233:        if G.opts.Debug {
1.8       rillig    234:                defer tracecall1(text)()
                    235:        }
1.7       rillig    236:
                    237:        // In the GNU coding standards, DESTDIR is defined as a (usually
                    238:        // empty) prefix that can be used to install files to a different
                    239:        // location from what they have been built for. Therefore
                    240:        // everything following it is considered an absolute pathname.
                    241:        //
                    242:        // Another context where absolute pathnames usually appear is in
                    243:        // assignments like "bindir=/bin".
                    244:        if m, path := match1(text, `(?:^|\$[{(]DESTDIR[)}]|[\w_]+\s*=\s*)(/(?:[^"'\s]|"[^"*]"|'[^']*')*)`); m {
                    245:                if matches(path, `^/\w`) {
1.8       rillig    246:                        checkwordAbsolutePathname(line, path)
                    247:                }
                    248:        }
                    249: }
                    250:
                    251: func (line *Line) CheckLength(maxlength int) {
                    252:        if len(line.Text) > maxlength {
                    253:                line.Warnf("Line too long (should be no more than %d characters).", maxlength)
                    254:                Explain3(
                    255:                        "Back in the old time, terminals with 80x25 characters were common.",
                    256:                        "And this is still the default size of many terminal emulators.",
                    257:                        "Moderately short lines also make reading easier.")
                    258:        }
                    259: }
                    260:
1.11    ! rillig    261: func (line *Line) CheckValidCharacters(reChar RegexPattern) {
1.8       rillig    262:        rest := regcomp(reChar).ReplaceAllString(line.Text, "")
                    263:        if rest != "" {
                    264:                uni := ""
                    265:                for _, c := range rest {
                    266:                        uni += fmt.Sprintf(" %U", c)
1.7       rillig    267:                }
1.8       rillig    268:                line.Warn1("Line contains invalid characters (%s).", uni[1:])
                    269:        }
                    270: }
                    271:
                    272: func (line *Line) CheckTrailingWhitespace() {
                    273:        if hasSuffix(line.Text, " ") || hasSuffix(line.Text, "\t") {
                    274:                if !line.AutofixReplaceRegexp(`\s+\n$`, "\n") {
                    275:                        line.Note0("Trailing white-space.")
                    276:                        Explain2(
                    277:                                "When a line ends with some white-space, that space is in most cases",
                    278:                                "irrelevant and can be removed.")
                    279:                }
                    280:        }
                    281: }
                    282:
1.11    ! rillig    283: func (line *Line) CheckRcsid(prefixRe RegexPattern, suggestedPrefix string) bool {
1.10      rillig    284:        if G.opts.Debug {
1.11    ! rillig    285:                defer tracecall(prefixRe, suggestedPrefix)()
1.8       rillig    286:        }
                    287:
                    288:        if matches(line.Text, `^`+prefixRe+`\$`+`NetBSD(?::[^\$]+)?\$$`) {
                    289:                return true
                    290:        }
                    291:
                    292:        if !line.AutofixInsertBefore(suggestedPrefix + "$" + "NetBSD$") {
                    293:                line.Error1("Expected %q.", suggestedPrefix+"$"+"NetBSD$")
                    294:                Explain3(
                    295:                        "Several files in pkgsrc must contain the CVS Id, so that their",
                    296:                        "current version can be traced back later from a binary package.",
                    297:                        "This is to ensure reproducible builds, for example for finding bugs.")
1.7       rillig    298:        }
1.8       rillig    299:        return false
1.7       rillig    300: }

CVSweb <webmaster@jp.NetBSD.org>