Skip to content

Instantly share code, notes, and snippets.

@dmitshur
Last active May 22, 2022 16:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dmitshur/7e055acb298b42549f9a7d53784545c9 to your computer and use it in GitHub Desktop.
Save dmitshur/7e055acb298b42549f9a7d53784545c9 to your computer and use it in GitHub Desktop.
A prototype of adding support for parsing module comments to cmd/go. It's related to a proposal that is in progress.
From 17fa3a2c59d8cb9710df96a8d3aebab779a202ae Mon Sep 17 00:00:00 2001
From: Dmitri Shuralyov <dmitshur@golang.org>
Date: Mon, 4 Mar 2019 09:42:00 -0500
Subject: [PATCH] cmd/go: add support for parsing module comments
DO NOT SUBMIT, DO NOT REVIEW: This is a prototype of a proposal
that is in progress. It has not been accepted.
Change-Id: I9f2aa9acc039e9f1e10e4f0c8a25cb14e4c3fc20
---
src/cmd/go/internal/list/list.go | 3 +-
src/cmd/go/internal/load/pkg.go | 2 +-
src/cmd/go/internal/moddoc/synopsis.go | 37 ++++++++++
src/cmd/go/internal/moddoc/synopsis_test.go | 82 +++++++++++++++++++++
src/cmd/go/internal/modinfo/info.go | 1 +
src/cmd/go/internal/modload/build.go | 3 +
src/cmd/go/internal/modload/load.go | 20 +++--
7 files changed, 141 insertions(+), 7 deletions(-)
create mode 100644 src/cmd/go/internal/moddoc/synopsis.go
create mode 100644 src/cmd/go/internal/moddoc/synopsis_test.go
diff --git a/src/cmd/go/internal/list/list.go b/src/cmd/go/internal/list/list.go
index 4a6633d9a1..14601b9324 100644
--- a/src/cmd/go/internal/list/list.go
+++ b/src/cmd/go/internal/list/list.go
@@ -51,7 +51,7 @@ to -f '{{.ImportPath}}'. The struct being passed to the template is:
ImportPath string // import path of package in dir
ImportComment string // path in import comment on package statement
Name string // package name
- Doc string // package documentation string
+ Doc string // package documentation synopsis
Target string // install path
Shlib string // the shared library that contains this package (only set when -linkshared)
Goroot bool // is this package in the Go root?
@@ -204,6 +204,7 @@ applied to a Go struct, but now a Module struct:
type Module struct {
Path string // module path
Version string // module version
+ Doc string // module documentation synopsis
Versions []string // available module versions (with -versions)
Replace *Module // replaced by this module
Time *time.Time // time version was created
diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go
index e6c893c257..47c64a6927 100644
--- a/src/cmd/go/internal/load/pkg.go
+++ b/src/cmd/go/internal/load/pkg.go
@@ -58,7 +58,7 @@ type PackagePublic struct {
ImportPath string `json:",omitempty"` // import path of package in dir
ImportComment string `json:",omitempty"` // path in import comment on package statement
Name string `json:",omitempty"` // package name
- Doc string `json:",omitempty"` // package documentation string
+ Doc string `json:",omitempty"` // package documentation synopsis
Target string `json:",omitempty"` // installed target for this package (may be executable)
Shlib string `json:",omitempty"` // the shared library that contains this package (only set when -linkshared)
Root string `json:",omitempty"` // Go root or Go path dir containing this package
diff --git a/src/cmd/go/internal/moddoc/synopsis.go b/src/cmd/go/internal/moddoc/synopsis.go
new file mode 100644
index 0000000000..e4d7555d96
--- /dev/null
+++ b/src/cmd/go/internal/moddoc/synopsis.go
@@ -0,0 +1,37 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package moddoc extracts module documentation from a parsed go.mod file.
+package moddoc
+
+import (
+ "cmd/go/internal/modfile"
+ "go/ast"
+ "go/doc"
+)
+
+// TODO: This is a package that contains a single simple function,
+// and it's used only in the modload package. Maybe it should
+// be moved into modload as moddoc.go and moddoc_test.go.
+//
+// This functionality could also live inside the modfile package,
+// but that would require it to import "go/ast" and "go/doc" packages.
+// I opted against that for now, since this is higher-order functionality.
+
+// Synopsis ...
+func Synopsis(f *modfile.File) string {
+ // Check that the module statement is present.
+ if f.Module == nil {
+ return ""
+ }
+
+ // Compute the module comment.
+ var mc ast.CommentGroup
+ for _, c := range f.Module.Syntax.Comments.Before {
+ mc.List = append(mc.List, &ast.Comment{Text: c.Token})
+ }
+
+ // Return its synopsis.
+ return doc.Synopsis(mc.Text())
+}
diff --git a/src/cmd/go/internal/moddoc/synopsis_test.go b/src/cmd/go/internal/moddoc/synopsis_test.go
new file mode 100644
index 0000000000..48219a912c
--- /dev/null
+++ b/src/cmd/go/internal/moddoc/synopsis_test.go
@@ -0,0 +1,82 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package moddoc_test
+
+import (
+ "cmd/go/internal/moddoc"
+ "cmd/go/internal/modfile"
+ "testing"
+)
+
+func TestSynopsis(t *testing.T) {
+ for _, test := range []struct {
+ in string
+ want string
+ }{
+ {
+ in: `// Module c is a documented module.
+module a/b/c
+`,
+ want: "Module c is a documented module.",
+ },
+ {
+ in: `// Module c is a module
+// that is documented
+// across multiple lines.
+module a/b/c
+`,
+ want: "Module c is a module that is documented across multiple lines.",
+ },
+ {
+ in: `// Module c is a documented module. It happens
+// to have a more detailed description. This description is not
+// a part of the synposis.
+module a/b/c
+`,
+ want: "Module c is a documented module.",
+ },
+ {
+ in: `// This is not a module comment.
+
+module foo/bar/m
+`,
+ want: "",
+ },
+ {
+ in: `// This is not a module comment.
+
+// But this is a module comment.
+module foo/bar/m
+`,
+ want: "But this is a module comment.",
+ },
+ {
+ in: "",
+ want: "",
+ },
+ {
+ in: `//Module c is a documented module, without a leading space.
+module a/b/c
+`,
+ want: "Module c is a documented module, without a leading space.",
+ },
+ {
+ in: `//Module c is a documented module,
+//without leading spaces on any of its lines.
+module a/b/c
+`,
+ want: "Module c is a documented module, without leading spaces on any of its lines.",
+ },
+ } {
+ f, err := modfile.ParseLax("", []byte(test.in), nil)
+ if err != nil {
+ t.Fatal("modfile.ParseLax:", err)
+ }
+ got := moddoc.Synopsis(f)
+ if got != test.want {
+ t.Errorf("got Synopsis = %q; want %q\n", got, test.want)
+ }
+ }
+}
diff --git a/src/cmd/go/internal/modinfo/info.go b/src/cmd/go/internal/modinfo/info.go
index 07248d1a61..edc61b29cc 100644
--- a/src/cmd/go/internal/modinfo/info.go
+++ b/src/cmd/go/internal/modinfo/info.go
@@ -12,6 +12,7 @@ import "time"
type ModulePublic struct {
Path string `json:",omitempty"` // module path
Version string `json:",omitempty"` // module version
+ Doc string `json:",omitempty"` // module documentation synopsis
Versions []string `json:",omitempty"` // available module versions
Replace *ModulePublic `json:",omitempty"` // replaced by this module
Time *time.Time `json:",omitempty"` // time version was created
diff --git a/src/cmd/go/internal/modload/build.go b/src/cmd/go/internal/modload/build.go
index 4d4e512ef5..366a80ce23 100644
--- a/src/cmd/go/internal/modload/build.go
+++ b/src/cmd/go/internal/modload/build.go
@@ -8,6 +8,7 @@ import (
"bytes"
"cmd/go/internal/base"
"cmd/go/internal/cfg"
+ "cmd/go/internal/moddoc"
"cmd/go/internal/modfetch"
"cmd/go/internal/modinfo"
"cmd/go/internal/module"
@@ -103,6 +104,7 @@ func moduleInfo(m module.Version, fromBuildList bool) *modinfo.ModulePublic {
if HasModRoot() {
info.Dir = ModRoot()
info.GoMod = filepath.Join(info.Dir, "go.mod")
+ info.Doc = moddoc.Synopsis(modFile)
if modFile.Go != nil {
info.GoVersion = modFile.Go.Version
}
@@ -116,6 +118,7 @@ func moduleInfo(m module.Version, fromBuildList bool) *modinfo.ModulePublic {
Indirect: fromBuildList && loaded != nil && !loaded.direct[m.Path],
}
if loaded != nil {
+ info.Doc = loaded.doc[m.Path]
info.GoVersion = loaded.goVersion[m.Path]
}
diff --git a/src/cmd/go/internal/modload/load.go b/src/cmd/go/internal/modload/load.go
index 6d6c037af2..40d9af23e2 100644
--- a/src/cmd/go/internal/modload/load.go
+++ b/src/cmd/go/internal/modload/load.go
@@ -20,6 +20,7 @@ import (
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/imports"
+ "cmd/go/internal/moddoc"
"cmd/go/internal/modfetch"
"cmd/go/internal/modfile"
"cmd/go/internal/module"
@@ -447,6 +448,7 @@ type loader struct {
// computed at end of iterations
direct map[string]bool // imported directly by main module
+ doc map[string]string // documentation synopsis of each module
goVersion map[string]string // go version recorded in each module
}
@@ -552,9 +554,12 @@ func (ld *loader) load(roots func() []string) {
}
}
- // Add Go versions, computed during walk.
+ // Add documentation synopses and Go versions, computed during walk.
+ ld.doc = make(map[string]string)
ld.goVersion = make(map[string]string)
for _, m := range buildList {
+ d, _ := reqs.(*mvsReqs).docs.Load(m)
+ ld.doc[m.Path], _ = d.(string)
v, _ := reqs.(*mvsReqs).versions.Load(m)
ld.goVersion[m.Path], _ = v.(string)
}
@@ -832,6 +837,7 @@ func Replacement(mod module.Version) module.Version {
type mvsReqs struct {
buildList []module.Version
cache par.Cache
+ docs sync.Map
versions sync.Map
}
@@ -918,11 +924,13 @@ func (r *mvsReqs) modFileToList(f *modfile.File) []module.Version {
func (r *mvsReqs) required(mod module.Version) ([]module.Version, error) {
if mod == Target {
- if modFile != nil && modFile.Go != nil {
- r.versions.LoadOrStore(mod, modFile.Go.Version)
+ if modFile != nil {
+ r.docs.LoadOrStore(mod, moddoc.Synopsis(modFile))
+ if modFile.Go != nil {
+ r.versions.LoadOrStore(mod, modFile.Go.Version)
+ }
}
- var list []module.Version
- return append(list, r.buildList[1:]...), nil
+ return append([]module.Version(nil), r.buildList[1:]...), nil
}
if cfg.BuildMod == "vendor" {
@@ -951,6 +959,7 @@ func (r *mvsReqs) required(mod module.Version) ([]module.Version, error) {
base.Errorf("go: parsing %s: %v", base.ShortPath(gomod), err)
return nil, ErrRequire
}
+ r.docs.LoadOrStore(mod, moddoc.Synopsis(f))
if f.Go != nil {
r.versions.LoadOrStore(mod, f.Go.Version)
}
@@ -987,6 +996,7 @@ func (r *mvsReqs) required(mod module.Version) ([]module.Version, error) {
base.Errorf("go: %s@%s: parsing go.mod: unexpected module path %q", mod.Path, mod.Version, mpath)
return nil, ErrRequire
}
+ r.docs.LoadOrStore(mod, moddoc.Synopsis(f))
if f.Go != nil {
r.versions.LoadOrStore(mod, f.Go.Version)
}
--
2.19.1.1215.g8438c0b245-goog
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment