// SortImports sorts runs of consecutive import lines in import blocks in f. 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.Dedent.IsValid() { // Not a block: sorted by default. continue } // Identify and sort runs of specs on successive lines. i := 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. sortSpecs(fset, f, d.Specs[i:j]) i = j } } sortSpecs(fset, f, d.Specs[i:]) } }
func sortSpecs(fset *token.FileSet, f *File, specs []Spec) { // Avoid work if already sorted (also catches < 2 entries). sorted := true for i, s := range specs { if i > 0 && importPath(specs[i-1]) > importPath(s) { sorted = false break } } if sorted { return } // 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. // 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(byImportPath(specs)) 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)) }
// 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 }