169 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			169 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			Go
		
	
	
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}
 | 
						|
}
 |