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>