-
Notifications
You must be signed in to change notification settings - Fork 29
Expand file tree
/
Copy pathlog.go
More file actions
280 lines (243 loc) · 8.02 KB
/
Copy pathlog.go
File metadata and controls
280 lines (243 loc) · 8.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
// Copyright 2015 Auburn University. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file defines the Log struct and associated methods. Every refactoring
// returns a Log, which contains informational messages, warnings, and errors
// generated during the refactoring process. If the log is nonempty, it should
// be displayed to the user before a refactoring's changes are applied.
// TERMINOLOGY: "Initial entries" are those that are added when the program is
// first loaded, before the refactoring begins. They are used to record
// semantic errors that are present file before refactoring starts. Some
// refactorings work in the presence of errors, and others may not. Therefore,
// there are two methods to modify initial entries: one that converts initial
// errors to warnings, and another that removes initial entries altogether.
package refactoring
import (
"bytes"
"fmt"
"io"
"path/filepath"
"go/ast"
"go/token"
"github.com/godoctor/godoctor/filesystem"
)
// A Severity indicates whether a log entry describes an informational message,
// a warning, or an error.
type Severity int
const (
Info Severity = iota // informational message
Warning // warning, something to be cautious of
Error // the refactoring transformation is, or might be, invalid
)
// A Entry constitutes a single entry in a Log. Every Entry has a
// severity and a message. If the filename is a nonempty string, the Entry
// is associated with a particular position in the given file. Some log
// entries are marked as "initial." These indicate semantic errors that were
// present in the input file (e.g., unresolved identifiers, unnecessary
// imports, etc.) before the refactoring was started.
type Entry struct {
isInitial bool
Severity Severity
Message string
Pos token.Pos
End token.Pos
}
func (entry *Entry) String() string {
var buffer bytes.Buffer
switch entry.Severity {
case Info:
// No prefix
case Warning:
buffer.WriteString("Warning: ")
case Error:
buffer.WriteString("Error: ")
}
buffer.WriteString(entry.Message)
return buffer.String()
}
// A Log is used to store informational messages, warnings, and errors that
// will be presented to the user before a refactoring's changes are applied.
type Log struct {
// FileSet to map log entries' Pos and End fields to file positions
Fset *token.FileSet
// Informational messages, warnings, and errors, in the (temporal)
// order they were added to the log
Entries []*Entry
}
// NewLog creates an empty Log. The Log will be unable to associate errors
// with filenames and line/column/offset information until its Fset field is
// set non-nil.
func NewLog() *Log {
return &Log{
Fset: nil,
Entries: []*Entry{}}
}
// Append adds the given entries to the end of this log, preserving their order.
func (log *Log) Append(entries []*Entry) {
for _, entry := range entries {
log.Entries = append(log.Entries, entry)
}
}
// Clear removes all Entries from the error log.
func (log *Log) Clear() {
log.Entries = []*Entry{}
}
// Infof adds an informational message (an entry with Info severity) to a log.
func (log *Log) Infof(format string, v ...interface{}) {
log.log(Info, format, v...)
}
// Info adds an informational message (an entry with Info severity) to a log.
func (log *Log) Info(entry interface{}) {
log.log(Info, "%v", entry)
}
// Warnf adds an entry with Warning severity to a log.
func (log *Log) Warnf(format string, v ...interface{}) {
log.log(Warning, format, v...)
}
// Warn adds an entry with Warning severity to a log.
func (log *Log) Warn(entry interface{}) {
log.log(Warning, "%v", entry)
}
// Errorf adds an entry with Error severity to a log.
func (log *Log) Errorf(format string, v ...interface{}) {
log.log(Error, format, v...)
}
// Error adds an entry with Error severity to a log.
func (log *Log) Error(entry interface{}) {
log.log(Error, "%v", entry)
}
func (log *Log) log(severity Severity, format string, v ...interface{}) {
log.Entries = append(log.Entries, &Entry{
isInitial: false,
Severity: severity,
Message: fmt.Sprintf(format, v...),
Pos: token.NoPos,
End: token.NoPos})
}
/*
// Associate associates the most recently-logged entry with the given filename.
func (log *Log) Associate(filename string) {
if len(log.Entries) == 0 {
return
}
entry := log.Entries[len(log.Entries)-1]
entry.Filename = displayablePath(filename)
}
*/
// AssociatePos associates the most recently-logged entry with the file and
// offset denoted by the given Pos.
func (log *Log) AssociatePos(start, end token.Pos) {
if len(log.Entries) == 0 {
return
}
entry := log.Entries[len(log.Entries)-1]
entry.Pos = start
entry.End = end
}
// AssociateNode associates the most recently-logged entry with the region of
// source code corresponding to the given AST Node.
func (log *Log) AssociateNode(node ast.Node) {
log.AssociatePos(node.Pos(), node.End())
}
// MarkInitial marks all entries that have been logged so far as initial
// entries. Subsequent entries will not be marked as initial unless this
// method is called again at a later point in time.
func (log *Log) MarkInitial() {
for _, entry := range log.Entries {
entry.isInitial = true
}
}
func (log *Log) String() string {
var buffer bytes.Buffer
log.Write(&buffer, "")
return buffer.String()
}
// Write outputs this log in a GNU-style 'file:line:col: message' format.
// Filenames are displayed relative to the given directory, if possible.
func (log *Log) Write(out io.Writer, cwd string) {
for _, entry := range log.Entries {
if log.Fset != nil && entry.Pos.IsValid() {
pos := log.Fset.Position(entry.Pos)
fmt.Fprintf(out, "%s:%d:%d: ",
displayablePath(pos.Filename, cwd),
pos.Line,
pos.Column)
}
fmt.Fprintf(out, "%s\n", entry.String())
}
}
// displayablePath returns a path for the given file relative to the given
// current directory. If a relative path cannot be determined, file is
// returned as-is. This is intended for use in displaying error messages.
func displayablePath(file, cwd string) string {
stdin, _ := filesystem.FakeStdinPath()
if file == stdin {
return "<stdin>"
}
if cwd == "" {
return file
}
absPath, err := filepath.Abs(file)
if err != nil {
absPath = file
}
relativePath, err := filepath.Rel(cwd, absPath)
if err != nil || relativePath == "" {
return file
}
return relativePath
}
// ContainsPositions returns true if the log contains at least one entry that
// has position information associated with it.
func (log *Log) ContainsPositions() bool {
return log.contains(func(entry *Entry) bool {
return entry.Pos.IsValid()
})
}
// ContainsInitialErrors returns true if the log contains at least one initial
// entry with Error severity.
func (log *Log) ContainsInitialErrors() bool {
return log.contains(func(entry *Entry) bool {
return entry.isInitial && entry.Severity >= Error
})
}
// ContainsErrors returns true if the log contains at least one error. The
// error may be an initial entry, or it may not.
func (log *Log) ContainsErrors() bool {
return log.contains(func(entry *Entry) bool {
return entry.Severity >= Error
})
}
func (log *Log) contains(predicate func(*Entry) bool) bool {
for _, entry := range log.Entries {
if predicate(entry) {
return true
}
}
return false
}
// RemoveInitialEntries removes all initial entries from the log. Entries that
// are not marked as initial are retained.
func (log *Log) RemoveInitialEntries() {
newEntries := []*Entry{}
for _, entry := range log.Entries {
if !entry.isInitial {
newEntries = append(newEntries, entry)
}
}
log.Entries = newEntries
}
// ChangeInitialErrorsToWarnings changes the severity of any initial errors to
// Warning severity.
func (log *Log) ChangeInitialErrorsToWarnings() {
newEntries := []*Entry{}
for _, entry := range log.Entries {
if entry.isInitial && entry.Severity == Error {
entry.Severity = Warning
newEntries = append(newEntries, entry)
} else {
newEntries = append(newEntries, entry)
}
}
log.Entries = newEntries
}