Skip to content

Commit 4422e60

Browse files
committed
Add experimental/printer package
1 parent 16922e2 commit 4422e60

26 files changed

+1644
-0
lines changed

experimental/printer/decl.go

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
// Copyright 2020-2025 Buf Technologies, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package printer
16+
17+
import (
18+
"github.com/bufbuild/protocompile/experimental/ast"
19+
"github.com/bufbuild/protocompile/experimental/dom"
20+
"github.com/bufbuild/protocompile/experimental/seq"
21+
)
22+
23+
// printDecl dispatches to the appropriate printer based on declaration kind.
24+
func (p *printer) printDecl(decl ast.DeclAny) {
25+
switch decl.Kind() {
26+
case ast.DeclKindEmpty:
27+
p.printEmpty(decl.AsEmpty())
28+
case ast.DeclKindSyntax:
29+
p.printSyntax(decl.AsSyntax())
30+
case ast.DeclKindPackage:
31+
p.printPackage(decl.AsPackage())
32+
case ast.DeclKindImport:
33+
p.printImport(decl.AsImport())
34+
case ast.DeclKindDef:
35+
p.printDef(decl.AsDef())
36+
case ast.DeclKindBody:
37+
p.printBody(decl.AsBody())
38+
case ast.DeclKindRange:
39+
p.printRange(decl.AsRange())
40+
}
41+
}
42+
43+
func (p *printer) printEmpty(decl ast.DeclEmpty) {
44+
p.printToken(decl.Semicolon())
45+
}
46+
47+
func (p *printer) printSyntax(decl ast.DeclSyntax) {
48+
p.printToken(decl.KeywordToken())
49+
p.printToken(decl.Equals())
50+
p.printExpr(decl.Value())
51+
p.printCompactOptions(decl.Options())
52+
p.printToken(decl.Semicolon())
53+
}
54+
55+
func (p *printer) printPackage(decl ast.DeclPackage) {
56+
p.printToken(decl.KeywordToken())
57+
p.printPath(decl.Path())
58+
p.printCompactOptions(decl.Options())
59+
p.printToken(decl.Semicolon())
60+
}
61+
62+
func (p *printer) printImport(decl ast.DeclImport) {
63+
p.printToken(decl.KeywordToken())
64+
modifiers := decl.ModifierTokens()
65+
for i := range modifiers.Len() {
66+
p.printToken(modifiers.At(i))
67+
}
68+
p.printExpr(decl.ImportPath())
69+
p.printCompactOptions(decl.Options())
70+
p.printToken(decl.Semicolon())
71+
}
72+
73+
func (p *printer) printDef(decl ast.DeclDef) {
74+
switch decl.Classify() {
75+
case ast.DefKindOption:
76+
p.printOption(decl.AsOption())
77+
case ast.DefKindMessage:
78+
p.printMessage(decl.AsMessage())
79+
case ast.DefKindEnum:
80+
p.printEnum(decl.AsEnum())
81+
case ast.DefKindService:
82+
p.printService(decl.AsService())
83+
case ast.DefKindField:
84+
p.printField(decl.AsField())
85+
case ast.DefKindEnumValue:
86+
p.printEnumValue(decl.AsEnumValue())
87+
case ast.DefKindOneof:
88+
p.printOneof(decl.AsOneof())
89+
case ast.DefKindMethod:
90+
p.printMethod(decl.AsMethod())
91+
case ast.DefKindExtend:
92+
p.printExtend(decl.AsExtend())
93+
case ast.DefKindGroup:
94+
p.printGroup(decl.AsGroup())
95+
}
96+
}
97+
98+
func (p *printer) printOption(opt ast.DefOption) {
99+
p.printToken(opt.Keyword)
100+
p.printPath(opt.Path)
101+
if !opt.Equals.IsZero() {
102+
p.printToken(opt.Equals)
103+
p.printExpr(opt.Value)
104+
}
105+
p.printToken(opt.Semicolon)
106+
}
107+
108+
func (p *printer) printMessage(msg ast.DefMessage) {
109+
p.printToken(msg.Keyword)
110+
p.printToken(msg.Name)
111+
p.printBody(msg.Body)
112+
}
113+
114+
func (p *printer) printEnum(e ast.DefEnum) {
115+
p.printToken(e.Keyword)
116+
p.printToken(e.Name)
117+
p.printBody(e.Body)
118+
}
119+
120+
func (p *printer) printService(svc ast.DefService) {
121+
p.printToken(svc.Keyword)
122+
p.printToken(svc.Name)
123+
p.printBody(svc.Body)
124+
}
125+
126+
func (p *printer) printExtend(ext ast.DefExtend) {
127+
p.printToken(ext.Keyword)
128+
p.printPath(ext.Extendee)
129+
p.printBody(ext.Body)
130+
}
131+
132+
func (p *printer) printOneof(o ast.DefOneof) {
133+
p.printToken(o.Keyword)
134+
p.printToken(o.Name)
135+
p.printBody(o.Body)
136+
}
137+
138+
func (p *printer) printGroup(g ast.DefGroup) {
139+
p.printToken(g.Keyword)
140+
p.printToken(g.Name)
141+
if !g.Equals.IsZero() {
142+
p.printToken(g.Equals)
143+
p.printExpr(g.Tag)
144+
}
145+
p.printCompactOptions(g.Options)
146+
p.printBody(g.Body)
147+
}
148+
149+
func (p *printer) printField(f ast.DefField) {
150+
p.printType(f.Type)
151+
p.printToken(f.Name)
152+
if !f.Equals.IsZero() {
153+
p.printToken(f.Equals)
154+
p.printExpr(f.Tag)
155+
}
156+
p.printCompactOptions(f.Options)
157+
p.printToken(f.Semicolon)
158+
}
159+
160+
func (p *printer) printEnumValue(ev ast.DefEnumValue) {
161+
p.printToken(ev.Name)
162+
if !ev.Equals.IsZero() {
163+
p.printToken(ev.Equals)
164+
p.printExpr(ev.Tag)
165+
}
166+
p.printCompactOptions(ev.Options)
167+
p.printToken(ev.Semicolon)
168+
}
169+
170+
func (p *printer) printMethod(m ast.DefMethod) {
171+
p.printToken(m.Keyword)
172+
p.printToken(m.Name)
173+
p.printSignature(m.Signature)
174+
if !m.Body.IsZero() {
175+
p.printBody(m.Body)
176+
} else {
177+
p.printToken(m.Decl.Semicolon())
178+
}
179+
}
180+
181+
func (p *printer) printSignature(sig ast.Signature) {
182+
if sig.IsZero() {
183+
return
184+
}
185+
186+
// Print input parameter list with its brackets
187+
// Note: brackets are fused tokens, so we handle them specially to preserve whitespace
188+
inputs := sig.Inputs()
189+
inputBrackets := inputs.Brackets()
190+
if !inputBrackets.IsZero() {
191+
p.printFusedBrackets(inputBrackets, func(child *printer) {
192+
child.printTypeListContents(inputs)
193+
})
194+
}
195+
196+
// Print returns clause if present
197+
if !sig.Returns().IsZero() {
198+
p.printToken(sig.Returns())
199+
outputs := sig.Outputs()
200+
outputBrackets := outputs.Brackets()
201+
if !outputBrackets.IsZero() {
202+
p.printFusedBrackets(outputBrackets, func(child *printer) {
203+
child.printTypeListContents(outputs)
204+
})
205+
}
206+
}
207+
}
208+
209+
func (p *printer) printTypeListContents(list ast.TypeList) {
210+
for i := range list.Len() {
211+
if i > 0 {
212+
p.printToken(list.Comma(i - 1))
213+
}
214+
p.printType(list.At(i))
215+
}
216+
}
217+
218+
func (p *printer) printBody(body ast.DeclBody) {
219+
if body.IsZero() {
220+
return
221+
}
222+
223+
braces := body.Braces()
224+
openTok, closeTok := braces.StartEnd()
225+
226+
p.emitOpen(openTok)
227+
228+
decls := body.Decls()
229+
if decls.Len() > 0 {
230+
p.push(dom.Indent(p.opts.Indent, func(push dom.Sink) {
231+
child := p.childWithCursor(push, braces, openTok)
232+
for d := range seq.Values(decls) {
233+
child.printDecl(d)
234+
}
235+
child.flushRemaining()
236+
}))
237+
}
238+
239+
p.emitClose(closeTok, openTok)
240+
}
241+
242+
func (p *printer) printRange(r ast.DeclRange) {
243+
if !r.KeywordToken().IsZero() {
244+
p.printToken(r.KeywordToken())
245+
}
246+
247+
ranges := r.Ranges()
248+
for i := range ranges.Len() {
249+
if i > 0 {
250+
p.printToken(ranges.Comma(i - 1))
251+
}
252+
p.printExpr(ranges.At(i))
253+
}
254+
p.printCompactOptions(r.Options())
255+
p.printToken(r.Semicolon())
256+
}
257+
258+
func (p *printer) printCompactOptions(co ast.CompactOptions) {
259+
if co.IsZero() {
260+
return
261+
}
262+
p.printFusedBrackets(co.Brackets(), func(child *printer) {
263+
entries := co.Entries()
264+
for i := range entries.Len() {
265+
if i > 0 {
266+
child.printToken(entries.Comma(i - 1))
267+
}
268+
opt := entries.At(i)
269+
child.printPath(opt.Path)
270+
if !opt.Equals.IsZero() {
271+
child.printToken(opt.Equals)
272+
child.printExpr(opt.Value)
273+
}
274+
}
275+
})
276+
}

experimental/printer/doc.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 2020-2025 Buf Technologies, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Package printer renders AST nodes to protobuf source text.
16+
//
17+
// The main entry point is [PrintFile], which renders an entire file while
18+
// preserving original formatting (whitespace, comments, blank lines).
19+
// Use [Print] for rendering individual declarations without context.
20+
package printer

0 commit comments

Comments
 (0)