snapshot of https://github.com/sqlc-dev/sqlc.git (main, 2026-09-16; blobs > 131072B omitted)
Some checks failed
kotlin / test (push) Has been skipped
python / test (push) Has been skipped
typescript / test (push) Has been skipped
go / build darwin/amd64 (push) Has been cancelled
go / build linux/amd64 (push) Has been cancelled
go / build windows/amd64 (push) Has been cancelled
go / build darwin/arm64 (push) Has been cancelled
go / build linux/arm64 (push) Has been cancelled
go / build windows/arm64 (push) Has been cancelled
go / test (push) Has been cancelled
go / vuln_check (push) Has been cancelled
Some checks failed
kotlin / test (push) Has been skipped
python / test (push) Has been skipped
typescript / test (push) Has been skipped
go / build darwin/amd64 (push) Has been cancelled
go / build linux/amd64 (push) Has been cancelled
go / build windows/amd64 (push) Has been cancelled
go / build darwin/arm64 (push) Has been cancelled
go / build linux/arm64 (push) Has been cancelled
go / build windows/arm64 (push) Has been cancelled
go / test (push) Has been cancelled
go / vuln_check (push) Has been cancelled
This commit is contained in:
235
internal/sql/ast/comment.go
Normal file
235
internal/sql/ast/comment.go
Normal file
@@ -0,0 +1,235 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/sqlc-dev/sqlc/internal/sql/format"
|
||||
)
|
||||
|
||||
// File is a parsed query file: its statements together with the comments
|
||||
// the parser's lexer saw. Engines whose parsers surface their trivia — the
|
||||
// lexical channel of whitespace and comments the grammar never sees, in
|
||||
// Roslyn's terminology — return it from ParseFile, so the formatter gets
|
||||
// statements and comments from one lexer pass.
|
||||
type File struct {
|
||||
Stmts []Statement
|
||||
Comments []Comment
|
||||
}
|
||||
|
||||
// Comment is a single SQL comment, positioned by byte offsets into the
|
||||
// source the statement was parsed from — the same coordinates the engine
|
||||
// parsers stamp on nodes.
|
||||
type Comment struct {
|
||||
// Text is the comment as written, marker included ("-- x", "/* x */").
|
||||
Text string
|
||||
Start int
|
||||
End int
|
||||
// OwnLine reports that only blank space preceded the comment on its
|
||||
// line, so it leads the code after it rather than trailing the code
|
||||
// before it.
|
||||
OwnLine bool
|
||||
}
|
||||
|
||||
// Line reports whether the comment runs to the end of its line (-- or #),
|
||||
// so nothing may be printed after it on the same line.
|
||||
func (c Comment) Line() bool {
|
||||
return len(c.Text) < 2 || c.Text[0] != '/' || c.Text[1] != '*'
|
||||
}
|
||||
|
||||
// commentRec is one attached comment: the anchor nodes on either side of it
|
||||
// in print order, and whether it trails the code before it (same source
|
||||
// line) or leads the code after it (its own line).
|
||||
//
|
||||
// The next anchor is where the printer emits the comment; the prev anchor
|
||||
// is the node the comment belongs to when the tree is edited — a trailing
|
||||
// comment travels with the node it annotates, the way dave/dst attaches
|
||||
// decorations in the Go ecosystem.
|
||||
type commentRec struct {
|
||||
c Comment
|
||||
prev, next Node
|
||||
trailing bool
|
||||
}
|
||||
|
||||
// CommentTable holds a statement's comments attached to its nodes. It is
|
||||
// built once by AttachComments, before any printing (or, later, editing):
|
||||
// each comment is classified against the statement's anchor nodes by source
|
||||
// position and line, and from then on positions are never consulted again —
|
||||
// the printer emits comments by node identity, which is what lets an edited
|
||||
// or synthetic tree print its comments correctly.
|
||||
type CommentTable struct {
|
||||
recs []commentRec
|
||||
// byNext indexes recs by their next anchor, in source order; end holds
|
||||
// the comments with no anchor after them, which trail the statement.
|
||||
byNext map[Node][]int
|
||||
end []int
|
||||
taken []bool
|
||||
nTaken int
|
||||
// breaks marks the emission points where the author broke the line:
|
||||
// the printer keeps those breaks, gofmt-style, instead of imposing a
|
||||
// line width.
|
||||
breaks map[Node]bool
|
||||
}
|
||||
|
||||
// breakAt reports whether the author broke the line at this emission point.
|
||||
func (t *CommentTable) breakAt(n Node) bool {
|
||||
return t != nil && t.breaks[n]
|
||||
}
|
||||
|
||||
// Exhausted reports whether every attached comment was printed.
|
||||
func (t *CommentTable) Exhausted() bool {
|
||||
return t == nil || t.nTaken == len(t.recs)
|
||||
}
|
||||
|
||||
// take returns and consumes the comments anchored to n, in order.
|
||||
func (t *CommentTable) take(n Node) []commentRec {
|
||||
if t == nil || n == nil {
|
||||
return nil
|
||||
}
|
||||
idxs := t.byNext[n]
|
||||
if len(idxs) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]commentRec, 0, len(idxs))
|
||||
for _, i := range idxs {
|
||||
if t.taken[i] {
|
||||
continue
|
||||
}
|
||||
t.taken[i] = true
|
||||
t.nTaken++
|
||||
out = append(out, t.recs[i])
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// takeRemaining returns and consumes every comment not yet printed; the
|
||||
// statement is over, so everything left trails it.
|
||||
func (t *CommentTable) takeRemaining() []commentRec {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
var out []commentRec
|
||||
for i := range t.recs {
|
||||
if t.taken[i] {
|
||||
continue
|
||||
}
|
||||
t.taken[i] = true
|
||||
t.nTaken++
|
||||
out = append(out, t.recs[i])
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// AttachComments classifies a statement's interior comments against its
|
||||
// nodes, producing the table the printer (and any future rewriting tool)
|
||||
// works from. Placement follows gofmt's rules, decided here once from
|
||||
// source positions and lines: a comment on the same line as the code before
|
||||
// it trails that code; any other comment leads the first node printed after
|
||||
// it; a comment after the last node trails the statement.
|
||||
func AttachComments(raw *RawStmt, d format.Dialect, comments []Comment, src string) *CommentTable {
|
||||
anchors := collectAnchors(raw, d)
|
||||
lines := []int{0}
|
||||
for i := 0; i < len(src); i++ {
|
||||
if src[i] == '\n' {
|
||||
lines = append(lines, i+1)
|
||||
}
|
||||
}
|
||||
lineOf := func(pos int) int {
|
||||
return sort.SearchInts(lines, pos+1) - 1
|
||||
}
|
||||
|
||||
table := &CommentTable{byNext: make(map[Node][]int, len(anchors))}
|
||||
for _, c := range comments {
|
||||
// prev: the printed node with the greatest position before the
|
||||
// comment (for the trailing/leading call). next: the emission point
|
||||
// for the first printed node after the comment — the node itself,
|
||||
// or, when boundary markers immediately precede it in print order,
|
||||
// the earliest of those markers, which is where the printer will
|
||||
// look for this comment first.
|
||||
var prev, next Node
|
||||
prevPos := -1
|
||||
for _, a := range anchors {
|
||||
if !a.marker && a.pos < c.Start && a.pos > prevPos {
|
||||
prev, prevPos = a.node, a.pos
|
||||
}
|
||||
}
|
||||
for i, a := range anchors {
|
||||
if a.marker || a.pos <= c.Start {
|
||||
continue
|
||||
}
|
||||
j := i
|
||||
for j > 0 && anchors[j-1].marker {
|
||||
j--
|
||||
}
|
||||
next = anchors[j].node
|
||||
break
|
||||
}
|
||||
rec := commentRec{
|
||||
c: c,
|
||||
prev: prev,
|
||||
next: next,
|
||||
trailing: !c.OwnLine && prev != nil && lineOf(prevPos) == lineOf(c.Start),
|
||||
}
|
||||
table.recs = append(table.recs, rec)
|
||||
i := len(table.recs) - 1
|
||||
if next == nil {
|
||||
table.end = append(table.end, i)
|
||||
} else {
|
||||
table.byNext[next] = append(table.byNext[next], i)
|
||||
}
|
||||
}
|
||||
table.taken = make([]bool, len(table.recs))
|
||||
|
||||
// Record where the author broke the line: an emission point whose
|
||||
// neighbouring printed nodes sit on different source lines. The printer
|
||||
// keeps these breaks (and only imposes new ones for comments), so the
|
||||
// statement's shape stays the author's.
|
||||
table.breaks = make(map[Node]bool)
|
||||
for i, a := range anchors {
|
||||
if !a.marker {
|
||||
continue
|
||||
}
|
||||
prevPos, nextPos := -1, -1
|
||||
for j := i - 1; j >= 0; j-- {
|
||||
if !anchors[j].marker {
|
||||
prevPos = anchors[j].pos
|
||||
break
|
||||
}
|
||||
}
|
||||
for j := i + 1; j < len(anchors); j++ {
|
||||
if !anchors[j].marker {
|
||||
nextPos = anchors[j].pos
|
||||
break
|
||||
}
|
||||
}
|
||||
if prevPos >= 0 && nextPos >= 0 && lineOf(prevPos) != lineOf(nextPos) {
|
||||
table.breaks[a.node] = true
|
||||
}
|
||||
}
|
||||
return table
|
||||
}
|
||||
|
||||
type anchor struct {
|
||||
node Node
|
||||
pos int
|
||||
// marker anchors are emission points (beforeClause, list boundaries)
|
||||
// rather than printed nodes; they carry no position of their own.
|
||||
marker bool
|
||||
}
|
||||
|
||||
// collectAnchors renders the statement once, flat, recording every
|
||||
// positioned node in the order the printer visits them. Print order is what
|
||||
// comment emission is defined against, so classifying against it keeps the
|
||||
// attach-time decision and the print-time emission point identical.
|
||||
func collectAnchors(n Node, d format.Dialect) (out []anchor) {
|
||||
defer func() {
|
||||
// A formatter panic here surfaces later, on the real print; anchors
|
||||
// collected so far still place most comments.
|
||||
recover()
|
||||
}()
|
||||
tb := NewTrackedBuffer()
|
||||
tb.anchors = &out
|
||||
if ft, ok := n.(nodeFormatter); ok {
|
||||
ft.Format(tb, d)
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user