| package gocheck |
| |
| import ( |
| "bytes" |
| "go/ast" |
| "go/parser" |
| "go/printer" |
| "go/token" |
| "os" |
| ) |
| |
| func indent(s, with string) (r string) { |
| eol := true |
| for i := 0; i != len(s); i++ { |
| c := s[i] |
| switch { |
| case eol && c == '\n' || c == '\r': |
| case c == '\n' || c == '\r': |
| eol = true |
| case eol: |
| eol = false |
| s = s[:i] + with + s[i:] |
| i += len(with) |
| } |
| } |
| return s |
| } |
| |
| func printLine(filename string, line int) (string, error) { |
| fset := token.NewFileSet() |
| file, err := os.Open(filename) |
| if err != nil { |
| return "", err |
| } |
| fnode, err := parser.ParseFile(fset, filename, file, parser.ParseComments) |
| if err != nil { |
| return "", err |
| } |
| config := &printer.Config{Mode: printer.UseSpaces, Tabwidth: 4} |
| lp := &linePrinter{fset: fset, fnode: fnode, line: line, config: config} |
| ast.Walk(lp, fnode) |
| result := lp.output.Bytes() |
| // Comments leave \n at the end. |
| n := len(result) |
| for n > 0 && result[n-1] == '\n' { |
| n-- |
| } |
| return string(result[:n]), nil |
| } |
| |
| type linePrinter struct { |
| config *printer.Config |
| fset *token.FileSet |
| fnode *ast.File |
| line int |
| output bytes.Buffer |
| stmt ast.Stmt |
| } |
| |
| func (lp *linePrinter) emit() bool { |
| if lp.stmt != nil { |
| lp.trim(lp.stmt) |
| lp.printWithComments(lp.stmt) |
| lp.stmt = nil |
| return true |
| } |
| return false |
| } |
| |
| func (lp *linePrinter) printWithComments(n ast.Node) { |
| nfirst := lp.fset.Position(n.Pos()).Line |
| nlast := lp.fset.Position(n.End()).Line |
| for _, g := range lp.fnode.Comments { |
| cfirst := lp.fset.Position(g.Pos()).Line |
| clast := lp.fset.Position(g.End()).Line |
| if clast == nfirst-1 && lp.fset.Position(n.Pos()).Column == lp.fset.Position(g.Pos()).Column { |
| for _, c := range g.List { |
| lp.output.WriteString(c.Text) |
| lp.output.WriteByte('\n') |
| } |
| } |
| if cfirst >= nfirst && cfirst <= nlast && n.End() <= g.List[0].Slash { |
| // The printer will not include the comment if it starts past |
| // the node itself. Trick it into printing by overlapping the |
| // slash with the end of the statement. |
| g.List[0].Slash = n.End() - 1 |
| } |
| } |
| node := &printer.CommentedNode{n, lp.fnode.Comments} |
| lp.config.Fprint(&lp.output, lp.fset, node) |
| } |
| |
| func (lp *linePrinter) Visit(n ast.Node) (w ast.Visitor) { |
| if n == nil { |
| if lp.output.Len() == 0 { |
| lp.emit() |
| } |
| return nil |
| } |
| first := lp.fset.Position(n.Pos()).Line |
| last := lp.fset.Position(n.End()).Line |
| if first <= lp.line && last >= lp.line { |
| // Print the innermost statement containing the line. |
| if stmt, ok := n.(ast.Stmt); ok { |
| if _, ok := n.(*ast.BlockStmt); !ok { |
| lp.stmt = stmt |
| } |
| } |
| if first == lp.line && lp.emit() { |
| return nil |
| } |
| return lp |
| } |
| return nil |
| } |
| |
| func (lp *linePrinter) trim(n ast.Node) bool { |
| stmt, ok := n.(ast.Stmt) |
| if !ok { |
| return true |
| } |
| line := lp.fset.Position(n.Pos()).Line |
| if line != lp.line { |
| return false |
| } |
| switch stmt := stmt.(type) { |
| case *ast.IfStmt: |
| stmt.Body = lp.trimBlock(stmt.Body) |
| case *ast.SwitchStmt: |
| stmt.Body = lp.trimBlock(stmt.Body) |
| case *ast.TypeSwitchStmt: |
| stmt.Body = lp.trimBlock(stmt.Body) |
| case *ast.CaseClause: |
| stmt.Body = lp.trimList(stmt.Body) |
| case *ast.CommClause: |
| stmt.Body = lp.trimList(stmt.Body) |
| case *ast.BlockStmt: |
| stmt.List = lp.trimList(stmt.List) |
| } |
| return true |
| } |
| |
| func (lp *linePrinter) trimBlock(stmt *ast.BlockStmt) *ast.BlockStmt { |
| if !lp.trim(stmt) { |
| return lp.emptyBlock(stmt) |
| } |
| stmt.Rbrace = stmt.Lbrace |
| return stmt |
| } |
| |
| func (lp *linePrinter) trimList(stmts []ast.Stmt) []ast.Stmt { |
| for i := 0; i != len(stmts); i++ { |
| if !lp.trim(stmts[i]) { |
| stmts[i] = lp.emptyStmt(stmts[i]) |
| break |
| } |
| } |
| return stmts |
| } |
| |
| func (lp *linePrinter) emptyStmt(n ast.Node) *ast.ExprStmt { |
| return &ast.ExprStmt{&ast.Ellipsis{n.Pos(), nil}} |
| } |
| |
| func (lp *linePrinter) emptyBlock(n ast.Node) *ast.BlockStmt { |
| p := n.Pos() |
| return &ast.BlockStmt{p, []ast.Stmt{lp.emptyStmt(n)}, p} |
| } |