1// Copyright 2013 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package godoc is a work-in-progress (2013-07-17) package to
6// begin splitting up the godoc binary into multiple pieces.
7//
8// This package comment will evolve over time as this package splits
9// into smaller pieces.
10package godoc // import "golang.org/x/tools/godoc"
11
12import (
13	"bufio"
14	"bytes"
15	"fmt"
16	"go/ast"
17	"go/doc"
18	"go/format"
19	"go/printer"
20	"go/token"
21	htmltemplate "html/template"
22	"io"
23	"log"
24	"os"
25	pathpkg "path"
26	"regexp"
27	"strconv"
28	"strings"
29	"text/template"
30	"time"
31	"unicode"
32	"unicode/utf8"
33)
34
35// Fake relative package path for built-ins. Documentation for all globals
36// (not just exported ones) will be shown for packages in this directory,
37// and there will be no association of consts, vars, and factory functions
38// with types (see issue 6645).
39const builtinPkgPath = "builtin"
40
41// FuncMap defines template functions used in godoc templates.
42//
43// Convention: template function names ending in "_html" or "_url" produce
44//             HTML- or URL-escaped strings; all other function results may
45//             require explicit escaping in the template.
46func (p *Presentation) FuncMap() template.FuncMap {
47	p.initFuncMapOnce.Do(p.initFuncMap)
48	return p.funcMap
49}
50
51func (p *Presentation) TemplateFuncs() template.FuncMap {
52	p.initFuncMapOnce.Do(p.initFuncMap)
53	return p.templateFuncs
54}
55
56func (p *Presentation) initFuncMap() {
57	if p.Corpus == nil {
58		panic("nil Presentation.Corpus")
59	}
60	p.templateFuncs = template.FuncMap{
61		"code": p.code,
62	}
63	p.funcMap = template.FuncMap{
64		// various helpers
65		"filename": filenameFunc,
66		"repeat":   strings.Repeat,
67		"since":    p.Corpus.pkgAPIInfo.sinceVersionFunc,
68
69		// access to FileInfos (directory listings)
70		"fileInfoName": fileInfoNameFunc,
71		"fileInfoTime": fileInfoTimeFunc,
72
73		// access to search result information
74		"infoKind_html":    infoKind_htmlFunc,
75		"infoLine":         p.infoLineFunc,
76		"infoSnippet_html": p.infoSnippet_htmlFunc,
77
78		// formatting of AST nodes
79		"node":         p.nodeFunc,
80		"node_html":    p.node_htmlFunc,
81		"comment_html": comment_htmlFunc,
82		"sanitize":     sanitizeFunc,
83
84		// support for URL attributes
85		"pkgLink":       pkgLinkFunc,
86		"srcLink":       srcLinkFunc,
87		"posLink_url":   newPosLink_urlFunc(srcPosLinkFunc),
88		"docLink":       docLinkFunc,
89		"queryLink":     queryLinkFunc,
90		"srcBreadcrumb": srcBreadcrumbFunc,
91		"srcToPkgLink":  srcToPkgLinkFunc,
92
93		// formatting of Examples
94		"example_html":   p.example_htmlFunc,
95		"example_name":   p.example_nameFunc,
96		"example_suffix": p.example_suffixFunc,
97
98		// formatting of analysis information
99		"callgraph_html":  p.callgraph_htmlFunc,
100		"implements_html": p.implements_htmlFunc,
101		"methodset_html":  p.methodset_htmlFunc,
102
103		// formatting of Notes
104		"noteTitle": noteTitle,
105
106		// Number operation
107		"multiply": multiply,
108
109		// formatting of PageInfoMode query string
110		"modeQueryString": modeQueryString,
111
112		// check whether to display third party section or not
113		"hasThirdParty": hasThirdParty,
114
115		// get the no. of columns to split the toc in search page
116		"tocColCount": tocColCount,
117	}
118	if p.URLForSrc != nil {
119		p.funcMap["srcLink"] = p.URLForSrc
120	}
121	if p.URLForSrcPos != nil {
122		p.funcMap["posLink_url"] = newPosLink_urlFunc(p.URLForSrcPos)
123	}
124	if p.URLForSrcQuery != nil {
125		p.funcMap["queryLink"] = p.URLForSrcQuery
126	}
127}
128
129func multiply(a, b int) int { return a * b }
130
131func filenameFunc(path string) string {
132	_, localname := pathpkg.Split(path)
133	return localname
134}
135
136func fileInfoNameFunc(fi os.FileInfo) string {
137	name := fi.Name()
138	if fi.IsDir() {
139		name += "/"
140	}
141	return name
142}
143
144func fileInfoTimeFunc(fi os.FileInfo) string {
145	if t := fi.ModTime(); t.Unix() != 0 {
146		return t.Local().String()
147	}
148	return "" // don't return epoch if time is obviously not set
149}
150
151// The strings in infoKinds must be properly html-escaped.
152var infoKinds = [nKinds]string{
153	PackageClause: "package clause",
154	ImportDecl:    "import decl",
155	ConstDecl:     "const decl",
156	TypeDecl:      "type decl",
157	VarDecl:       "var decl",
158	FuncDecl:      "func decl",
159	MethodDecl:    "method decl",
160	Use:           "use",
161}
162
163func infoKind_htmlFunc(info SpotInfo) string {
164	return infoKinds[info.Kind()] // infoKind entries are html-escaped
165}
166
167func (p *Presentation) infoLineFunc(info SpotInfo) int {
168	line := info.Lori()
169	if info.IsIndex() {
170		index, _ := p.Corpus.searchIndex.Get()
171		if index != nil {
172			line = index.(*Index).Snippet(line).Line
173		} else {
174			// no line information available because
175			// we don't have an index - this should
176			// never happen; be conservative and don't
177			// crash
178			line = 0
179		}
180	}
181	return line
182}
183
184func (p *Presentation) infoSnippet_htmlFunc(info SpotInfo) string {
185	if info.IsIndex() {
186		index, _ := p.Corpus.searchIndex.Get()
187		// Snippet.Text was HTML-escaped when it was generated
188		return index.(*Index).Snippet(info.Lori()).Text
189	}
190	return `<span class="alert">no snippet text available</span>`
191}
192
193func (p *Presentation) nodeFunc(info *PageInfo, node interface{}) string {
194	var buf bytes.Buffer
195	p.writeNode(&buf, info, info.FSet, node)
196	return buf.String()
197}
198
199func (p *Presentation) node_htmlFunc(info *PageInfo, node interface{}, linkify bool) string {
200	var buf1 bytes.Buffer
201	p.writeNode(&buf1, info, info.FSet, node)
202
203	var buf2 bytes.Buffer
204	if n, _ := node.(ast.Node); n != nil && linkify && p.DeclLinks {
205		LinkifyText(&buf2, buf1.Bytes(), n)
206		if st, name := isStructTypeDecl(n); st != nil {
207			addStructFieldIDAttributes(&buf2, name, st)
208		}
209	} else {
210		FormatText(&buf2, buf1.Bytes(), -1, true, "", nil)
211	}
212
213	return buf2.String()
214}
215
216// isStructTypeDecl checks whether n is a struct declaration.
217// It either returns a non-nil StructType and its name, or zero values.
218func isStructTypeDecl(n ast.Node) (st *ast.StructType, name string) {
219	gd, ok := n.(*ast.GenDecl)
220	if !ok || gd.Tok != token.TYPE {
221		return nil, ""
222	}
223	if gd.Lparen > 0 {
224		// Parenthesized type. Who does that, anyway?
225		// TODO: Reportedly gri does. Fix this to handle that too.
226		return nil, ""
227	}
228	if len(gd.Specs) != 1 {
229		return nil, ""
230	}
231	ts, ok := gd.Specs[0].(*ast.TypeSpec)
232	if !ok {
233		return nil, ""
234	}
235	st, ok = ts.Type.(*ast.StructType)
236	if !ok {
237		return nil, ""
238	}
239	return st, ts.Name.Name
240}
241
242// addStructFieldIDAttributes modifies the contents of buf such that
243// all struct fields of the named struct have <span id='name.Field'>
244// in them, so people can link to /#Struct.Field.
245func addStructFieldIDAttributes(buf *bytes.Buffer, name string, st *ast.StructType) {
246	if st.Fields == nil {
247		return
248	}
249	// needsLink is a set of identifiers that still need to be
250	// linked, where value == key, to avoid an allocation in func
251	// linkedField.
252	needsLink := make(map[string]string)
253
254	for _, f := range st.Fields.List {
255		if len(f.Names) == 0 {
256			continue
257		}
258		fieldName := f.Names[0].Name
259		needsLink[fieldName] = fieldName
260	}
261	var newBuf bytes.Buffer
262	foreachLine(buf.Bytes(), func(line []byte) {
263		if fieldName := linkedField(line, needsLink); fieldName != "" {
264			fmt.Fprintf(&newBuf, `<span id="%s.%s"></span>`, name, fieldName)
265			delete(needsLink, fieldName)
266		}
267		newBuf.Write(line)
268	})
269	buf.Reset()
270	buf.Write(newBuf.Bytes())
271}
272
273// foreachLine calls fn for each line of in, where a line includes
274// the trailing "\n", except on the last line, if it doesn't exist.
275func foreachLine(in []byte, fn func(line []byte)) {
276	for len(in) > 0 {
277		nl := bytes.IndexByte(in, '\n')
278		if nl == -1 {
279			fn(in)
280			return
281		}
282		fn(in[:nl+1])
283		in = in[nl+1:]
284	}
285}
286
287// commentPrefix is the line prefix for comments after they've been HTMLified.
288var commentPrefix = []byte(`<span class="comment">// `)
289
290// linkedField determines whether the given line starts with an
291// identifer in the provided ids map (mapping from identifier to the
292// same identifier). The line can start with either an identifier or
293// an identifier in a comment. If one matches, it returns the
294// identifier that matched. Otherwise it returns the empty string.
295func linkedField(line []byte, ids map[string]string) string {
296	line = bytes.TrimSpace(line)
297
298	// For fields with a doc string of the
299	// conventional form, we put the new span into
300	// the comment instead of the field.
301	// The "conventional" form is a complete sentence
302	// per https://golang.org/s/style#comment-sentences like:
303	//
304	//    // Foo is an optional Fooer to foo the foos.
305	//    Foo Fooer
306	//
307	// In this case, we want the #StructName.Foo
308	// link to make the browser go to the comment
309	// line "Foo is an optional Fooer" instead of
310	// the "Foo Fooer" line, which could otherwise
311	// obscure the docs above the browser's "fold".
312	//
313	// TODO: do this better, so it works for all
314	// comments, including unconventional ones.
315	if bytes.HasPrefix(line, commentPrefix) {
316		line = line[len(commentPrefix):]
317	}
318	id := scanIdentifier(line)
319	if len(id) == 0 {
320		// No leading identifier. Avoid map lookup for
321		// somewhat common case.
322		return ""
323	}
324	return ids[string(id)]
325}
326
327// scanIdentifier scans a valid Go identifier off the front of v and
328// either returns a subslice of v if there's a valid identifier, or
329// returns a zero-length slice.
330func scanIdentifier(v []byte) []byte {
331	var n int // number of leading bytes of v belonging to an identifier
332	for {
333		r, width := utf8.DecodeRune(v[n:])
334		if !(isLetter(r) || n > 0 && isDigit(r)) {
335			break
336		}
337		n += width
338	}
339	return v[:n]
340}
341
342func isLetter(ch rune) bool {
343	return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= utf8.RuneSelf && unicode.IsLetter(ch)
344}
345
346func isDigit(ch rune) bool {
347	return '0' <= ch && ch <= '9' || ch >= utf8.RuneSelf && unicode.IsDigit(ch)
348}
349
350func comment_htmlFunc(comment string) string {
351	var buf bytes.Buffer
352	// TODO(gri) Provide list of words (e.g. function parameters)
353	//           to be emphasized by ToHTML.
354	doc.ToHTML(&buf, comment, nil) // does html-escaping
355	return buf.String()
356}
357
358// sanitizeFunc sanitizes the argument src by replacing newlines with
359// blanks, removing extra blanks, and by removing trailing whitespace
360// and commas before closing parentheses.
361func sanitizeFunc(src string) string {
362	buf := make([]byte, len(src))
363	j := 0      // buf index
364	comma := -1 // comma index if >= 0
365	for i := 0; i < len(src); i++ {
366		ch := src[i]
367		switch ch {
368		case '\t', '\n', ' ':
369			// ignore whitespace at the beginning, after a blank, or after opening parentheses
370			if j == 0 {
371				continue
372			}
373			if p := buf[j-1]; p == ' ' || p == '(' || p == '{' || p == '[' {
374				continue
375			}
376			// replace all whitespace with blanks
377			ch = ' '
378		case ',':
379			comma = j
380		case ')', '}', ']':
381			// remove any trailing comma
382			if comma >= 0 {
383				j = comma
384			}
385			// remove any trailing whitespace
386			if j > 0 && buf[j-1] == ' ' {
387				j--
388			}
389		default:
390			comma = -1
391		}
392		buf[j] = ch
393		j++
394	}
395	// remove trailing blank, if any
396	if j > 0 && buf[j-1] == ' ' {
397		j--
398	}
399	return string(buf[:j])
400}
401
402type PageInfo struct {
403	Dirname  string // directory containing the package
404	Err      error  // error or nil
405	GoogleCN bool   // page is being served from golang.google.cn
406
407	Mode PageInfoMode // display metadata from query string
408
409	// package info
410	FSet       *token.FileSet         // nil if no package documentation
411	PDoc       *doc.Package           // nil if no package documentation
412	Examples   []*doc.Example         // nil if no example code
413	Notes      map[string][]*doc.Note // nil if no package Notes
414	PAst       map[string]*ast.File   // nil if no AST with package exports
415	IsMain     bool                   // true for package main
416	IsFiltered bool                   // true if results were filtered
417
418	// analysis info
419	TypeInfoIndex  map[string]int  // index of JSON datum for type T (if -analysis=type)
420	AnalysisData   htmltemplate.JS // array of TypeInfoJSON values
421	CallGraph      htmltemplate.JS // array of PCGNodeJSON values    (if -analysis=pointer)
422	CallGraphIndex map[string]int  // maps func name to index in CallGraph
423
424	// directory info
425	Dirs    *DirList  // nil if no directory information
426	DirTime time.Time // directory time stamp
427	DirFlat bool      // if set, show directory in a flat (non-indented) manner
428}
429
430func (info *PageInfo) IsEmpty() bool {
431	return info.Err != nil || info.PAst == nil && info.PDoc == nil && info.Dirs == nil
432}
433
434func pkgLinkFunc(path string) string {
435	// because of the irregular mapping under goroot
436	// we need to correct certain relative paths
437	path = strings.TrimPrefix(path, "/")
438	path = strings.TrimPrefix(path, "src/")
439	path = strings.TrimPrefix(path, "pkg/")
440	return "pkg/" + path
441}
442
443// srcToPkgLinkFunc builds an <a> tag linking to the package
444// documentation of relpath.
445func srcToPkgLinkFunc(relpath string) string {
446	relpath = pkgLinkFunc(relpath)
447	relpath = pathpkg.Dir(relpath)
448	if relpath == "pkg" {
449		return `<a href="/pkg">Index</a>`
450	}
451	return fmt.Sprintf(`<a href="/%s">%s</a>`, relpath, relpath[len("pkg/"):])
452}
453
454// srcBreadcrumbFun converts each segment of relpath to a HTML <a>.
455// Each segment links to its corresponding src directories.
456func srcBreadcrumbFunc(relpath string) string {
457	segments := strings.Split(relpath, "/")
458	var buf bytes.Buffer
459	var selectedSegment string
460	var selectedIndex int
461
462	if strings.HasSuffix(relpath, "/") {
463		// relpath is a directory ending with a "/".
464		// Selected segment is the segment before the last slash.
465		selectedIndex = len(segments) - 2
466		selectedSegment = segments[selectedIndex] + "/"
467	} else {
468		selectedIndex = len(segments) - 1
469		selectedSegment = segments[selectedIndex]
470	}
471
472	for i := range segments[:selectedIndex] {
473		buf.WriteString(fmt.Sprintf(`<a href="/%s">%s</a>/`,
474			strings.Join(segments[:i+1], "/"),
475			segments[i],
476		))
477	}
478
479	buf.WriteString(`<span class="text-muted">`)
480	buf.WriteString(selectedSegment)
481	buf.WriteString(`</span>`)
482	return buf.String()
483}
484
485func newPosLink_urlFunc(srcPosLinkFunc func(s string, line, low, high int) string) func(info *PageInfo, n interface{}) string {
486	// n must be an ast.Node or a *doc.Note
487	return func(info *PageInfo, n interface{}) string {
488		var pos, end token.Pos
489
490		switch n := n.(type) {
491		case ast.Node:
492			pos = n.Pos()
493			end = n.End()
494		case *doc.Note:
495			pos = n.Pos
496			end = n.End
497		default:
498			panic(fmt.Sprintf("wrong type for posLink_url template formatter: %T", n))
499		}
500
501		var relpath string
502		var line int
503		var low, high int // selection offset range
504
505		if pos.IsValid() {
506			p := info.FSet.Position(pos)
507			relpath = p.Filename
508			line = p.Line
509			low = p.Offset
510		}
511		if end.IsValid() {
512			high = info.FSet.Position(end).Offset
513		}
514
515		return srcPosLinkFunc(relpath, line, low, high)
516	}
517}
518
519func srcPosLinkFunc(s string, line, low, high int) string {
520	s = srcLinkFunc(s)
521	var buf bytes.Buffer
522	template.HTMLEscape(&buf, []byte(s))
523	// selection ranges are of form "s=low:high"
524	if low < high {
525		fmt.Fprintf(&buf, "?s=%d:%d", low, high) // no need for URL escaping
526		// if we have a selection, position the page
527		// such that the selection is a bit below the top
528		line -= 10
529		if line < 1 {
530			line = 1
531		}
532	}
533	// line id's in html-printed source are of the
534	// form "L%d" where %d stands for the line number
535	if line > 0 {
536		fmt.Fprintf(&buf, "#L%d", line) // no need for URL escaping
537	}
538	return buf.String()
539}
540
541func srcLinkFunc(s string) string {
542	s = pathpkg.Clean("/" + s)
543	if !strings.HasPrefix(s, "/src/") {
544		s = "/src" + s
545	}
546	return s
547}
548
549// queryLinkFunc returns a URL for a line in a source file with a highlighted
550// query term.
551// s is expected to be a path to a source file.
552// query is expected to be a string that has already been appropriately escaped
553// for use in a URL query.
554func queryLinkFunc(s, query string, line int) string {
555	url := pathpkg.Clean("/"+s) + "?h=" + query
556	if line > 0 {
557		url += "#L" + strconv.Itoa(line)
558	}
559	return url
560}
561
562func docLinkFunc(s string, ident string) string {
563	return pathpkg.Clean("/pkg/"+s) + "/#" + ident
564}
565
566func (p *Presentation) example_htmlFunc(info *PageInfo, funcName string) string {
567	var buf bytes.Buffer
568	for _, eg := range info.Examples {
569		name := stripExampleSuffix(eg.Name)
570
571		if name != funcName {
572			continue
573		}
574
575		// print code
576		cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments}
577		code := p.node_htmlFunc(info, cnode, true)
578		out := eg.Output
579		wholeFile := true
580
581		// Additional formatting if this is a function body.
582		if n := len(code); n >= 2 && code[0] == '{' && code[n-1] == '}' {
583			wholeFile = false
584			// remove surrounding braces
585			code = code[1 : n-1]
586			// unindent
587			code = replaceLeadingIndentation(code, strings.Repeat(" ", p.TabWidth), "")
588			// remove output comment
589			if loc := exampleOutputRx.FindStringIndex(code); loc != nil {
590				code = strings.TrimSpace(code[:loc[0]])
591			}
592		}
593
594		// Write out the playground code in standard Go style
595		// (use tabs, no comment highlight, etc).
596		play := ""
597		if eg.Play != nil && p.ShowPlayground {
598			var buf bytes.Buffer
599			eg.Play.Comments = filterOutBuildAnnotations(eg.Play.Comments)
600			if err := format.Node(&buf, info.FSet, eg.Play); err != nil {
601				log.Print(err)
602			} else {
603				play = buf.String()
604			}
605		}
606
607		// Drop output, as the output comment will appear in the code.
608		if wholeFile && play == "" {
609			out = ""
610		}
611
612		if p.ExampleHTML == nil {
613			out = ""
614			return ""
615		}
616
617		err := p.ExampleHTML.Execute(&buf, struct {
618			Name, Doc, Code, Play, Output string
619			GoogleCN                      bool
620		}{eg.Name, eg.Doc, code, play, out, info.GoogleCN})
621		if err != nil {
622			log.Print(err)
623		}
624	}
625	return buf.String()
626}
627
628func filterOutBuildAnnotations(cg []*ast.CommentGroup) []*ast.CommentGroup {
629	if len(cg) == 0 {
630		return cg
631	}
632
633	for i := range cg {
634		if !strings.HasPrefix(cg[i].Text(), "+build ") {
635			// Found the first non-build tag, return from here until the end
636			// of the slice.
637			return cg[i:]
638		}
639	}
640
641	// There weren't any non-build tags, return an empty slice.
642	return []*ast.CommentGroup{}
643}
644
645// example_nameFunc takes an example function name and returns its display
646// name. For example, "Foo_Bar_quux" becomes "Foo.Bar (Quux)".
647func (p *Presentation) example_nameFunc(s string) string {
648	name, suffix := splitExampleName(s)
649	// replace _ with . for method names
650	name = strings.Replace(name, "_", ".", 1)
651	// use "Package" if no name provided
652	if name == "" {
653		name = "Package"
654	}
655	return name + suffix
656}
657
658// example_suffixFunc takes an example function name and returns its suffix in
659// parenthesized form. For example, "Foo_Bar_quux" becomes " (Quux)".
660func (p *Presentation) example_suffixFunc(name string) string {
661	_, suffix := splitExampleName(name)
662	return suffix
663}
664
665// implements_html returns the "> Implements" toggle for a package-level named type.
666// Its contents are populated from JSON data by client-side JS at load time.
667func (p *Presentation) implements_htmlFunc(info *PageInfo, typeName string) string {
668	if p.ImplementsHTML == nil {
669		return ""
670	}
671	index, ok := info.TypeInfoIndex[typeName]
672	if !ok {
673		return ""
674	}
675	var buf bytes.Buffer
676	err := p.ImplementsHTML.Execute(&buf, struct{ Index int }{index})
677	if err != nil {
678		log.Print(err)
679	}
680	return buf.String()
681}
682
683// methodset_html returns the "> Method set" toggle for a package-level named type.
684// Its contents are populated from JSON data by client-side JS at load time.
685func (p *Presentation) methodset_htmlFunc(info *PageInfo, typeName string) string {
686	if p.MethodSetHTML == nil {
687		return ""
688	}
689	index, ok := info.TypeInfoIndex[typeName]
690	if !ok {
691		return ""
692	}
693	var buf bytes.Buffer
694	err := p.MethodSetHTML.Execute(&buf, struct{ Index int }{index})
695	if err != nil {
696		log.Print(err)
697	}
698	return buf.String()
699}
700
701// callgraph_html returns the "> Call graph" toggle for a package-level func.
702// Its contents are populated from JSON data by client-side JS at load time.
703func (p *Presentation) callgraph_htmlFunc(info *PageInfo, recv, name string) string {
704	if p.CallGraphHTML == nil {
705		return ""
706	}
707	if recv != "" {
708		// Format must match (*ssa.Function).RelString().
709		name = fmt.Sprintf("(%s).%s", recv, name)
710	}
711	index, ok := info.CallGraphIndex[name]
712	if !ok {
713		return ""
714	}
715	var buf bytes.Buffer
716	err := p.CallGraphHTML.Execute(&buf, struct{ Index int }{index})
717	if err != nil {
718		log.Print(err)
719	}
720	return buf.String()
721}
722
723func noteTitle(note string) string {
724	return strings.Title(strings.ToLower(note))
725}
726
727func startsWithUppercase(s string) bool {
728	r, _ := utf8.DecodeRuneInString(s)
729	return unicode.IsUpper(r)
730}
731
732var exampleOutputRx = regexp.MustCompile(`(?i)//[[:space:]]*(unordered )?output:`)
733
734// stripExampleSuffix strips lowercase braz in Foo_braz or Foo_Bar_braz from name
735// while keeping uppercase Braz in Foo_Braz.
736func stripExampleSuffix(name string) string {
737	if i := strings.LastIndex(name, "_"); i != -1 {
738		if i < len(name)-1 && !startsWithUppercase(name[i+1:]) {
739			name = name[:i]
740		}
741	}
742	return name
743}
744
745func splitExampleName(s string) (name, suffix string) {
746	i := strings.LastIndex(s, "_")
747	if 0 <= i && i < len(s)-1 && !startsWithUppercase(s[i+1:]) {
748		name = s[:i]
749		suffix = " (" + strings.Title(s[i+1:]) + ")"
750		return
751	}
752	name = s
753	return
754}
755
756// replaceLeadingIndentation replaces oldIndent at the beginning of each line
757// with newIndent. This is used for formatting examples. Raw strings that
758// span multiple lines are handled specially: oldIndent is not removed (since
759// go/printer will not add any indentation there), but newIndent is added
760// (since we may still want leading indentation).
761func replaceLeadingIndentation(body, oldIndent, newIndent string) string {
762	// Handle indent at the beginning of the first line. After this, we handle
763	// indentation only after a newline.
764	var buf bytes.Buffer
765	if strings.HasPrefix(body, oldIndent) {
766		buf.WriteString(newIndent)
767		body = body[len(oldIndent):]
768	}
769
770	// Use a state machine to keep track of whether we're in a string or
771	// rune literal while we process the rest of the code.
772	const (
773		codeState = iota
774		runeState
775		interpretedStringState
776		rawStringState
777	)
778	searchChars := []string{
779		"'\"`\n", // codeState
780		`\'`,     // runeState
781		`\"`,     // interpretedStringState
782		"`\n",    // rawStringState
783		// newlineState does not need to search
784	}
785	state := codeState
786	for {
787		i := strings.IndexAny(body, searchChars[state])
788		if i < 0 {
789			buf.WriteString(body)
790			break
791		}
792		c := body[i]
793		buf.WriteString(body[:i+1])
794		body = body[i+1:]
795		switch state {
796		case codeState:
797			switch c {
798			case '\'':
799				state = runeState
800			case '"':
801				state = interpretedStringState
802			case '`':
803				state = rawStringState
804			case '\n':
805				if strings.HasPrefix(body, oldIndent) {
806					buf.WriteString(newIndent)
807					body = body[len(oldIndent):]
808				}
809			}
810
811		case runeState:
812			switch c {
813			case '\\':
814				r, size := utf8.DecodeRuneInString(body)
815				buf.WriteRune(r)
816				body = body[size:]
817			case '\'':
818				state = codeState
819			}
820
821		case interpretedStringState:
822			switch c {
823			case '\\':
824				r, size := utf8.DecodeRuneInString(body)
825				buf.WriteRune(r)
826				body = body[size:]
827			case '"':
828				state = codeState
829			}
830
831		case rawStringState:
832			switch c {
833			case '`':
834				state = codeState
835			case '\n':
836				buf.WriteString(newIndent)
837			}
838		}
839	}
840	return buf.String()
841}
842
843// writeNode writes the AST node x to w.
844//
845// The provided fset must be non-nil. The pageInfo is optional. If
846// present, the pageInfo is used to add comments to struct fields to
847// say which version of Go introduced them.
848func (p *Presentation) writeNode(w io.Writer, pageInfo *PageInfo, fset *token.FileSet, x interface{}) {
849	// convert trailing tabs into spaces using a tconv filter
850	// to ensure a good outcome in most browsers (there may still
851	// be tabs in comments and strings, but converting those into
852	// the right number of spaces is much harder)
853	//
854	// TODO(gri) rethink printer flags - perhaps tconv can be eliminated
855	//           with an another printer mode (which is more efficiently
856	//           implemented in the printer than here with another layer)
857
858	var pkgName, structName string
859	var apiInfo pkgAPIVersions
860	if gd, ok := x.(*ast.GenDecl); ok && pageInfo != nil && pageInfo.PDoc != nil &&
861		p.Corpus != nil &&
862		gd.Tok == token.TYPE && len(gd.Specs) != 0 {
863		pkgName = pageInfo.PDoc.ImportPath
864		if ts, ok := gd.Specs[0].(*ast.TypeSpec); ok {
865			if _, ok := ts.Type.(*ast.StructType); ok {
866				structName = ts.Name.Name
867			}
868		}
869		apiInfo = p.Corpus.pkgAPIInfo[pkgName]
870	}
871
872	var out = w
873	var buf bytes.Buffer
874	if structName != "" {
875		out = &buf
876	}
877
878	mode := printer.TabIndent | printer.UseSpaces
879	err := (&printer.Config{Mode: mode, Tabwidth: p.TabWidth}).Fprint(&tconv{p: p, output: out}, fset, x)
880	if err != nil {
881		log.Print(err)
882	}
883
884	// Add comments to struct fields saying which Go version introducd them.
885	if structName != "" {
886		fieldSince := apiInfo.fieldSince[structName]
887		typeSince := apiInfo.typeSince[structName]
888		// Add/rewrite comments on struct fields to note which Go version added them.
889		var buf2 bytes.Buffer
890		buf2.Grow(buf.Len() + len(" // Added in Go 1.n")*10)
891		bs := bufio.NewScanner(&buf)
892		for bs.Scan() {
893			line := bs.Bytes()
894			field := firstIdent(line)
895			var since string
896			if field != "" {
897				since = fieldSince[field]
898				if since != "" && since == typeSince {
899					// Don't highlight field versions if they were the
900					// same as the struct itself.
901					since = ""
902				}
903			}
904			if since == "" {
905				buf2.Write(line)
906			} else {
907				if bytes.Contains(line, slashSlash) {
908					line = bytes.TrimRight(line, " \t.")
909					buf2.Write(line)
910					buf2.WriteString("; added in Go ")
911				} else {
912					buf2.Write(line)
913					buf2.WriteString(" // Go ")
914				}
915				buf2.WriteString(since)
916			}
917			buf2.WriteByte('\n')
918		}
919		w.Write(buf2.Bytes())
920	}
921}
922
923var slashSlash = []byte("//")
924
925// WriteNode writes x to w.
926// TODO(bgarcia) Is this method needed? It's just a wrapper for p.writeNode.
927func (p *Presentation) WriteNode(w io.Writer, fset *token.FileSet, x interface{}) {
928	p.writeNode(w, nil, fset, x)
929}
930
931// firstIdent returns the first identifier in x.
932// This actually parses "identifiers" that begin with numbers too, but we
933// never feed it such input, so it's fine.
934func firstIdent(x []byte) string {
935	x = bytes.TrimSpace(x)
936	i := bytes.IndexFunc(x, func(r rune) bool { return !unicode.IsLetter(r) && !unicode.IsNumber(r) })
937	if i == -1 {
938		return string(x)
939	}
940	return string(x[:i])
941}
942