package check 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} }