You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

657 lines
15 KiB

  1. // Copyright 2013 The Go Authors. All rights reserved.
  2. //
  3. // Use of this source code is governed by a BSD-style
  4. // license that can be found in the LICENSE file or at
  5. // https://developers.google.com/open-source/licenses/bsd.
  6. package doc
  7. import (
  8. "bytes"
  9. "errors"
  10. "go/ast"
  11. "go/build"
  12. "go/doc"
  13. "go/format"
  14. "go/parser"
  15. "go/token"
  16. "regexp"
  17. "sort"
  18. "strings"
  19. "time"
  20. "unicode"
  21. "unicode/utf8"
  22. "github.com/golang/gddo/gosrc"
  23. )
  24. func startsWithUppercase(s string) bool {
  25. r, _ := utf8.DecodeRuneInString(s)
  26. return unicode.IsUpper(r)
  27. }
  28. var badSynopsisPrefixes = []string{
  29. "Autogenerated by Thrift Compiler",
  30. "Automatically generated ",
  31. "Auto-generated by ",
  32. "Copyright ",
  33. "COPYRIGHT ",
  34. `THE SOFTWARE IS PROVIDED "AS IS"`,
  35. "TODO: ",
  36. "vim:",
  37. }
  38. // synopsis extracts the first sentence from s. All runs of whitespace are
  39. // replaced by a single space.
  40. func synopsis(s string) string {
  41. parts := strings.SplitN(s, "\n\n", 2)
  42. s = parts[0]
  43. var buf []byte
  44. const (
  45. other = iota
  46. period
  47. space
  48. )
  49. last := space
  50. Loop:
  51. for i := 0; i < len(s); i++ {
  52. b := s[i]
  53. switch b {
  54. case ' ', '\t', '\r', '\n':
  55. switch last {
  56. case period:
  57. break Loop
  58. case other:
  59. buf = append(buf, ' ')
  60. last = space
  61. }
  62. case '.':
  63. last = period
  64. buf = append(buf, b)
  65. default:
  66. last = other
  67. buf = append(buf, b)
  68. }
  69. }
  70. // Ensure that synopsis fits an App Engine datastore text property.
  71. const m = 400
  72. if len(buf) > m {
  73. buf = buf[:m]
  74. if i := bytes.LastIndex(buf, []byte{' '}); i >= 0 {
  75. buf = buf[:i]
  76. }
  77. buf = append(buf, " ..."...)
  78. }
  79. s = string(buf)
  80. r, n := utf8.DecodeRuneInString(s)
  81. if n < 0 || unicode.IsPunct(r) || unicode.IsSymbol(r) {
  82. // ignore Markdown headings, editor settings, Go build constraints, and * in poorly formatted block comments.
  83. s = ""
  84. } else {
  85. for _, prefix := range badSynopsisPrefixes {
  86. if strings.HasPrefix(s, prefix) {
  87. s = ""
  88. break
  89. }
  90. }
  91. }
  92. return s
  93. }
  94. var referencesPats = []*regexp.Regexp{
  95. regexp.MustCompile(`"([-a-zA-Z0-9~+_./]+)"`), // quoted path
  96. regexp.MustCompile(`https://drone\.io/([-a-zA-Z0-9~+_./]+)/status\.png`),
  97. regexp.MustCompile(`\b(?:` + strings.Join([]string{
  98. `go\s+get\s+`,
  99. `goinstall\s+`,
  100. regexp.QuoteMeta("http://godoc.org/"),
  101. regexp.QuoteMeta("http://gopkgdoc.appspot.com/pkg/"),
  102. regexp.QuoteMeta("http://go.pkgdoc.org/"),
  103. regexp.QuoteMeta("http://gowalker.org/"),
  104. }, "|") + `)([-a-zA-Z0-9~+_./]+)`),
  105. }
  106. // addReferences adds packages referenced in plain text s.
  107. func addReferences(references map[string]bool, s []byte) {
  108. for _, pat := range referencesPats {
  109. for _, m := range pat.FindAllSubmatch(s, -1) {
  110. p := string(m[1])
  111. if gosrc.IsValidRemotePath(p) {
  112. references[p] = true
  113. }
  114. }
  115. }
  116. }
  117. type byFuncName []*doc.Func
  118. func (s byFuncName) Len() int { return len(s) }
  119. func (s byFuncName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
  120. func (s byFuncName) Less(i, j int) bool { return s[i].Name < s[j].Name }
  121. func removeAssociations(dpkg *doc.Package) {
  122. for _, t := range dpkg.Types {
  123. dpkg.Funcs = append(dpkg.Funcs, t.Funcs...)
  124. t.Funcs = nil
  125. }
  126. sort.Sort(byFuncName(dpkg.Funcs))
  127. }
  128. // builder holds the state used when building the documentation.
  129. type builder struct {
  130. srcs map[string]*source
  131. fset *token.FileSet
  132. examples []*doc.Example
  133. buf []byte // scratch space for printNode method.
  134. }
  135. type Value struct {
  136. Decl Code
  137. Pos Pos
  138. Doc string
  139. }
  140. func (b *builder) values(vdocs []*doc.Value) []*Value {
  141. var result []*Value
  142. for _, d := range vdocs {
  143. result = append(result, &Value{
  144. Decl: b.printDecl(d.Decl),
  145. Pos: b.position(d.Decl),
  146. Doc: d.Doc,
  147. })
  148. }
  149. return result
  150. }
  151. type Note struct {
  152. Pos Pos
  153. UID string
  154. Body string
  155. }
  156. type posNode token.Pos
  157. func (p posNode) Pos() token.Pos { return token.Pos(p) }
  158. func (p posNode) End() token.Pos { return token.Pos(p) }
  159. func (b *builder) notes(gnotes map[string][]*doc.Note) map[string][]*Note {
  160. if len(gnotes) == 0 {
  161. return nil
  162. }
  163. notes := make(map[string][]*Note)
  164. for tag, gvalues := range gnotes {
  165. values := make([]*Note, len(gvalues))
  166. for i := range gvalues {
  167. values[i] = &Note{
  168. Pos: b.position(posNode(gvalues[i].Pos)),
  169. UID: gvalues[i].UID,
  170. Body: strings.TrimSpace(gvalues[i].Body),
  171. }
  172. }
  173. notes[tag] = values
  174. }
  175. return notes
  176. }
  177. type Example struct {
  178. Name string
  179. Doc string
  180. Code Code
  181. Play string
  182. Output string
  183. }
  184. var exampleOutputRx = regexp.MustCompile(`(?i)//[[:space:]]*output:`)
  185. func (b *builder) getExamples(name string) []*Example {
  186. var docs []*Example
  187. for _, e := range b.examples {
  188. if !strings.HasPrefix(e.Name, name) {
  189. continue
  190. }
  191. n := e.Name[len(name):]
  192. if n != "" {
  193. if i := strings.LastIndex(n, "_"); i != 0 {
  194. continue
  195. }
  196. n = n[1:]
  197. if startsWithUppercase(n) {
  198. continue
  199. }
  200. n = strings.Title(n)
  201. }
  202. code, output := b.printExample(e)
  203. play := ""
  204. if e.Play != nil {
  205. b.buf = b.buf[:0]
  206. if err := format.Node(sliceWriter{&b.buf}, b.fset, e.Play); err != nil {
  207. play = err.Error()
  208. } else {
  209. play = string(b.buf)
  210. }
  211. }
  212. docs = append(docs, &Example{
  213. Name: n,
  214. Doc: e.Doc,
  215. Code: code,
  216. Output: output,
  217. Play: play})
  218. }
  219. return docs
  220. }
  221. type Func struct {
  222. Decl Code
  223. Pos Pos
  224. Doc string
  225. Name string
  226. Recv string // Actual receiver "T" or "*T".
  227. Orig string // Original receiver "T" or "*T". This can be different from Recv due to embedding.
  228. Examples []*Example
  229. }
  230. func (b *builder) funcs(fdocs []*doc.Func) []*Func {
  231. var result []*Func
  232. for _, d := range fdocs {
  233. var exampleName string
  234. switch {
  235. case d.Recv == "":
  236. exampleName = d.Name
  237. case d.Recv[0] == '*':
  238. exampleName = d.Recv[1:] + "_" + d.Name
  239. default:
  240. exampleName = d.Recv + "_" + d.Name
  241. }
  242. result = append(result, &Func{
  243. Decl: b.printDecl(d.Decl),
  244. Pos: b.position(d.Decl),
  245. Doc: d.Doc,
  246. Name: d.Name,
  247. Recv: d.Recv,
  248. Orig: d.Orig,
  249. Examples: b.getExamples(exampleName),
  250. })
  251. }
  252. return result
  253. }
  254. type Type struct {
  255. Doc string
  256. Name string
  257. Decl Code
  258. Pos Pos
  259. Consts []*Value
  260. Vars []*Value
  261. Funcs []*Func
  262. Methods []*Func
  263. Examples []*Example
  264. }
  265. func (b *builder) types(tdocs []*doc.Type) []*Type {
  266. var result []*Type
  267. for _, d := range tdocs {
  268. result = append(result, &Type{
  269. Doc: d.Doc,
  270. Name: d.Name,
  271. Decl: b.printDecl(d.Decl),
  272. Pos: b.position(d.Decl),
  273. Consts: b.values(d.Consts),
  274. Vars: b.values(d.Vars),
  275. Funcs: b.funcs(d.Funcs),
  276. Methods: b.funcs(d.Methods),
  277. Examples: b.getExamples(d.Name),
  278. })
  279. }
  280. return result
  281. }
  282. var packageNamePats = []*regexp.Regexp{
  283. // Last element with .suffix removed.
  284. regexp.MustCompile(`/([^-./]+)[-.](?:git|svn|hg|bzr|v\d+)$`),
  285. // Last element with "go" prefix or suffix removed.
  286. regexp.MustCompile(`/([^-./]+)[-.]go$`),
  287. regexp.MustCompile(`/go[-.]([^-./]+)$`),
  288. // Special cases for popular repos.
  289. regexp.MustCompile(`^code\.google\.com/p/google-api-go-client/([^/]+)/v[^/]+$`),
  290. regexp.MustCompile(`^code\.google\.com/p/biogo\.([^/]+)$`),
  291. // It's also common for the last element of the path to contain an
  292. // extra "go" prefix, but not always. TODO: examine unresolved ids to
  293. // detect when trimming the "go" prefix is appropriate.
  294. // Last component of path.
  295. regexp.MustCompile(`([^/]+)$`),
  296. }
  297. func simpleImporter(imports map[string]*ast.Object, path string) (*ast.Object, error) {
  298. pkg := imports[path]
  299. if pkg != nil {
  300. return pkg, nil
  301. }
  302. // Guess the package name without importing it.
  303. for _, pat := range packageNamePats {
  304. m := pat.FindStringSubmatch(path)
  305. if m != nil {
  306. pkg = ast.NewObj(ast.Pkg, m[1])
  307. pkg.Data = ast.NewScope(nil)
  308. imports[path] = pkg
  309. return pkg, nil
  310. }
  311. }
  312. return nil, errors.New("package not found")
  313. }
  314. type File struct {
  315. Name string
  316. URL string
  317. }
  318. type Pos struct {
  319. Line int32 // 0 if not valid.
  320. N uint16 // number of lines - 1
  321. File int16 // index in Package.Files
  322. }
  323. type source struct {
  324. name string
  325. browseURL string
  326. data []byte
  327. index int
  328. }
  329. // PackageVersion is modified when previously stored packages are invalid.
  330. const PackageVersion = "8"
  331. type Package struct {
  332. // The import path for this package.
  333. ImportPath string
  334. // Import path prefix for all packages in the project.
  335. ProjectRoot string
  336. // Name of the project.
  337. ProjectName string
  338. // Project home page.
  339. ProjectURL string
  340. // Errors found when fetching or parsing this package.
  341. Errors []string
  342. // Packages referenced in README files.
  343. References []string
  344. // Version control system: git, hg, bzr, ...
  345. VCS string
  346. // Version control: active or suppressed.
  347. Status gosrc.DirectoryStatus
  348. // Whether the package is a fork of another one.
  349. Fork bool
  350. // How many stars (for a GitHub project) or followers (for a BitBucket
  351. // project) the repository of this package has.
  352. Stars int
  353. // The time this object was created.
  354. Updated time.Time
  355. // Cache validation tag. This tag is not necessarily an HTTP entity tag.
  356. // The tag is "" if there is no meaningful cache validation for the VCS.
  357. Etag string
  358. // Subdirectories, possibly containing Go code.
  359. Subdirectories []string
  360. // Package name or "" if no package for this import path. The proceeding
  361. // fields are set even if a package is not found for the import path.
  362. Name string
  363. // Synopsis and full documentation for the package.
  364. Synopsis string
  365. Doc string
  366. // Format this package as a command.
  367. IsCmd bool
  368. // True if package documentation is incomplete.
  369. Truncated bool
  370. // Environment
  371. GOOS, GOARCH string
  372. // Top-level declarations.
  373. Consts []*Value
  374. Funcs []*Func
  375. Types []*Type
  376. Vars []*Value
  377. // Package examples
  378. Examples []*Example
  379. Notes map[string][]*Note
  380. // Source.
  381. LineFmt string
  382. BrowseURL string
  383. Files []*File
  384. TestFiles []*File
  385. // Source size in bytes.
  386. SourceSize int
  387. TestSourceSize int
  388. // Imports
  389. Imports []string
  390. TestImports []string
  391. XTestImports []string
  392. }
  393. var goEnvs = []struct{ GOOS, GOARCH string }{
  394. {"linux", "amd64"},
  395. {"darwin", "amd64"},
  396. {"windows", "amd64"},
  397. {"linux", "js"},
  398. }
  399. // SetDefaultGOOS sets given GOOS value as default one to use when building
  400. // package documents. SetDefaultGOOS has no effect on some windows-only
  401. // packages.
  402. func SetDefaultGOOS(goos string) {
  403. if goos == "" {
  404. return
  405. }
  406. var i int
  407. for ; i < len(goEnvs); i++ {
  408. if goEnvs[i].GOOS == goos {
  409. break
  410. }
  411. }
  412. switch i {
  413. case 0:
  414. return
  415. case len(goEnvs):
  416. env := goEnvs[0]
  417. env.GOOS = goos
  418. goEnvs = append(goEnvs, env)
  419. }
  420. goEnvs[0], goEnvs[i] = goEnvs[i], goEnvs[0]
  421. }
  422. var windowsOnlyPackages = map[string]bool{
  423. "internal/syscall/windows": true,
  424. "internal/syscall/windows/registry": true,
  425. "golang.org/x/exp/shiny/driver/internal/win32": true,
  426. "golang.org/x/exp/shiny/driver/windriver": true,
  427. "golang.org/x/sys/windows": true,
  428. "golang.org/x/sys/windows/registry": true,
  429. }
  430. func newPackage(dir *gosrc.Directory) (*Package, error) {
  431. pkg := &Package{
  432. Updated: time.Now().UTC(),
  433. LineFmt: dir.LineFmt,
  434. ImportPath: dir.ImportPath,
  435. ProjectRoot: dir.ProjectRoot,
  436. ProjectName: dir.ProjectName,
  437. ProjectURL: dir.ProjectURL,
  438. BrowseURL: dir.BrowseURL,
  439. Etag: PackageVersion + "-" + dir.Etag,
  440. VCS: dir.VCS,
  441. Status: dir.Status,
  442. Subdirectories: dir.Subdirectories,
  443. Fork: dir.Fork,
  444. Stars: dir.Stars,
  445. }
  446. var b builder
  447. b.srcs = make(map[string]*source)
  448. references := make(map[string]bool)
  449. for _, file := range dir.Files {
  450. if strings.HasSuffix(file.Name, ".go") {
  451. gosrc.OverwriteLineComments(file.Data)
  452. b.srcs[file.Name] = &source{name: file.Name, browseURL: file.BrowseURL, data: file.Data}
  453. } else {
  454. addReferences(references, file.Data)
  455. }
  456. }
  457. for r := range references {
  458. pkg.References = append(pkg.References, r)
  459. }
  460. if len(b.srcs) == 0 {
  461. return pkg, nil
  462. }
  463. b.fset = token.NewFileSet()
  464. // Find the package and associated files.
  465. ctxt := build.Context{
  466. GOOS: "linux",
  467. GOARCH: "amd64",
  468. CgoEnabled: true,
  469. ReleaseTags: build.Default.ReleaseTags,
  470. BuildTags: build.Default.BuildTags,
  471. Compiler: "gc",
  472. }
  473. var err error
  474. var bpkg *build.Package
  475. for _, env := range goEnvs {
  476. // Some packages should be always displayed as GOOS=windows (see issue #16509 for details).
  477. // TODO: remove this once issue #16509 is resolved.
  478. if windowsOnlyPackages[dir.ImportPath] && env.GOOS != "windows" {
  479. continue
  480. }
  481. ctxt.GOOS = env.GOOS
  482. ctxt.GOARCH = env.GOARCH
  483. bpkg, err = dir.Import(&ctxt, build.ImportComment)
  484. if _, ok := err.(*build.NoGoError); !ok {
  485. break
  486. }
  487. }
  488. if err != nil {
  489. if _, ok := err.(*build.NoGoError); !ok {
  490. pkg.Errors = append(pkg.Errors, err.Error())
  491. }
  492. return pkg, nil
  493. }
  494. if bpkg.ImportComment != "" && bpkg.ImportComment != dir.ImportPath {
  495. return nil, gosrc.NotFoundError{
  496. Message: "not at canonical import path",
  497. Redirect: bpkg.ImportComment,
  498. }
  499. }
  500. // Parse the Go files
  501. files := make(map[string]*ast.File)
  502. names := append(bpkg.GoFiles, bpkg.CgoFiles...)
  503. sort.Strings(names)
  504. pkg.Files = make([]*File, len(names))
  505. for i, name := range names {
  506. file, err := parser.ParseFile(b.fset, name, b.srcs[name].data, parser.ParseComments)
  507. if err != nil {
  508. pkg.Errors = append(pkg.Errors, err.Error())
  509. } else {
  510. files[name] = file
  511. }
  512. src := b.srcs[name]
  513. src.index = i
  514. pkg.Files[i] = &File{Name: name, URL: src.browseURL}
  515. pkg.SourceSize += len(src.data)
  516. }
  517. apkg, _ := ast.NewPackage(b.fset, files, simpleImporter, nil)
  518. // Find examples in the test files.
  519. names = append(bpkg.TestGoFiles, bpkg.XTestGoFiles...)
  520. sort.Strings(names)
  521. pkg.TestFiles = make([]*File, len(names))
  522. for i, name := range names {
  523. file, err := parser.ParseFile(b.fset, name, b.srcs[name].data, parser.ParseComments)
  524. if err != nil {
  525. pkg.Errors = append(pkg.Errors, err.Error())
  526. } else {
  527. b.examples = append(b.examples, doc.Examples(file)...)
  528. }
  529. pkg.TestFiles[i] = &File{Name: name, URL: b.srcs[name].browseURL}
  530. pkg.TestSourceSize += len(b.srcs[name].data)
  531. }
  532. b.vetPackage(pkg, apkg)
  533. mode := doc.Mode(0)
  534. if pkg.ImportPath == "builtin" {
  535. mode |= doc.AllDecls
  536. }
  537. dpkg := doc.New(apkg, pkg.ImportPath, mode)
  538. if pkg.ImportPath == "builtin" {
  539. removeAssociations(dpkg)
  540. }
  541. pkg.Name = dpkg.Name
  542. pkg.Doc = strings.TrimRight(dpkg.Doc, " \t\n\r")
  543. pkg.Synopsis = synopsis(pkg.Doc)
  544. pkg.Examples = b.getExamples("")
  545. pkg.IsCmd = bpkg.IsCommand()
  546. pkg.GOOS = ctxt.GOOS
  547. pkg.GOARCH = ctxt.GOARCH
  548. pkg.Consts = b.values(dpkg.Consts)
  549. pkg.Funcs = b.funcs(dpkg.Funcs)
  550. pkg.Types = b.types(dpkg.Types)
  551. pkg.Vars = b.values(dpkg.Vars)
  552. pkg.Notes = b.notes(dpkg.Notes)
  553. pkg.Imports = bpkg.Imports
  554. pkg.TestImports = bpkg.TestImports
  555. pkg.XTestImports = bpkg.XTestImports
  556. return pkg, nil
  557. }