summaryrefslogtreecommitdiff
path: root/pkgtools/pkglint/files/autofix.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkgtools/pkglint/files/autofix.go')
-rw-r--r--pkgtools/pkglint/files/autofix.go128
1 files changed, 99 insertions, 29 deletions
diff --git a/pkgtools/pkglint/files/autofix.go b/pkgtools/pkglint/files/autofix.go
index 20e277bd77c..0063d5d2eb0 100644
--- a/pkgtools/pkglint/files/autofix.go
+++ b/pkgtools/pkglint/files/autofix.go
@@ -6,6 +6,7 @@ import (
"netbsd.org/pkglint/regex"
"netbsd.org/pkglint/trace"
"os"
+ "strconv"
"strings"
)
@@ -15,16 +16,20 @@ import (
// until they are written to disk by SaveAutofixChanges.
type Autofix struct {
line Line
- linesBefore []string // Newly inserted lines, including \n
- lines []*RawLine // Original lines, available for diff
- linesAfter []string // Newly inserted lines, including \n
- modified bool // Modified in memory, but not necessarily written back to disk
- descrFormat string // Human-readable description of the latest modification
- descrArgs []interface{} //
- level *LogLevel //
- diagFormat string // Is printed only if it couldn't be fixed automatically
- diagArgs []interface{} //
- explanation []string // Is printed together with the diagnostic
+ linesBefore []string // Newly inserted lines, including \n
+ lines []*RawLine // Original lines, available for diff
+ linesAfter []string // Newly inserted lines, including \n
+ modified bool // Modified in memory, but not necessarily written back to disk
+ actions []autofixAction // Human-readable description of the actual autofix actions
+ level *LogLevel //
+ diagFormat string // Is printed only if it couldn't be fixed automatically
+ diagArgs []interface{} //
+ explanation []string // Is printed together with the diagnostic
+}
+
+type autofixAction struct {
+ description string
+ lineno int
}
func NewAutofix(line Line) *Autofix {
@@ -34,17 +39,24 @@ func NewAutofix(line Line) *Autofix {
}
func (fix *Autofix) Replace(from string, to string) {
+ fix.ReplaceAfter("", from, to)
+}
+
+// ReplaceAfter replaces the text "prefix+from" with "prefix+to",
+// but in the diagnostic, only the replacement of "from" with "to"
+// is mentioned.
+func (fix *Autofix) ReplaceAfter(prefix, from string, to string) {
if fix.skip() {
return
}
for _, rawLine := range fix.lines {
if rawLine.Lineno != 0 {
- if replaced := strings.Replace(rawLine.textnl, from, to, 1); replaced != rawLine.textnl {
+ if replaced := strings.Replace(rawLine.textnl, prefix+from, prefix+to, 1); replaced != rawLine.textnl {
if G.opts.PrintAutofix || G.opts.Autofix {
rawLine.textnl = replaced
}
- fix.Describef("Replacing %q with %q.", from, to)
+ fix.Describef(rawLine.Lineno, "Replacing %q with %q.", from, to)
}
}
}
@@ -61,19 +73,71 @@ func (fix *Autofix) ReplaceRegex(from regex.Pattern, to string) {
if G.opts.PrintAutofix || G.opts.Autofix {
rawLine.textnl = replaced
}
- fix.Describef("Replacing regular expression %q with %q.", from, to)
+ fix.Describef(rawLine.Lineno, "Replacing regular expression %q with %q.", from, to)
}
}
}
}
+func (fix *Autofix) Realign(mkline MkLine, newWidth int) {
+ if fix.skip() || !mkline.IsMultiline() || !mkline.IsVarassign() {
+ return
+ }
+
+ normalized := true // Whether all indentation is tabs, followed by spaces.
+ oldWidth := 0 // The minimum required indentation in the original lines.
+
+ {
+ // Interpreting the continuation marker as variable value
+ // is cheating, but works well.
+ m, _, _, _, valueAlign, value, _, _ := MatchVarassign(mkline.raw[0].orignl)
+ if m && value != "\\" {
+ oldWidth = tabWidth(valueAlign)
+ }
+ }
+
+ for _, rawLine := range fix.lines[1:] {
+ _, space := regex.Match1(rawLine.textnl, `^(\s*)`)
+ width := tabWidth(space)
+ if oldWidth == 0 || width < oldWidth {
+ oldWidth = width
+ }
+ if !regex.Matches(space, `^\t*\s{0,7}`) {
+ normalized = false
+ }
+ }
+
+ if normalized && newWidth == oldWidth {
+ return
+ }
+
+ // Continuation lines with the minimal unambiguous indentation
+ // attempt to keep the indentation as small as possible, so don't
+ // realign them.
+ if oldWidth == 8 {
+ return
+ }
+
+ for _, rawLine := range fix.lines[1:] {
+ _, oldSpace := regex.Match1(rawLine.textnl, `^(\s*)`)
+ newWidth := tabWidth(oldSpace) - oldWidth + newWidth
+ newSpace := strings.Repeat("\t", newWidth/8) + strings.Repeat(" ", newWidth%8)
+ if replaced := strings.Replace(rawLine.textnl, oldSpace, newSpace, 1); replaced != rawLine.textnl {
+ if G.opts.PrintAutofix || G.opts.Autofix {
+ rawLine.textnl = replaced
+ }
+ fix.Describef(rawLine.Lineno, "Replacing indentation %q with %q.", oldSpace, newSpace)
+ }
+ }
+}
+
func (fix *Autofix) InsertBefore(text string) {
if fix.skip() {
return
}
fix.linesBefore = append(fix.linesBefore, text+"\n")
- fix.Describef("Inserting a line %q before this line.", text)
+ fix.Describef(fix.lines[0].Lineno, "Inserting a line %q before this line.", text)
}
func (fix *Autofix) InsertAfter(text string) {
@@ -82,7 +146,7 @@ func (fix *Autofix) InsertAfter(text string) {
}
fix.linesAfter = append(fix.linesAfter, text+"\n")
- fix.Describef("Inserting a line %q after this line.", text)
+ fix.Describef(fix.lines[len(fix.lines)-1].Lineno, "Inserting a line %q after this line.", text)
}
func (fix *Autofix) Delete() {
@@ -91,34 +155,40 @@ func (fix *Autofix) Delete() {
}
for _, line := range fix.lines {
+ fix.Describef(line.Lineno, "Deleting this line.")
line.textnl = ""
}
- fix.Describef("Deleting this line.")
}
-func (fix *Autofix) Describef(format string, args ...interface{}) {
- fix.descrFormat = format
- fix.descrArgs = args
+// Describef remembers a description of the actual fix
+// for logging it later when Apply is called.
+// There may be multiple fixes in one pass.
+func (fix *Autofix) Describef(lineno int, format string, args ...interface{}) {
+ fix.actions = append(fix.actions, autofixAction{fmt.Sprintf(format, args...), lineno})
}
+// Notef remembers the note for logging it later when Apply is called.
func (fix *Autofix) Notef(format string, args ...interface{}) {
fix.level = llNote
fix.diagFormat = format
fix.diagArgs = args
}
+// Notef remembers the warning for logging it later when Apply is called.
func (fix *Autofix) Warnf(format string, args ...interface{}) {
fix.level = llWarn
fix.diagFormat = format
fix.diagArgs = args
}
+// Notef remembers the error for logging it later when Apply is called.
func (fix *Autofix) Errorf(format string, args ...interface{}) {
fix.level = llError
fix.diagFormat = format
fix.diagArgs = args
}
+// Explain remembers the explanation for logging it later when Apply is called.
func (fix *Autofix) Explain(explanation ...string) {
fix.explanation = explanation
}
@@ -134,17 +204,19 @@ func (fix *Autofix) Apply() {
return
}
- if shallBeLogged(fix.diagFormat) && fix.descrFormat != "" {
- logDiagnostic := fix.level != nil && fix.diagFormat != "Silent-Magic-Diagnostic" && !G.opts.Autofix
+ if shallBeLogged(fix.diagFormat) {
+ logDiagnostic := fix.level != nil && fix.diagFormat != "Silent-Magic-Diagnostic" &&
+ !(G.opts.Autofix && !G.opts.PrintAutofix) && len(fix.actions) > 0
if logDiagnostic {
msg := fmt.Sprintf(fix.diagFormat, fix.diagArgs...)
logs(fix.level, line.Filename, line.Linenos(), fix.diagFormat, msg)
}
- logRepair := G.opts.Autofix || G.opts.PrintAutofix
+ logRepair := len(fix.actions) > 0 && (G.opts.Autofix || G.opts.PrintAutofix)
if logRepair {
- msg := fmt.Sprintf(fix.descrFormat, fix.descrArgs...)
- logs(llAutofix, line.Filename, line.Linenos(), "", msg)
+ for _, action := range fix.actions {
+ logs(llAutofix, line.Filename, strconv.Itoa(action.lineno), "", action.description)
+ }
}
if logDiagnostic || logRepair {
@@ -157,10 +229,9 @@ func (fix *Autofix) Apply() {
}
}
- fix.modified = fix.modified || fix.descrFormat != ""
+ fix.modified = fix.modified || len(fix.actions) > 0
- fix.descrFormat = ""
- fix.descrArgs = nil
+ fix.actions = nil
fix.level = nil
fix.diagFormat = ""
fix.diagArgs = nil
@@ -168,6 +239,7 @@ func (fix *Autofix) Apply() {
}
func (fix *Autofix) skip() bool {
+ // This check is necessary for the --only command line option.
if fix.diagFormat == "" {
panic("Autofix: The diagnostic must be given before the action.")
}
@@ -230,8 +302,6 @@ func SaveAutofixChanges(lines []Line) (autofixed bool) {
NewLineWhole(fname).Errorf("Cannot overwrite with auto-fixed content.")
continue
}
- msg := "Has been auto-fixed. Please re-run pkglint."
- logs(llAutofix, fname, "", msg, msg)
autofixed = true
}
return