func newSnippet(fset *token.FileSet, decl ast.Decl, id *ast.Ident) *Snippet { // TODO instead of pretty-printing the node, should use the original source instead var buf1 bytes.Buffer writeNode(&buf1, fset, decl) // wrap text with <pre> tag var buf2 bytes.Buffer buf2.WriteString("<pre>") FormatText(&buf2, buf1.Bytes(), -1, true, id.Name, nil) buf2.WriteString("</pre>") return &Snippet{fset.Position(id.Pos()).Line, buf2.String()} }
// NewSnippet creates a text snippet from a declaration decl containing an // identifier id. Parts of the declaration not containing the identifier // may be removed for a more compact snippet. // func NewSnippet(fset *token.FileSet, decl ast.Decl, id *ast.Ident) (s *Snippet) { switch d := decl.(type) { case *ast.GenDecl: s = genSnippet(fset, d, id) case *ast.FuncDecl: s = funcSnippet(fset, d, id) } // handle failure gracefully if s == nil { var buf bytes.Buffer fmt.Fprintf(&buf, `<span class="alert">could not generate a snippet for <span class="highlight">%s</span></span>`, id.Name) s = &Snippet{fset.Position(id.Pos()).Line, buf.String()} } return }
// SortImports sorts runs of consecutive import lines in import blocks in f. // It also removes duplicate imports when it is possible to do so without data loss. func SortImports(fset *token.FileSet, f *File) { for _, d := range f.Decls { d, ok := d.(*GenDecl) if !ok || d.Tok != token.IMPORT { // Not an import declaration, so we're done. // Imports are always first. break } if !d.Lparen.IsValid() { // Not a block: sorted by default. continue } // Identify and sort runs of specs on successive lines. i := 0 specs := d.Specs[:0] for j, s := range d.Specs { if j > i && fset.Position(s.Pos()).Line > 1+fset.Position(d.Specs[j-1].End()).Line { // j begins a new run. End this one. specs = append(specs, sortSpecs(fset, f, d.Specs[i:j])...) i = j } } specs = append(specs, sortSpecs(fset, f, d.Specs[i:])...) d.Specs = specs // Deduping can leave a blank line before the rparen; clean that up. if len(d.Specs) > 0 { lastSpec := d.Specs[len(d.Specs)-1] lastLine := fset.Position(lastSpec.Pos()).Line if rParenLine := fset.Position(d.Rparen).Line; rParenLine > lastLine+1 { fset.File(d.Rparen).MergeLine(rParenLine - 1) } } } }
func sortSpecs(fset *token.FileSet, f *File, specs []Spec) []Spec { // Can't short-circuit here even if specs are already sorted, // since they might yet need deduplication. // A lone import, however, may be safely ignored. if len(specs) <= 1 { return specs } // Record positions for specs. pos := make([]posSpan, len(specs)) for i, s := range specs { pos[i] = posSpan{s.Pos(), s.End()} } // Identify comments in this range. // Any comment from pos[0].Start to the final line counts. lastLine := fset.Position(pos[len(pos)-1].End).Line cstart := len(f.Comments) cend := len(f.Comments) for i, g := range f.Comments { if g.Pos() < pos[0].Start { continue } if i < cstart { cstart = i } if fset.Position(g.End()).Line > lastLine { cend = i break } } comments := f.Comments[cstart:cend] // Assign each comment to the import spec preceding it. importComment := map[*ImportSpec][]*CommentGroup{} specIndex := 0 for _, g := range comments { for specIndex+1 < len(specs) && pos[specIndex+1].Start <= g.Pos() { specIndex++ } s := specs[specIndex].(*ImportSpec) importComment[s] = append(importComment[s], g) } // Sort the import specs by import path. // Remove duplicates, when possible without data loss. // Reassign the import paths to have the same position sequence. // Reassign each comment to abut the end of its spec. // Sort the comments by new position. sort.Sort(byImportSpec(specs)) // Dedup. Thanks to our sorting, we can just consider // adjacent pairs of imports. deduped := specs[:0] for i, s := range specs { if i == len(specs)-1 || !collapse(s, specs[i+1]) { deduped = append(deduped, s) } else { p := s.Pos() fset.File(p).MergeLine(fset.Position(p).Line) } } specs = deduped // Fix up comment positions for i, s := range specs { s := s.(*ImportSpec) if s.Name != nil { s.Name.NamePos = pos[i].Start } s.Path.ValuePos = pos[i].Start s.EndPos = pos[i].End for _, g := range importComment[s] { for _, c := range g.List { c.Slash = pos[i].End } } } sort.Sort(byCommentPos(comments)) return specs }
// NewCommentMap creates a new comment map by associating comment groups // of the comments list with the nodes of the AST specified by node. // // A comment group g is associated with a node n if: // // - g starts on the same line as n ends // - g starts on the line immediately following n, and there is // at least one empty line after g and before the next node // - g starts before n and is not associated to the node before n // via the previous rules // // NewCommentMap tries to associate a comment group to the "largest" // node possible: For instance, if the comment is a line comment // trailing an assignment, the comment is associated with the entire // assignment rather than just the last operand in the assignment. // func NewCommentMap(fset *token.FileSet, node Node, comments []*CommentGroup) CommentMap { if len(comments) == 0 { return nil // no comments to map } cmap := make(CommentMap) // set up comment reader r tmp := make([]*CommentGroup, len(comments)) copy(tmp, comments) // don't change incomming comments sortComments(tmp) r := commentListReader{fset: fset, list: tmp} // !r.eol() because len(comments) > 0 r.next() // create node list in lexical order nodes := nodeList(node) nodes = append(nodes, nil) // append sentinel // set up iteration variables var ( p Node // previous node pend token.Position // end of p pg Node // previous node group (enclosing nodes of "importance") pgend token.Position // end of pg stack nodeStack // stack of node groups ) for _, q := range nodes { var qpos token.Position if q != nil { qpos = fset.Position(q.Pos()) // current node position } else { // set fake sentinel position to infinity so that // all comments get processed before the sentinel const infinity = 1 << 30 qpos.Offset = infinity qpos.Line = infinity } // process comments before current node for r.end.Offset <= qpos.Offset { // determine recent node group if top := stack.pop(r.comment.Pos()); top != nil { pg = top pgend = fset.Position(pg.End()) } // Try to associate a comment first with a node group // (i.e., a node of "importance" such as a declaration); // if that fails, try to associate it with the most recent // node. // TODO(gri) try to simplify the logic below var assoc Node switch { case pg != nil && (pgend.Line == r.pos.Line || pgend.Line+1 == r.pos.Line && r.end.Line+1 < qpos.Line): // 1) comment starts on same line as previous node group ends, or // 2) comment starts on the line immediately after the // previous node group and there is an empty line before // the current node // => associate comment with previous node group assoc = pg case p != nil && (pend.Line == r.pos.Line || pend.Line+1 == r.pos.Line && r.end.Line+1 < qpos.Line || q == nil): // same rules apply as above for p rather than pg, // but also associate with p if we are at the end (q == nil) assoc = p default: // otherwise, associate comment with current node if q == nil { // we can only reach here if there was no p // which would imply that there were no nodes panic("internal error: no comments should be associated with sentinel") } assoc = q } cmap.addComment(assoc, r.comment) if r.eol() { return cmap } r.next() } // update previous node p = q pend = fset.Position(p.End()) // update previous node group if we see an "important" node switch q.(type) { case *File, *Field, Decl, Spec, Stmt: stack.push(q) } } return cmap }