diff --git a/cmd/guru/callees.go b/cmd/guru/callees.go index 678450bf..6c6f70e1 100644 --- a/cmd/guru/callees.go +++ b/cmd/guru/callees.go @@ -32,7 +32,6 @@ func callees(q *Query) error { if err != nil { return err } - q.Fset = lprog.Fset qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos if err != nil { @@ -69,10 +68,10 @@ func callees(q *Query) error { return fmt.Errorf("this is a call to the built-in '%s' operator", obj.Name()) case *types.Func: // This is a static function call - q.result = &calleesTypesResult{ + q.Output(lprog.Fset, &calleesTypesResult{ site: e, callee: obj, - } + }) return nil } case *ast.SelectorExpr: @@ -83,10 +82,10 @@ func callees(q *Query) error { // or to top level function. callee := qpos.info.Uses[funexpr.Sel] if obj, ok := callee.(*types.Func); ok { - q.result = &calleesTypesResult{ + q.Output(lprog.Fset, &calleesTypesResult{ site: e, callee: obj, - } + }) return nil } } else if sel.Kind() == types.MethodVal { @@ -98,10 +97,10 @@ func callees(q *Query) error { recvtype := method.Type().(*types.Signature).Recv().Type() if !types.IsInterface(recvtype) { // static method call - q.result = &calleesTypesResult{ + q.Output(lprog.Fset, &calleesTypesResult{ site: e, callee: method, - } + }) return nil } } @@ -139,10 +138,10 @@ func callees(q *Query) error { return err } - q.result = &calleesSSAResult{ + q.Output(lprog.Fset, &calleesSSAResult{ site: site, funcs: funcs, - } + }) return nil } @@ -204,7 +203,7 @@ type calleesTypesResult struct { callee *types.Func } -func (r *calleesSSAResult) display(printf printfFunc) { +func (r *calleesSSAResult) PrintPlain(printf printfFunc) { if len(r.funcs) == 0 { // dynamic call on a provably nil func/interface printf(r.site, "%s on nil value", r.site.Common().Description()) @@ -216,37 +215,37 @@ func (r *calleesSSAResult) display(printf printfFunc) { } } -func (r *calleesSSAResult) toSerial(res *serial.Result, fset *token.FileSet) { +func (r *calleesSSAResult) JSON(fset *token.FileSet) []byte { j := &serial.Callees{ Pos: fset.Position(r.site.Pos()).String(), Desc: r.site.Common().Description(), } for _, callee := range r.funcs { - j.Callees = append(j.Callees, &serial.CalleesItem{ + j.Callees = append(j.Callees, &serial.Callee{ Name: callee.String(), Pos: fset.Position(callee.Pos()).String(), }) } - res.Callees = j + return toJSON(j) } -func (r *calleesTypesResult) display(printf printfFunc) { +func (r *calleesTypesResult) PrintPlain(printf printfFunc) { printf(r.site, "this static function call dispatches to:") printf(r.callee, "\t%s", r.callee.FullName()) } -func (r *calleesTypesResult) toSerial(res *serial.Result, fset *token.FileSet) { +func (r *calleesTypesResult) JSON(fset *token.FileSet) []byte { j := &serial.Callees{ Pos: fset.Position(r.site.Pos()).String(), Desc: "static function call", } - j.Callees = []*serial.CalleesItem{ - &serial.CalleesItem{ + j.Callees = []*serial.Callee{ + &serial.Callee{ Name: r.callee.FullName(), Pos: fset.Position(r.callee.Pos()).String(), }, } - res.Callees = j + return toJSON(j) } // NB: byFuncPos is not deterministic across packages since it depends on load order. diff --git a/cmd/guru/callers.go b/cmd/guru/callers.go index 0dd8c144..c02ffa6e 100644 --- a/cmd/guru/callers.go +++ b/cmd/guru/callers.go @@ -31,7 +31,6 @@ func callers(q *Query) error { if err != nil { return err } - q.Fset = lprog.Fset qpos, err := parseQueryPos(lprog, q.Pos, false) if err != nil { @@ -77,11 +76,11 @@ func callers(q *Query) error { // TODO(adonovan): sort + dedup calls to ensure test determinism. - q.result = &callersResult{ + q.Output(lprog.Fset, &callersResult{ target: target, callgraph: cg, edges: edges, - } + }) return nil } @@ -167,7 +166,7 @@ type callersResult struct { edges []*callgraph.Edge } -func (r *callersResult) display(printf printfFunc) { +func (r *callersResult) PrintPlain(printf printfFunc) { root := r.callgraph.Root if r.edges == nil { printf(r.target, "%s is not reachable in this program.", r.target) @@ -183,7 +182,7 @@ func (r *callersResult) display(printf printfFunc) { } } -func (r *callersResult) toSerial(res *serial.Result, fset *token.FileSet) { +func (r *callersResult) JSON(fset *token.FileSet) []byte { var callers []serial.Caller for _, edge := range r.edges { callers = append(callers, serial.Caller{ @@ -192,5 +191,5 @@ func (r *callersResult) toSerial(res *serial.Result, fset *token.FileSet) { Desc: edge.Description(), }) } - res.Callers = callers + return toJSON(callers) } diff --git a/cmd/guru/callstack.go b/cmd/guru/callstack.go index b2160b9f..a1870723 100644 --- a/cmd/guru/callstack.go +++ b/cmd/guru/callstack.go @@ -96,12 +96,11 @@ func callstack(q *Query) error { } } - q.Fset = fset - q.result = &callstackResult{ + q.Output(fset, &callstackResult{ qpos: qpos, target: target, callpath: callpath, - } + }) return nil } @@ -111,7 +110,7 @@ type callstackResult struct { callpath []*callgraph.Edge } -func (r *callstackResult) display(printf printfFunc) { +func (r *callstackResult) PrintPlain(printf printfFunc) { if r.callpath != nil { printf(r.qpos, "Found a call path from root to %s", r.target) printf(r.target, "%s", r.target) @@ -124,7 +123,7 @@ func (r *callstackResult) display(printf printfFunc) { } } -func (r *callstackResult) toSerial(res *serial.Result, fset *token.FileSet) { +func (r *callstackResult) JSON(fset *token.FileSet) []byte { var callers []serial.Caller for i := len(r.callpath) - 1; i >= 0; i-- { // (innermost first) edge := r.callpath[i] @@ -134,9 +133,9 @@ func (r *callstackResult) toSerial(res *serial.Result, fset *token.FileSet) { Desc: edge.Description(), }) } - res.Callstack = &serial.CallStack{ + return toJSON(&serial.CallStack{ Pos: fset.Position(r.target.Pos()).String(), Target: r.target.String(), Callers: callers, - } + }) } diff --git a/cmd/guru/definition.go b/cmd/guru/definition.go index f290b5b4..828316da 100644 --- a/cmd/guru/definition.go +++ b/cmd/guru/definition.go @@ -31,11 +31,10 @@ func definition(q *Query) error { } if obj := id.Obj; obj != nil && obj.Pos().IsValid() { - q.Fset = qpos.fset - q.result = &definitionResult{ + q.Output(qpos.fset, &definitionResult{ pos: obj.Pos(), descr: fmt.Sprintf("%s %s", obj.Kind, obj.Name), - } + }) return nil // success } } @@ -53,7 +52,6 @@ func definition(q *Query) error { if err != nil { return err } - q.Fset = lprog.Fset qpos, err := parseQueryPos(lprog, q.Pos, false) if err != nil { @@ -77,10 +75,10 @@ func definition(q *Query) error { return fmt.Errorf("%s is built in", obj.Name()) } - q.result = &definitionResult{ + q.Output(lprog.Fset, &definitionResult{ pos: obj.Pos(), descr: qpos.objectString(obj), - } + }) return nil } @@ -89,14 +87,13 @@ type definitionResult struct { descr string // description of object it denotes } -func (r *definitionResult) display(printf printfFunc) { +func (r *definitionResult) PrintPlain(printf printfFunc) { printf(r.pos, "defined here as %s", r.descr) } -func (r *definitionResult) toSerial(res *serial.Result, fset *token.FileSet) { - definition := &serial.Definition{ +func (r *definitionResult) JSON(fset *token.FileSet) []byte { + return toJSON(&serial.Definition{ Desc: r.descr, ObjPos: fset.Position(r.pos).String(), - } - res.Definition = definition + }) } diff --git a/cmd/guru/describe.go b/cmd/guru/describe.go index a1111efb..451f9ef6 100644 --- a/cmd/guru/describe.go +++ b/cmd/guru/describe.go @@ -40,7 +40,6 @@ func describe(q *Query) error { if err != nil { return err } - q.Fset = lprog.Fset qpos, err := parseQueryPos(lprog, q.Pos, true) // (need exact pos) if err != nil { @@ -52,43 +51,48 @@ func describe(q *Query) error { astutil.NodeDescription(qpos.path[0]), pathToString(qpos.path)) } + var qr QueryResult path, action := findInterestingNode(qpos.info, qpos.path) switch action { case actionExpr: - q.result, err = describeValue(qpos, path) + qr, err = describeValue(qpos, path) case actionType: - q.result, err = describeType(qpos, path) + qr, err = describeType(qpos, path) case actionPackage: - q.result, err = describePackage(qpos, path) + qr, err = describePackage(qpos, path) case actionStmt: - q.result, err = describeStmt(qpos, path) + qr, err = describeStmt(qpos, path) case actionUnknown: - q.result = &describeUnknownResult{path[0]} + qr = &describeUnknownResult{path[0]} default: panic(action) // unreachable } - return err + if err != nil { + return err + } + q.Output(lprog.Fset, qr) + return nil } type describeUnknownResult struct { node ast.Node } -func (r *describeUnknownResult) display(printf printfFunc) { +func (r *describeUnknownResult) PrintPlain(printf printfFunc) { // Nothing much to say about misc syntax. printf(r.node, "%s", astutil.NodeDescription(r.node)) } -func (r *describeUnknownResult) toSerial(res *serial.Result, fset *token.FileSet) { - res.Describe = &serial.Describe{ +func (r *describeUnknownResult) JSON(fset *token.FileSet) []byte { + return toJSON(&serial.Describe{ Desc: astutil.NodeDescription(r.node), Pos: fset.Position(r.node.Pos()).String(), - } + }) } type action int @@ -350,7 +354,7 @@ type describeValueResult struct { fields []describeField } -func (r *describeValueResult) display(printf printfFunc) { +func (r *describeValueResult) PrintPlain(printf printfFunc) { var prefix, suffix string if r.constVal != nil { suffix = fmt.Sprintf(" of constant value %s", constValString(r.constVal)) @@ -393,7 +397,7 @@ func (r *describeValueResult) display(printf printfFunc) { printFields(printf, r.expr, r.fields) } -func (r *describeValueResult) toSerial(res *serial.Result, fset *token.FileSet) { +func (r *describeValueResult) JSON(fset *token.FileSet) []byte { var value, objpos string if r.constVal != nil { value = r.constVal.String() @@ -402,7 +406,7 @@ func (r *describeValueResult) toSerial(res *serial.Result, fset *token.FileSet) objpos = fset.Position(r.obj.Pos()).String() } - res.Describe = &serial.Describe{ + return toJSON(&serial.Describe{ Desc: astutil.NodeDescription(r.expr), Pos: fset.Position(r.expr.Pos()).String(), Detail: "value", @@ -411,7 +415,7 @@ func (r *describeValueResult) toSerial(res *serial.Result, fset *token.FileSet) Value: value, ObjPos: objpos, }, - } + }) } // ---- TYPE ------------------------------------------------------------ @@ -519,11 +523,12 @@ func printFields(printf printfFunc, node ast.Node, fields []describeField) { } } -func (r *describeTypeResult) display(printf printfFunc) { +func (r *describeTypeResult) PrintPlain(printf printfFunc) { printf(r.node, "%s", r.description) // Show the underlying type for a reference to a named type. if nt, ok := r.typ.(*types.Named); ok && r.node.Pos() != nt.Obj().Pos() { + // TODO(adonovan): improve display of complex struct/interface types. printf(nt.Obj(), "defined as %s", r.qpos.typeString(nt.Underlying())) } @@ -540,13 +545,13 @@ func (r *describeTypeResult) display(printf printfFunc) { printFields(printf, r.node, r.fields) } -func (r *describeTypeResult) toSerial(res *serial.Result, fset *token.FileSet) { +func (r *describeTypeResult) JSON(fset *token.FileSet) []byte { var namePos, nameDef string if nt, ok := r.typ.(*types.Named); ok { namePos = fset.Position(nt.Obj().Pos()).String() nameDef = nt.Underlying().String() } - res.Describe = &serial.Describe{ + return toJSON(&serial.Describe{ Desc: r.description, Pos: fset.Position(r.node.Pos()).String(), Detail: "type", @@ -556,7 +561,7 @@ func (r *describeTypeResult) toSerial(res *serial.Result, fset *token.FileSet) { NameDef: nameDef, Methods: methodsToSerial(r.qpos.info.Pkg, r.methods, fset), }, - } + }) } // ---- PACKAGE ------------------------------------------------------------ @@ -633,7 +638,7 @@ type describeMember struct { methods []*types.Selection // in types.MethodSet order } -func (r *describePackageResult) display(printf printfFunc) { +func (r *describePackageResult) PrintPlain(printf printfFunc) { printf(r.node, "%s", r.description) // Compute max width of name "column". @@ -700,7 +705,7 @@ func formatMember(obj types.Object, maxname int) string { return buf.String() } -func (r *describePackageResult) toSerial(res *serial.Result, fset *token.FileSet) { +func (r *describePackageResult) JSON(fset *token.FileSet) []byte { var members []*serial.DescribeMember for _, mem := range r.members { typ := mem.obj.Type() @@ -720,7 +725,7 @@ func (r *describePackageResult) toSerial(res *serial.Result, fset *token.FileSet Methods: methodsToSerial(r.pkg, mem.methods, fset), }) } - res.Describe = &serial.Describe{ + return toJSON(&serial.Describe{ Desc: r.description, Pos: fset.Position(r.node.Pos()).String(), Detail: "package", @@ -728,7 +733,7 @@ func (r *describePackageResult) toSerial(res *serial.Result, fset *token.FileSet Path: r.pkg.Path(), Members: members, }, - } + }) } func tokenOf(o types.Object) string { @@ -778,16 +783,16 @@ type describeStmtResult struct { description string } -func (r *describeStmtResult) display(printf printfFunc) { +func (r *describeStmtResult) PrintPlain(printf printfFunc) { printf(r.node, "%s", r.description) } -func (r *describeStmtResult) toSerial(res *serial.Result, fset *token.FileSet) { - res.Describe = &serial.Describe{ +func (r *describeStmtResult) JSON(fset *token.FileSet) []byte { + return toJSON(&serial.Describe{ Desc: r.description, Pos: fset.Position(r.node.Pos()).String(), Detail: "unknown", - } + }) } // ------------------- Utilities ------------------- diff --git a/cmd/guru/freevars.go b/cmd/guru/freevars.go index 39b04d48..a36d1f80 100644 --- a/cmd/guru/freevars.go +++ b/cmd/guru/freevars.go @@ -42,7 +42,6 @@ func freevars(q *Query) error { if err != nil { return err } - q.Fset = lprog.Fset qpos, err := parseQueryPos(lprog, q.Pos, false) if err != nil { @@ -156,10 +155,10 @@ func freevars(q *Query) error { } sort.Sort(byRef(refs)) - q.result = &freevarsResult{ + q.Output(lprog.Fset, &freevarsResult{ qpos: qpos, refs: refs, - } + }) return nil } @@ -175,7 +174,7 @@ type freevarsRef struct { obj types.Object } -func (r *freevarsResult) display(printf printfFunc) { +func (r *freevarsResult) PrintPlain(printf printfFunc) { if len(r.refs) == 0 { printf(r.qpos, "No free identifiers.") } else { @@ -192,18 +191,20 @@ func (r *freevarsResult) display(printf printfFunc) { } } -func (r *freevarsResult) toSerial(res *serial.Result, fset *token.FileSet) { - var refs []*serial.FreeVar - for _, ref := range r.refs { - refs = append(refs, - &serial.FreeVar{ - Pos: fset.Position(ref.obj.Pos()).String(), - Kind: ref.kind, - Ref: ref.ref, - Type: ref.typ.String(), - }) +func (r *freevarsResult) JSON(fset *token.FileSet) []byte { + var buf bytes.Buffer + for i, ref := range r.refs { + if i > 0 { + buf.WriteByte('\n') + } + buf.Write(toJSON(serial.FreeVar{ + Pos: fset.Position(ref.obj.Pos()).String(), + Kind: ref.kind, + Ref: ref.ref, + Type: ref.typ.String(), + })) } - res.Freevars = refs + return buf.Bytes() } // -------- utils -------- diff --git a/cmd/guru/go-guru.el b/cmd/guru/go-guru.el index a097d2dc..b79fe82e 100644 --- a/cmd/guru/go-guru.el +++ b/cmd/guru/go-guru.el @@ -301,11 +301,9 @@ function containing the current point." (defun go-guru-definition () "Jump to the definition of the selected identifier." (interactive) - ;; TODO(adonovan): use -format=sexpr when available to avoid a - ;; dependency and to simplify parsing. - (let* ((res (with-current-buffer (go-guru--exec "definition" nil '("-format=json")) + (let* ((res (with-current-buffer (go-guru--exec "definition" nil '("-json")) (goto-char (point-min)) - (cdr (car (json-read))))) + (json-read))) (desc (cdr (assoc 'desc res)))) (push-mark) (ring-insert find-tag-marker-ring (point-marker)) @@ -360,11 +358,10 @@ expression (of type 'error') may refer." (defun go-guru-what () "Run a 'what' query and return the parsed JSON response as an -associative list." - (let ((res (with-current-buffer (go-guru--exec "what" nil '("-format=json") t) - (goto-char (point-min)) - (cdr (car (json-read)))))) - res)) +association list." + (with-current-buffer (go-guru--exec "what" nil '("-json") t) + (goto-char (point-min)) + (json-read))) (defun go-guru--hl-symbols (posn face id) "Highlight the symbols at the positions POSN by creating diff --git a/cmd/guru/guru.go b/cmd/guru/guru.go index e5b4b269..0cc91573 100644 --- a/cmd/guru/guru.go +++ b/cmd/guru/guru.go @@ -11,6 +11,7 @@ package main // (&T{}, var t T, new(T), new(struct{array [3]T}), etc. import ( + "encoding/json" "fmt" "go/ast" "go/build" @@ -18,9 +19,9 @@ import ( "go/token" "go/types" "io" + "log" "path/filepath" - "golang.org/x/tools/cmd/guru/serial" "golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/go/buildutil" "golang.org/x/tools/go/loader" @@ -30,10 +31,15 @@ import ( type printfFunc func(pos interface{}, format string, args ...interface{}) -// queryResult is the interface of each query-specific result type. -type queryResult interface { - toSerial(res *serial.Result, fset *token.FileSet) - display(printf printfFunc) +// A QueryResult is an item of output. Each query produces a stream of +// query results, calling Query.Output for each one. +type QueryResult interface { + // JSON returns the QueryResult in JSON form. + JSON(fset *token.FileSet) []byte + + // PrintPlain prints the QueryResult in plain text form. + // The implementation calls printfFunc to print each line of output. + PrintPlain(printf printfFunc) } // A QueryPos represents the position provided as input to a query: @@ -65,7 +71,6 @@ func (qpos *queryPos) selectionString(sel *types.Selection) string { // A Query specifies a single guru query. type Query struct { - Mode string // query mode ("callers", etc) Pos string // query position Build *build.Context // package loading configuration @@ -74,32 +79,13 @@ type Query struct { PTALog io.Writer // (optional) pointer-analysis log file Reflection bool // model reflection soundly (currently slow). - // Populated during Run() - Fset *token.FileSet - result queryResult -} - -// Serial returns an instance of serial.Result, which implements the -// {xml,json}.Marshaler interfaces so that query results can be -// serialized as JSON or XML. -// -func (q *Query) Serial() *serial.Result { - resj := &serial.Result{Mode: q.Mode} - q.result.toSerial(resj, q.Fset) - return resj -} - -// WriteTo writes the guru query result res to out in a compiler diagnostic format. -func (q *Query) WriteTo(out io.Writer) { - printf := func(pos interface{}, format string, args ...interface{}) { - fprintf(out, q.Fset, pos, format, args...) - } - q.result.display(printf) + // result-printing function + Output func(*token.FileSet, QueryResult) } // Run runs an guru query and populates its Fset and Result. -func Run(q *Query) error { - switch q.Mode { +func Run(mode string, q *Query) error { + switch mode { case "callees": return callees(q) case "callers": @@ -125,7 +111,7 @@ func Run(q *Query) error { case "what": return what(q) default: - return fmt.Errorf("invalid mode: %q", q.Mode) + return fmt.Errorf("invalid mode: %q", mode) } } @@ -319,7 +305,6 @@ func deref(typ types.Type) types.Type { // // The output format is is compatible with the 'gnu' // compilation-error-regexp in Emacs' compilation mode. -// TODO(adonovan): support other editors. // func fprintf(w io.Writer, fset *token.FileSet, pos interface{}, format string, args ...interface{}) { var start, end token.Pos @@ -360,3 +345,11 @@ func fprintf(w io.Writer, fset *token.FileSet, pos interface{}, format string, a fmt.Fprintf(w, format, args...) io.WriteString(w, "\n") } + +func toJSON(x interface{}) []byte { + b, err := json.MarshalIndent(x, "", "\t") + if err != nil { + log.Fatalf("JSON error: %v", err) + } + return b +} diff --git a/cmd/guru/guru_test.go b/cmd/guru/guru_test.go index 86e00a77..96374640 100644 --- a/cmd/guru/guru_test.go +++ b/cmd/guru/guru_test.go @@ -28,7 +28,6 @@ package main_test import ( "bytes" - "encoding/json" "flag" "fmt" "go/build" @@ -41,8 +40,10 @@ import ( "path/filepath" "regexp" "runtime" + "sort" "strconv" "strings" + "sync" "testing" guru "golang.org/x/tools/cmd/guru" @@ -150,51 +151,53 @@ func parseQueries(t *testing.T, filename string) []*query { return queries } -// WriteResult writes res (-format=plain) to w, stripping file locations. -func WriteResult(w io.Writer, q *guru.Query) { - capture := new(bytes.Buffer) // capture standard output - q.WriteTo(capture) - for _, line := range strings.Split(capture.String(), "\n") { - // Remove a "file:line: " prefix. - if i := strings.Index(line, ": "); i >= 0 { - line = line[i+2:] - } - fmt.Fprintf(w, "%s\n", line) - } -} - // doQuery poses query q to the guru and writes its response and // error (if any) to out. -func doQuery(out io.Writer, q *query, useJson bool) { +func doQuery(out io.Writer, q *query, json bool) { fmt.Fprintf(out, "-------- @%s %s --------\n", q.verb, q.id) var buildContext = build.Default buildContext.GOPATH = "testdata" pkg := filepath.Dir(strings.TrimPrefix(q.filename, "testdata/src/")) + + var outputMu sync.Mutex // guards out, jsons + var jsons []string + output := func(fset *token.FileSet, qr guru.QueryResult) { + outputMu.Lock() + defer outputMu.Unlock() + if json { + jsons = append(jsons, string(qr.JSON(fset))) + } else { + // suppress position information + qr.PrintPlain(func(_ interface{}, format string, args ...interface{}) { + fmt.Fprintf(out, format, args...) + io.WriteString(out, "\n") + }) + } + } + query := guru.Query{ - Mode: q.verb, Pos: q.queryPos, Build: &buildContext, Scope: []string{pkg}, Reflection: true, + Output: output, } - if err := guru.Run(&query); err != nil { + + if err := guru.Run(q.verb, &query); err != nil { fmt.Fprintf(out, "\nError: %s\n", err) return } - if useJson { - // JSON output - b, err := json.MarshalIndent(query.Serial(), "", "\t") - if err != nil { - fmt.Fprintf(out, "JSON error: %s\n", err.Error()) - return + if json { + if q.verb == "referrers" { + sort.Strings(jsons[1:]) // for determinism + } + for _, json := range jsons { + fmt.Fprintf(out, "%s\n", json) } - out.Write(b) - fmt.Fprintln(out) } else { - // "plain" (compiler diagnostic format) output - WriteResult(out, &query) + io.WriteString(out, "\n") } } @@ -230,7 +233,7 @@ func TestGuru(t *testing.T) { "testdata/src/referrers-json/main.go", "testdata/src/what-json/main.go", } { - useJson := strings.Contains(filename, "-json/") + json := strings.Contains(filename, "-json/") queries := parseQueries(t, filename) golden := filename + "lden" got := filename + "t" @@ -245,7 +248,7 @@ func TestGuru(t *testing.T) { // Run the guru on each query, redirecting its output // and error (if any) to the foo.got file. for _, q := range queries { - doQuery(gotfh, q, useJson) + doQuery(gotfh, q, json) } // Compare foo.got with foo.golden. @@ -277,11 +280,10 @@ func TestIssue14684(t *testing.T) { var buildContext = build.Default buildContext.GOPATH = "testdata" query := guru.Query{ - Mode: "freevars", Pos: "testdata/src/README.txt:#1", Build: &buildContext, } - err := guru.Run(&query) + err := guru.Run("freevars", &query) if err == nil { t.Fatal("guru query succeeded unexpectedly") } diff --git a/cmd/guru/implements.go b/cmd/guru/implements.go index 07a617dc..e852441e 100644 --- a/cmd/guru/implements.go +++ b/cmd/guru/implements.go @@ -63,7 +63,6 @@ func implements(q *Query) error { if err != nil { return err } - q.Fset = lprog.Fset qpos, err := parseQueryPos(lprog, q.Pos, false) if err != nil { @@ -179,9 +178,9 @@ func implements(q *Query) error { } } - q.result = &implementsResult{ + q.Output(lprog.Fset, &implementsResult{ qpos, T, pos, to, from, fromPtr, method, toMethod, fromMethod, fromPtrMethod, - } + }) return nil } @@ -201,7 +200,7 @@ type implementsResult struct { fromPtrMethod []*types.Selection // method of type fromPtrMethod[i], if any } -func (r *implementsResult) display(printf printfFunc) { +func (r *implementsResult) PrintPlain(printf printfFunc) { relation := "is implemented by" meth := func(sel *types.Selection) { @@ -298,8 +297,15 @@ func (r *implementsResult) display(printf printfFunc) { } } -func (r *implementsResult) toSerial(res *serial.Result, fset *token.FileSet) { - res.Implements = &serial.Implements{ +func (r *implementsResult) JSON(fset *token.FileSet) []byte { + var method *serial.DescribeMethod + if r.method != nil { + method = &serial.DescribeMethod{ + Name: r.qpos.objectString(r.method), + Pos: fset.Position(r.method.Pos()).String(), + } + } + return toJSON(&serial.Implements{ T: makeImplementsType(r.t, fset), AssignableTo: makeImplementsTypes(r.to, fset), AssignableFrom: makeImplementsTypes(r.from, fset), @@ -307,13 +313,9 @@ func (r *implementsResult) toSerial(res *serial.Result, fset *token.FileSet) { AssignableToMethod: methodsToSerial(r.qpos.info.Pkg, r.toMethod, fset), AssignableFromMethod: methodsToSerial(r.qpos.info.Pkg, r.fromMethod, fset), AssignableFromPtrMethod: methodsToSerial(r.qpos.info.Pkg, r.fromPtrMethod, fset), - } - if r.method != nil { - res.Implements.Method = &serial.DescribeMethod{ - Name: r.qpos.objectString(r.method), - Pos: fset.Position(r.method.Pos()).String(), - } - } + Method: method, + }) + } func makeImplementsTypes(tt []types.Type, fset *token.FileSet) []serial.ImplementsType { diff --git a/cmd/guru/main.go b/cmd/guru/main.go index b99b0584..b704ef59 100644 --- a/cmd/guru/main.go +++ b/cmd/guru/main.go @@ -14,11 +14,10 @@ package main // import "golang.org/x/tools/cmd/guru" import ( "bufio" "bytes" - "encoding/json" - "encoding/xml" "flag" "fmt" "go/build" + "go/token" "io" "io/ioutil" "log" @@ -27,6 +26,7 @@ import ( "runtime/pprof" "strconv" "strings" + "sync" "golang.org/x/tools/go/buildutil" ) @@ -36,7 +36,7 @@ var ( modifiedFlag = flag.Bool("modified", false, "read archive of modified files from standard input") scopeFlag = flag.String("scope", "", "comma-separated list of `packages` the analysis should be limited to") ptalogFlag = flag.String("ptalog", "", "write points-to analysis log to `file`") - formatFlag = flag.String("format", "plain", "output `format`; one of {plain,json,xml}") + jsonFlag = flag.Bool("json", false, "emit output in JSON format") reflectFlag = flag.Bool("reflect", false, "analyze reflection soundly (slow)") cpuprofileFlag = flag.String("cpuprofile", "", "write CPU profile to `file`") ) @@ -71,11 +71,10 @@ of the syntax element to query. For example: foo.go:#123,#128 bar.go:#123 -The -format flag controls the output format: - plain an editor-friendly format in which every line of output - is of the form "pos: text", where pos is "-" if unknown. - json structured data in JSON syntax. - xml structured data in XML syntax. +The -json flag causes guru to emit output in JSON format; + golang.org/x/tools/cmd/guru/serial defines its schema. + Otherwise, the output is in an editor-friendly format in which + every line has the form "pos: text", where pos is "-" if unknown. The -modified flag causes guru to read an archive from standard input. Files in this archive will be used in preference to those in @@ -163,14 +162,6 @@ func main() { defer pprof.StopCPUProfile() } - // -format flag - switch *formatFlag { - case "json", "plain", "xml": - // ok - default: - log.Fatalf("illegal -format value: %q.\n"+useHelp, *formatFlag) - } - ctxt := &build.Default // If there were modified files, @@ -191,39 +182,35 @@ func main() { } } + var outputMu sync.Mutex + output := func(fset *token.FileSet, qr QueryResult) { + outputMu.Lock() + defer outputMu.Unlock() + if *jsonFlag { + // JSON output + fmt.Printf("%s\n", qr.JSON(fset)) + } else { + // plain output + printf := func(pos interface{}, format string, args ...interface{}) { + fprintf(os.Stdout, fset, pos, format, args...) + } + qr.PrintPlain(printf) + } + } + // Ask the guru. query := Query{ - Mode: mode, Pos: posn, Build: ctxt, Scope: strings.Split(*scopeFlag, ","), PTALog: ptalog, Reflection: *reflectFlag, + Output: output, } - if err := Run(&query); err != nil { + if err := Run(mode, &query); err != nil { log.Fatal(err) } - - // Print the result. - switch *formatFlag { - case "json": - b, err := json.MarshalIndent(query.Serial(), "", "\t") - if err != nil { - log.Fatalf("JSON error: %s", err) - } - os.Stdout.Write(b) - - case "xml": - b, err := xml.MarshalIndent(query.Serial(), "", "\t") - if err != nil { - log.Fatalf("XML error: %s", err) - } - os.Stdout.Write(b) - - case "plain": - query.WriteTo(os.Stdout) - } } func parseArchive(archive io.Reader) (map[string][]byte, error) { diff --git a/cmd/guru/peers.go b/cmd/guru/peers.go index 1d3ed15a..35bd3c83 100644 --- a/cmd/guru/peers.go +++ b/cmd/guru/peers.go @@ -35,7 +35,6 @@ func peers(q *Query) error { if err != nil { return err } - q.Fset = lprog.Fset qpos, err := parseQueryPos(lprog, q.Pos, false) if err != nil { @@ -127,14 +126,14 @@ func peers(q *Query) error { sort.Sort(byPos(receives)) sort.Sort(byPos(closes)) - q.result = &peersResult{ + q.Output(lprog.Fset, &peersResult{ queryPos: opPos, queryType: queryType, makes: makes, sends: sends, receives: receives, closes: closes, - } + }) return nil } @@ -195,13 +194,14 @@ func chanOps(instr ssa.Instruction) []chanOp { return ops } +// TODO(adonovan): show the line of text for each pos, like "referrers" does. type peersResult struct { queryPos token.Pos // of queried channel op queryType types.Type // type of queried channel makes, sends, receives, closes []token.Pos // positions of aliased makechan/send/receive/close instrs } -func (r *peersResult) display(printf printfFunc) { +func (r *peersResult) PrintPlain(printf printfFunc) { if len(r.makes) == 0 { printf(r.queryPos, "This channel can't point to anything.") return @@ -221,7 +221,7 @@ func (r *peersResult) display(printf printfFunc) { } } -func (r *peersResult) toSerial(res *serial.Result, fset *token.FileSet) { +func (r *peersResult) JSON(fset *token.FileSet) []byte { peers := &serial.Peers{ Pos: fset.Position(r.queryPos).String(), Type: r.queryType.String(), @@ -238,7 +238,7 @@ func (r *peersResult) toSerial(res *serial.Result, fset *token.FileSet) { for _, clos := range r.closes { peers.Closes = append(peers.Closes, fset.Position(clos).String()) } - res.Peers = peers + return toJSON(peers) } // -------- utils -------- diff --git a/cmd/guru/pointsto.go b/cmd/guru/pointsto.go index 65fff7b4..90e128c5 100644 --- a/cmd/guru/pointsto.go +++ b/cmd/guru/pointsto.go @@ -38,7 +38,6 @@ func pointsto(q *Query) error { if err != nil { return err } - q.Fset = lprog.Fset qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos if err != nil { @@ -103,11 +102,11 @@ func pointsto(q *Query) error { return err // e.g. analytically unreachable } - q.result = &pointstoResult{ + q.Output(lprog.Fset, &pointstoResult{ qpos: qpos, typ: typ, ptrs: ptrs, - } + }) return nil } @@ -209,7 +208,7 @@ type pointstoResult struct { ptrs []pointerResult // pointer info (typ is concrete => len==1) } -func (r *pointstoResult) display(printf printfFunc) { +func (r *pointstoResult) PrintPlain(printf printfFunc) { if pointer.CanHaveDynamicTypes(r.typ) { // Show concrete types for interface, reflect.Type or // reflect.Value expression. @@ -244,7 +243,7 @@ func (r *pointstoResult) display(printf printfFunc) { } } -func (r *pointstoResult) toSerial(res *serial.Result, fset *token.FileSet) { +func (r *pointstoResult) JSON(fset *token.FileSet) []byte { var pts []serial.PointsTo for _, ptr := range r.ptrs { var namePos string @@ -264,7 +263,7 @@ func (r *pointstoResult) toSerial(res *serial.Result, fset *token.FileSet) { Labels: labels, }) } - res.PointsTo = pts + return toJSON(pts) } type byTypeString []pointerResult diff --git a/cmd/guru/referrers.go b/cmd/guru/referrers.go index 15fcf04c..d6b9df90 100644 --- a/cmd/guru/referrers.go +++ b/cmd/guru/referrers.go @@ -82,17 +82,13 @@ func referrers(q *Query) error { return globalReferrers(q, qpos.info.Pkg.Path(), defpkg, objposn, pkglevel) } - // Find uses of obj within the query package. - refs := usesOf(obj, qpos.info) - sort.Sort(byNamePos{fset, refs}) - q.Fset = fset - q.result = &referrersResult{ - build: q.Build, - fset: fset, + q.Output(fset, &referrersInitialResult{ qinfo: qpos.info, obj: obj, - refs: refs, - } + }) + + outputUses(q, fset, usesOf(obj, qpos.info), obj.Pkg()) + return nil // success } @@ -113,8 +109,8 @@ func classify(obj types.Object) (global, pkglevel bool) { return false, false } -// packageReferrers finds all references to the specified package -// throughout the workspace and populates q.result. +// packageReferrers reports all references to the specified package +// throughout the workspace. func packageReferrers(q *Query, path string) error { // Scan the workspace and build the import graph. // Ignore broken packages. @@ -134,34 +130,86 @@ func packageReferrers(q *Query, path string) error { }, } allowErrors(&lconf) + + // The importgraph doesn't treat external test packages + // as separate nodes, so we must use ImportWithTests. for path := range users { lconf.ImportWithTests(path) } - lprog, err := lconf.Load() - if err != nil { - return err + + // Subtle! AfterTypeCheck needs no mutex for qpkg because the + // topological import order gives us the necessary happens-before edges. + // TODO(adonovan): what about import cycles? + var qpkg *types.Package + + // For efficiency, we scan each package for references + // just after it has been type-checked. The loader calls + // AfterTypeCheck (concurrently), providing us with a stream of + // packages. + lconf.AfterTypeCheck = func(info *loader.PackageInfo, files []*ast.File) { + // AfterTypeCheck may be called twice for the same package due to augmentation. + + if info.Pkg.Path() == path && qpkg == nil { + // Found the package of interest. + qpkg = info.Pkg + fakepkgname := types.NewPkgName(token.NoPos, qpkg, qpkg.Name(), qpkg) + q.Output(fset, &referrersInitialResult{ + qinfo: info, + obj: fakepkgname, // bogus + }) + } + + // Only inspect packages that directly import the + // declaring package (and thus were type-checked). + if lconf.TypeCheckFuncBodies(info.Pkg.Path()) { + // Find PkgNames that refer to qpkg. + // TODO(adonovan): perhaps more useful would be to show imports + // of the package instead of qualified identifiers. + var refs []*ast.Ident + for id, obj := range info.Uses { + if obj, ok := obj.(*types.PkgName); ok && obj.Imported() == qpkg { + refs = append(refs, id) + } + } + outputUses(q, fset, refs, info.Pkg) + } + + clearInfoFields(info) // save memory } - // Find uses of [a fake PkgName that imports] the package. - // - // TODO(adonovan): perhaps more useful would be to show imports - // of the package instead of qualified identifiers. - qinfo := lprog.Package(path) - obj := types.NewPkgName(token.NoPos, qinfo.Pkg, qinfo.Pkg.Name(), qinfo.Pkg) - refs := usesOf(obj, lprog.InitialPackages()...) - sort.Sort(byNamePos{fset, refs}) - q.Fset = fset - q.result = &referrersResult{ - build: q.Build, - fset: fset, - qinfo: qinfo, - obj: obj, - refs: refs, + lconf.Load() // ignore error + + if qpkg == nil { + log.Fatalf("query package %q not found during reloading", path) } + return nil } -// globalReferrers finds references throughout the entire workspace to the +func usesOf(queryObj types.Object, info *loader.PackageInfo) []*ast.Ident { + var refs []*ast.Ident + for id, obj := range info.Uses { + if sameObj(queryObj, obj) { + refs = append(refs, id) + } + } + return refs +} + +// outputUses outputs a result describing refs, which appear in the package denoted by info. +func outputUses(q *Query, fset *token.FileSet, refs []*ast.Ident, pkg *types.Package) { + if len(refs) > 0 { + sort.Sort(byNamePos{fset, refs}) + q.Output(fset, &referrersPackageResult{ + pkg: pkg, + build: q.Build, + fset: fset, + refs: refs, + }) + } +} + +// globalReferrers reports references throughout the entire workspace to the // object at the specified source position. Its defining package is defpkg, // and the query package is qpkg. isPkgLevel indicates whether the object // is defined at package-level. @@ -207,6 +255,11 @@ func globalReferrers(q *Query, qpkg, defpkg string, objposn token.Position, isPk // so we can't use them here. // TODO(adonovan): smooth things out once the other changes have landed. + // Results are reported concurrently from within the + // AfterTypeCheck hook. The program may provide a useful stream + // of information even if the user doesn't let the program run + // to completion. + var ( mu sync.Mutex qobj types.Object @@ -217,8 +270,9 @@ func globalReferrers(q *Query, qpkg, defpkg string, objposn token.Position, isPk // just after it has been type-checked. The loader calls // AfterTypeCheck (concurrently), providing us with a stream of // packages. - ch := make(chan []*ast.Ident) lconf.AfterTypeCheck = func(info *loader.PackageInfo, files []*ast.File) { + // AfterTypeCheck may be called twice for the same package due to augmentation. + // Only inspect packages that depend on the declaring package // (and thus were type-checked). if lconf.TypeCheckFuncBodies(info.Pkg.Path()) { @@ -233,66 +287,32 @@ func globalReferrers(q *Query, qpkg, defpkg string, objposn token.Position, isPk log.Fatalf("object at %s not found in package %s", objposn, defpkg) } + + // Object found. qinfo = info + q.Output(fset, &referrersInitialResult{ + qinfo: qinfo, + obj: qobj, + }) } obj := qobj mu.Unlock() // Look for references to the query object. if obj != nil { - ch <- usesOf(obj, info) + outputUses(q, fset, usesOf(obj, info), info.Pkg) } } - // TODO(adonovan): opt: save memory by eliminating unneeded scopes/objects. - // (Requires go/types change for Go 1.7.) - // info.Pkg.Scope().ClearChildren() - - // Discard the file ASTs and their accumulated type - // information to save memory. - info.Files = nil - info.Defs = make(map[*ast.Ident]types.Object) - info.Uses = make(map[*ast.Ident]types.Object) - info.Implicits = make(map[ast.Node]types.Object) - - // Also, disable future collection of wholly unneeded - // type information for the package in case there is - // more type-checking to do (augmentation). - info.Types = nil - info.Scopes = nil - info.Selections = nil + clearInfoFields(info) // save memory } - go func() { - lconf.Load() // ignore error - close(ch) - }() - - var refs []*ast.Ident - for ids := range ch { - refs = append(refs, ids...) - } - sort.Sort(byNamePos{fset, refs}) + lconf.Load() // ignore error if qobj == nil { log.Fatal("query object not found during reloading") } - // TODO(adonovan): in a follow-up, do away with the - // analyze/display split so we can print a stream of output - // directly from the AfterTypeCheck hook. - // (We should not assume that users let the program run long - // enough for Load to return.) - - q.Fset = fset - q.result = &referrersResult{ - build: q.Build, - fset: fset, - qinfo: qinfo, - obj: qobj, - refs: refs, - } - return nil // success } @@ -318,20 +338,6 @@ func findObject(fset *token.FileSet, info *types.Info, objposn token.Position) t return nil } -// usesOf returns all identifiers in the packages denoted by infos -// that refer to queryObj. -func usesOf(queryObj types.Object, infos ...*loader.PackageInfo) []*ast.Ident { - var refs []*ast.Ident - for _, info := range infos { - for id, obj := range info.Uses { - if sameObj(queryObj, obj) { - refs = append(refs, id) - } - } - } - return refs -} - // same reports whether x and y are identical, or both are PkgNames // that import the same Package. // @@ -347,6 +353,26 @@ func sameObj(x, y types.Object) bool { return false } +func clearInfoFields(info *loader.PackageInfo) { + // TODO(adonovan): opt: save memory by eliminating unneeded scopes/objects. + // (Requires go/types change for Go 1.7.) + // info.Pkg.Scope().ClearChildren() + + // Discard the file ASTs and their accumulated type + // information to save memory. + info.Files = nil + info.Defs = make(map[*ast.Ident]types.Object) + info.Uses = make(map[*ast.Ident]types.Object) + info.Implicits = make(map[ast.Node]types.Object) + + // Also, disable future collection of wholly unneeded + // type information for the package in case there is + // more type-checking to do (augmentation). + info.Types = nil + info.Scopes = nil + info.Selections = nil +} + // -------- utils -------- // An deterministic ordering for token.Pos that doesn't @@ -371,19 +397,39 @@ func (p byNamePos) Less(i, j int) bool { return lessPos(p.fset, p.ids[i].NamePos, p.ids[j].NamePos) } -type referrersResult struct { +// referrersInitialResult is the initial result of a "referrers" query. +type referrersInitialResult struct { + qinfo *loader.PackageInfo + obj types.Object // object it denotes +} + +func (r *referrersInitialResult) PrintPlain(printf printfFunc) { + printf(r.obj, "references to %s", + types.ObjectString(r.obj, types.RelativeTo(r.qinfo.Pkg))) +} + +func (r *referrersInitialResult) JSON(fset *token.FileSet) []byte { + var objpos string + if pos := r.obj.Pos(); pos.IsValid() { + objpos = fset.Position(pos).String() + } + return toJSON(&serial.ReferrersInitial{ + Desc: r.obj.String(), + ObjPos: objpos, + }) +} + +// referrersPackageResult is the streaming result for one package of a "referrers" query. +type referrersPackageResult struct { + pkg *types.Package build *build.Context fset *token.FileSet - qinfo *loader.PackageInfo - qpos *queryPos - obj types.Object // object it denotes refs []*ast.Ident // set of all other references to it } -func (r *referrersResult) display(printf printfFunc) { - printf(r.obj, "%d references to %s", - len(r.refs), types.ObjectString(r.obj, types.RelativeTo(r.qinfo.Pkg))) - +// forEachRef calls f(id, text) for id in r.refs, in order. +// Text is the text of the line on which id appears. +func (r *referrersPackageResult) foreachRef(f func(id *ast.Ident, text string)) { // Show referring lines, like grep. type fileinfo struct { refs []*ast.Ident @@ -432,13 +478,13 @@ func (r *referrersResult) display(printf printfFunc) { if more := len(fi.refs) - 1; more > 0 { suffix = fmt.Sprintf(" (+ %d more refs in this file)", more) } - printf(fi.refs[0], "%v%s", err, suffix) + f(fi.refs[0], err.Error()+suffix) continue } lines := bytes.Split(v.([]byte), []byte("\n")) for i, ref := range fi.refs { - printf(ref, "%s", lines[fi.linenums[i]-1]) + f(ref, string(lines[fi.linenums[i]-1])) } } } @@ -458,15 +504,19 @@ func readFile(ctxt *build.Context, filename string) ([]byte, error) { return buf.Bytes(), nil } -func (r *referrersResult) toSerial(res *serial.Result, fset *token.FileSet) { - referrers := &serial.Referrers{ - Desc: r.obj.String(), - } - if pos := r.obj.Pos(); pos != token.NoPos { // Package objects have no Pos() - referrers.ObjPos = fset.Position(pos).String() - } - for _, ref := range r.refs { - referrers.Refs = append(referrers.Refs, fset.Position(ref.NamePos).String()) - } - res.Referrers = referrers +func (r *referrersPackageResult) PrintPlain(printf printfFunc) { + r.foreachRef(func(id *ast.Ident, text string) { + printf(id, "%s", text) + }) +} + +func (r *referrersPackageResult) JSON(fset *token.FileSet) []byte { + refs := serial.ReferrersPackage{Package: r.pkg.Path()} + r.foreachRef(func(id *ast.Ident, text string) { + refs.Refs = append(refs.Refs, serial.Ref{ + Pos: fset.Position(id.NamePos).String(), + Text: text, + }) + }) + return toJSON(refs) } diff --git a/cmd/guru/serial/serial.go b/cmd/guru/serial/serial.go index f7260ef9..95287977 100644 --- a/cmd/guru/serial/serial.go +++ b/cmd/guru/serial/serial.go @@ -2,17 +2,30 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package serial defines the guru's schema for structured data -// serialization using JSON, XML, etc. -package serial - -// All 'pos' strings are of the form "file:line:col". -// TODO(adonovan): improve performance by sharing filename strings. -// TODO(adonovan): improve precision by providing the start/end -// interval when available. +// Package serial defines the guru's schema for -json output. // -// TODO(adonovan): consider richer encodings of types, functions, -// methods, etc. +// The output of a guru query is a stream of one or more JSON objects. +// This table shows the types of objects in the result stream for each +// query type. +// +// Query Result stream +// ----- ------------- +// callees Callees +// callers Caller ... +// callstack CallStack +// definition Definition +// describe Describe +// freevars FreeVar ... +// implements Implements +// peers Peers +// pointsto PointsTo ... +// referrers ReferrersInitial ReferrersPackage ... +// what What +// whicherrs WhichErrs +// +// All 'pos' strings in the output are of the form "file:line:col", +// where line is the 1-based line number and col is the 1-based byte index. +package serial // A Peers is the result of a 'peers' query. // If Allocs is empty, the selected channel can't point to anything. @@ -25,12 +38,22 @@ type Peers struct { Closes []string `json:"closes,omitempty"` // locations of aliased close(ch) ops } -// A Referrers is the result of a 'referrers' query. -type Referrers struct { - ObjPos string `json:"objpos,omitempty"` // location of the definition - Desc string `json:"desc"` // description of the denoted object - Refs []string `json:"refs,omitempty"` // locations of all references -} +// A "referrers" query emits a ReferrersInitial object followed by zero or +// more ReferrersPackage objects, one per package that contains a reference. +type ( + ReferrersInitial struct { + ObjPos string `json:"objpos,omitempty"` // location of the definition + Desc string `json:"desc"` // description of the denoted object + } + ReferrersPackage struct { + Package string `json:"package"` + Refs []Ref `json:"refs"` // non-empty list of references within this package + } + Ref struct { + Pos string `json:"pos"` // location of all references + Text string `json:"text"` // text of the referring line + } +) // A Definition is the result of a 'definition' query. type Definition struct { @@ -38,20 +61,21 @@ type Definition struct { Desc string `json:"desc"` // description of the denoted object } -type CalleesItem struct { - Name string `json:"name"` // full name of called function - Pos string `json:"pos"` // location of called function -} - // A Callees is the result of a 'callees' query. // // Callees is nonempty unless the call was a dynamic call on a // provably nil func or interface value. -type Callees struct { - Pos string `json:"pos"` // location of selected call site - Desc string `json:"desc"` // description of call site - Callees []*CalleesItem `json:"callees,omitempty"` // set of possible call targets -} +type ( + Callees struct { + Pos string `json:"pos"` // location of selected call site + Desc string `json:"desc"` // description of call site + Callees []*Callee `json:"callees"` + } + Callee struct { + Name string `json:"name"` // full name of called function + Pos string `json:"pos"` // location of called function + } +) // A Caller is one element of the slice returned by a 'callers' query. // (Callstack also contains a similar slice.) @@ -233,27 +257,3 @@ type WhichErrsType struct { Type string `json:"type,omitempty"` Position string `json:"position,omitempty"` } - -// A Result is the common result of any guru query. -// It contains a query-specific result element. -// -// TODO(adonovan): perhaps include other info such as: analysis scope, -// raw query position, stack of ast nodes, query package, etc. -type Result struct { - Mode string `json:"mode"` // mode of the query - - // Exactly one of the following fields is populated: - // the one specified by 'mode'. - Callees *Callees `json:"callees,omitempty"` - Callers []Caller `json:"callers,omitempty"` - Callstack *CallStack `json:"callstack,omitempty"` - Definition *Definition `json:"definition,omitempty"` - Describe *Describe `json:"describe,omitempty"` - Freevars []*FreeVar `json:"freevars,omitempty"` - Implements *Implements `json:"implements,omitempty"` - Peers *Peers `json:"peers,omitempty"` - PointsTo []PointsTo `json:"pointsto,omitempty"` - Referrers *Referrers `json:"referrers,omitempty"` - What *What `json:"what,omitempty"` - WhichErrs *WhichErrs `json:"whicherrs,omitempty"` -} diff --git a/cmd/guru/testdata/src/calls-json/main.golden b/cmd/guru/testdata/src/calls-json/main.golden index 80de6695..27dc5090 100644 --- a/cmd/guru/testdata/src/calls-json/main.golden +++ b/cmd/guru/testdata/src/calls-json/main.golden @@ -1,34 +1,28 @@ -------- @callees @callees-f -------- { - "mode": "callees", - "callees": { - "pos": "testdata/src/calls-json/main.go:8:3", - "desc": "dynamic function call", - "callees": [ - { - "name": "calls-json.main$1", - "pos": "testdata/src/calls-json/main.go:12:7" - } - ] - } + "pos": "testdata/src/calls-json/main.go:8:3", + "desc": "dynamic function call", + "callees": [ + { + "name": "calls-json.main$1", + "pos": "testdata/src/calls-json/main.go:12:7" + } + ] } -------- @callstack callstack-main.anon -------- { - "mode": "callstack", - "callstack": { - "pos": "testdata/src/calls-json/main.go:12:7", - "target": "calls-json.main$1", - "callers": [ - { - "pos": "testdata/src/calls-json/main.go:8:3", - "desc": "dynamic function call", - "caller": "calls-json.call" - }, - { - "pos": "testdata/src/calls-json/main.go:12:6", - "desc": "static function call", - "caller": "calls-json.main" - } - ] - } + "pos": "testdata/src/calls-json/main.go:12:7", + "target": "calls-json.main$1", + "callers": [ + { + "pos": "testdata/src/calls-json/main.go:8:3", + "desc": "dynamic function call", + "caller": "calls-json.call" + }, + { + "pos": "testdata/src/calls-json/main.go:12:6", + "desc": "static function call", + "caller": "calls-json.main" + } + ] } diff --git a/cmd/guru/testdata/src/describe-json/main.golden b/cmd/guru/testdata/src/describe-json/main.golden index 9d03661a..5806e0c4 100644 --- a/cmd/guru/testdata/src/describe-json/main.golden +++ b/cmd/guru/testdata/src/describe-json/main.golden @@ -1,111 +1,96 @@ -------- @describe pkgdecl -------- { - "mode": "describe", - "describe": { - "desc": "definition of package \"describe-json\"", - "pos": "testdata/src/describe-json/main.go:1:9", - "detail": "package", - "package": { - "path": "describe-json", - "members": [ - { - "name": "C", - "type": "int", - "pos": "testdata/src/describe-json/main.go:25:6", - "kind": "type", - "methods": [ - { - "name": "method (C) f()", - "pos": "testdata/src/describe-json/main.go:28:12" - } - ] - }, - { - "name": "D", - "type": "struct{}", - "pos": "testdata/src/describe-json/main.go:26:6", - "kind": "type", - "methods": [ - { - "name": "method (*D) f()", - "pos": "testdata/src/describe-json/main.go:29:13" - } - ] - }, - { - "name": "I", - "type": "interface{f()}", - "pos": "testdata/src/describe-json/main.go:21:6", - "kind": "type", - "methods": [ - { - "name": "method (I) f()", - "pos": "testdata/src/describe-json/main.go:22:2" - } - ] - }, - { - "name": "main", - "type": "func()", - "pos": "testdata/src/describe-json/main.go:7:6", - "kind": "func" - } - ] - } + "desc": "definition of package \"describe-json\"", + "pos": "testdata/src/describe-json/main.go:1:9", + "detail": "package", + "package": { + "path": "describe-json", + "members": [ + { + "name": "C", + "type": "int", + "pos": "testdata/src/describe-json/main.go:25:6", + "kind": "type", + "methods": [ + { + "name": "method (C) f()", + "pos": "testdata/src/describe-json/main.go:28:12" + } + ] + }, + { + "name": "D", + "type": "struct{}", + "pos": "testdata/src/describe-json/main.go:26:6", + "kind": "type", + "methods": [ + { + "name": "method (*D) f()", + "pos": "testdata/src/describe-json/main.go:29:13" + } + ] + }, + { + "name": "I", + "type": "interface{f()}", + "pos": "testdata/src/describe-json/main.go:21:6", + "kind": "type", + "methods": [ + { + "name": "method (I) f()", + "pos": "testdata/src/describe-json/main.go:22:2" + } + ] + }, + { + "name": "main", + "type": "func()", + "pos": "testdata/src/describe-json/main.go:7:6", + "kind": "func" + } + ] } } -------- @describe desc-val-p -------- { - "mode": "describe", - "describe": { - "desc": "identifier", - "pos": "testdata/src/describe-json/main.go:9:2", - "detail": "value", - "value": { - "type": "*int", - "objpos": "testdata/src/describe-json/main.go:9:2" - } + "desc": "identifier", + "pos": "testdata/src/describe-json/main.go:9:2", + "detail": "value", + "value": { + "type": "*int", + "objpos": "testdata/src/describe-json/main.go:9:2" } } -------- @describe desc-val-i -------- { - "mode": "describe", - "describe": { - "desc": "identifier", - "pos": "testdata/src/describe-json/main.go:16:8", - "detail": "value", - "value": { - "type": "I", - "objpos": "testdata/src/describe-json/main.go:12:6" - } + "desc": "identifier", + "pos": "testdata/src/describe-json/main.go:16:8", + "detail": "value", + "value": { + "type": "I", + "objpos": "testdata/src/describe-json/main.go:12:6" } } -------- @describe desc-stmt -------- { - "mode": "describe", - "describe": { - "desc": "go statement", - "pos": "testdata/src/describe-json/main.go:18:2", - "detail": "unknown" - } + "desc": "go statement", + "pos": "testdata/src/describe-json/main.go:18:2", + "detail": "unknown" } -------- @describe desc-type-C -------- { - "mode": "describe", - "describe": { - "desc": "definition of type C (size 8, align 8)", - "pos": "testdata/src/describe-json/main.go:25:6", - "detail": "type", - "type": { - "type": "C", - "namepos": "testdata/src/describe-json/main.go:25:6", - "namedef": "int", - "methods": [ - { - "name": "method (C) f()", - "pos": "testdata/src/describe-json/main.go:28:12" - } - ] - } + "desc": "definition of type C (size 8, align 8)", + "pos": "testdata/src/describe-json/main.go:25:6", + "detail": "type", + "type": { + "type": "C", + "namepos": "testdata/src/describe-json/main.go:25:6", + "namedef": "int", + "methods": [ + { + "name": "method (C) f()", + "pos": "testdata/src/describe-json/main.go:28:12" + } + ] } } diff --git a/cmd/guru/testdata/src/implements-json/main.golden b/cmd/guru/testdata/src/implements-json/main.golden index 7e37f9e0..ce18c1c6 100644 --- a/cmd/guru/testdata/src/implements-json/main.golden +++ b/cmd/guru/testdata/src/implements-json/main.golden @@ -1,159 +1,135 @@ -------- @implements E -------- { - "mode": "implements", - "implements": { - "type": { - "name": "implements-json.E", - "pos": "testdata/src/implements-json/main.go:10:6", - "kind": "interface" - } + "type": { + "name": "implements-json.E", + "pos": "testdata/src/implements-json/main.go:10:6", + "kind": "interface" } } -------- @implements F -------- { - "mode": "implements", - "implements": { - "type": { - "name": "implements-json.F", - "pos": "testdata/src/implements-json/main.go:12:6", - "kind": "interface" - }, - "to": [ - { - "name": "*implements-json.C", - "pos": "testdata/src/implements-json/main.go:21:6", - "kind": "pointer" - }, - { - "name": "implements-json.D", - "pos": "testdata/src/implements-json/main.go:22:6", - "kind": "struct" - }, - { - "name": "implements-json.FG", - "pos": "testdata/src/implements-json/main.go:16:6", - "kind": "interface" - } - ] - } -} --------- @implements FG -------- -{ - "mode": "implements", - "implements": { - "type": { - "name": "implements-json.FG", - "pos": "testdata/src/implements-json/main.go:16:6", - "kind": "interface" - }, - "to": [ - { - "name": "*implements-json.D", - "pos": "testdata/src/implements-json/main.go:22:6", - "kind": "pointer" - } - ], - "from": [ - { - "name": "implements-json.F", - "pos": "testdata/src/implements-json/main.go:12:6", - "kind": "interface" - } - ] - } -} --------- @implements slice -------- -{ - "mode": "implements", - "implements": { - "type": { - "name": "[]int", - "pos": "-", - "kind": "slice" - } - } -} --------- @implements C -------- -{ - "mode": "implements", - "implements": { - "type": { - "name": "implements-json.C", - "pos": "testdata/src/implements-json/main.go:21:6", - "kind": "basic" - }, - "fromptr": [ - { - "name": "implements-json.F", - "pos": "testdata/src/implements-json/main.go:12:6", - "kind": "interface" - } - ] - } -} --------- @implements starC -------- -{ - "mode": "implements", - "implements": { - "type": { + "type": { + "name": "implements-json.F", + "pos": "testdata/src/implements-json/main.go:12:6", + "kind": "interface" + }, + "to": [ + { "name": "*implements-json.C", "pos": "testdata/src/implements-json/main.go:21:6", "kind": "pointer" }, - "from": [ - { - "name": "implements-json.F", - "pos": "testdata/src/implements-json/main.go:12:6", - "kind": "interface" - } - ] - } -} --------- @implements D -------- -{ - "mode": "implements", - "implements": { - "type": { + { "name": "implements-json.D", "pos": "testdata/src/implements-json/main.go:22:6", "kind": "struct" }, - "from": [ - { - "name": "implements-json.F", - "pos": "testdata/src/implements-json/main.go:12:6", - "kind": "interface" - } - ], - "fromptr": [ - { - "name": "implements-json.FG", - "pos": "testdata/src/implements-json/main.go:16:6", - "kind": "interface" - } - ] - } + { + "name": "implements-json.FG", + "pos": "testdata/src/implements-json/main.go:16:6", + "kind": "interface" + } + ] } --------- @implements starD -------- +-------- @implements FG -------- { - "mode": "implements", - "implements": { - "type": { + "type": { + "name": "implements-json.FG", + "pos": "testdata/src/implements-json/main.go:16:6", + "kind": "interface" + }, + "to": [ + { "name": "*implements-json.D", "pos": "testdata/src/implements-json/main.go:22:6", "kind": "pointer" - }, - "from": [ - { - "name": "implements-json.F", - "pos": "testdata/src/implements-json/main.go:12:6", - "kind": "interface" - }, - { - "name": "implements-json.FG", - "pos": "testdata/src/implements-json/main.go:16:6", - "kind": "interface" - } - ] + } + ], + "from": [ + { + "name": "implements-json.F", + "pos": "testdata/src/implements-json/main.go:12:6", + "kind": "interface" + } + ] +} +-------- @implements slice -------- +{ + "type": { + "name": "[]int", + "pos": "-", + "kind": "slice" } } +-------- @implements C -------- +{ + "type": { + "name": "implements-json.C", + "pos": "testdata/src/implements-json/main.go:21:6", + "kind": "basic" + }, + "fromptr": [ + { + "name": "implements-json.F", + "pos": "testdata/src/implements-json/main.go:12:6", + "kind": "interface" + } + ] +} +-------- @implements starC -------- +{ + "type": { + "name": "*implements-json.C", + "pos": "testdata/src/implements-json/main.go:21:6", + "kind": "pointer" + }, + "from": [ + { + "name": "implements-json.F", + "pos": "testdata/src/implements-json/main.go:12:6", + "kind": "interface" + } + ] +} +-------- @implements D -------- +{ + "type": { + "name": "implements-json.D", + "pos": "testdata/src/implements-json/main.go:22:6", + "kind": "struct" + }, + "from": [ + { + "name": "implements-json.F", + "pos": "testdata/src/implements-json/main.go:12:6", + "kind": "interface" + } + ], + "fromptr": [ + { + "name": "implements-json.FG", + "pos": "testdata/src/implements-json/main.go:16:6", + "kind": "interface" + } + ] +} +-------- @implements starD -------- +{ + "type": { + "name": "*implements-json.D", + "pos": "testdata/src/implements-json/main.go:22:6", + "kind": "pointer" + }, + "from": [ + { + "name": "implements-json.F", + "pos": "testdata/src/implements-json/main.go:12:6", + "kind": "interface" + }, + { + "name": "implements-json.FG", + "pos": "testdata/src/implements-json/main.go:16:6", + "kind": "interface" + } + ] +} diff --git a/cmd/guru/testdata/src/implements-methods-json/main.golden b/cmd/guru/testdata/src/implements-methods-json/main.golden index fa117df4..137261b6 100644 --- a/cmd/guru/testdata/src/implements-methods-json/main.golden +++ b/cmd/guru/testdata/src/implements-methods-json/main.golden @@ -1,290 +1,266 @@ -------- @implements F.f -------- { - "mode": "implements", - "implements": { - "type": { - "name": "implements-methods-json.F", - "pos": "testdata/src/implements-methods-json/main.go:12:6", - "kind": "interface" - }, - "to": [ - { - "name": "*implements-methods-json.C", - "pos": "testdata/src/implements-methods-json/main.go:21:6", - "kind": "pointer" - }, - { - "name": "implements-methods-json.D", - "pos": "testdata/src/implements-methods-json/main.go:22:6", - "kind": "struct" - }, - { - "name": "implements-methods-json.FG", - "pos": "testdata/src/implements-methods-json/main.go:16:6", - "kind": "interface" - } - ], - "method": { - "name": "func (F).f()", - "pos": "testdata/src/implements-methods-json/main.go:13:2" - }, - "to_method": [ - { - "name": "method (*C) f()", - "pos": "testdata/src/implements-methods-json/main.go:24:13" - }, - { - "name": "method (D) f()", - "pos": "testdata/src/implements-methods-json/main.go:25:12" - }, - { - "name": "method (FG) f()", - "pos": "testdata/src/implements-methods-json/main.go:17:2" - } - ] - } -} --------- @implements FG.f -------- -{ - "mode": "implements", - "implements": { - "type": { - "name": "implements-methods-json.FG", - "pos": "testdata/src/implements-methods-json/main.go:16:6", - "kind": "interface" - }, - "to": [ - { - "name": "*implements-methods-json.D", - "pos": "testdata/src/implements-methods-json/main.go:22:6", - "kind": "pointer" - } - ], - "from": [ - { - "name": "implements-methods-json.F", - "pos": "testdata/src/implements-methods-json/main.go:12:6", - "kind": "interface" - } - ], - "method": { - "name": "func (FG).f()", - "pos": "testdata/src/implements-methods-json/main.go:17:2" - }, - "to_method": [ - { - "name": "method (*D) f()", - "pos": "testdata/src/implements-methods-json/main.go:25:12" - } - ], - "from_method": [ - { - "name": "method (F) f()", - "pos": "testdata/src/implements-methods-json/main.go:13:2" - } - ] - } -} --------- @implements FG.g -------- -{ - "mode": "implements", - "implements": { - "type": { - "name": "implements-methods-json.FG", - "pos": "testdata/src/implements-methods-json/main.go:16:6", - "kind": "interface" - }, - "to": [ - { - "name": "*implements-methods-json.D", - "pos": "testdata/src/implements-methods-json/main.go:22:6", - "kind": "pointer" - } - ], - "from": [ - { - "name": "implements-methods-json.F", - "pos": "testdata/src/implements-methods-json/main.go:12:6", - "kind": "interface" - } - ], - "method": { - "name": "func (FG).g() []int", - "pos": "testdata/src/implements-methods-json/main.go:18:2" - }, - "to_method": [ - { - "name": "method (*D) g() []int", - "pos": "testdata/src/implements-methods-json/main.go:27:13" - } - ], - "from_method": [ - { - "name": "", - "pos": "" - } - ] - } -} --------- @implements *C.f -------- -{ - "mode": "implements", - "implements": { - "type": { + "type": { + "name": "implements-methods-json.F", + "pos": "testdata/src/implements-methods-json/main.go:12:6", + "kind": "interface" + }, + "to": [ + { "name": "*implements-methods-json.C", "pos": "testdata/src/implements-methods-json/main.go:21:6", "kind": "pointer" }, - "from": [ - { - "name": "implements-methods-json.F", - "pos": "testdata/src/implements-methods-json/main.go:12:6", - "kind": "interface" - } - ], - "method": { - "name": "func (*C).f()", - "pos": "testdata/src/implements-methods-json/main.go:24:13" - }, - "from_method": [ - { - "name": "method (F) f()", - "pos": "testdata/src/implements-methods-json/main.go:13:2" - } - ] - } -} --------- @implements D.f -------- -{ - "mode": "implements", - "implements": { - "type": { + { "name": "implements-methods-json.D", "pos": "testdata/src/implements-methods-json/main.go:22:6", "kind": "struct" }, - "from": [ - { - "name": "implements-methods-json.F", - "pos": "testdata/src/implements-methods-json/main.go:12:6", - "kind": "interface" - } - ], - "fromptr": [ - { - "name": "implements-methods-json.FG", - "pos": "testdata/src/implements-methods-json/main.go:16:6", - "kind": "interface" - } - ], - "method": { - "name": "func (D).f()", + { + "name": "implements-methods-json.FG", + "pos": "testdata/src/implements-methods-json/main.go:16:6", + "kind": "interface" + } + ], + "method": { + "name": "func (F).f()", + "pos": "testdata/src/implements-methods-json/main.go:13:2" + }, + "to_method": [ + { + "name": "method (*C) f()", + "pos": "testdata/src/implements-methods-json/main.go:24:13" + }, + { + "name": "method (D) f()", "pos": "testdata/src/implements-methods-json/main.go:25:12" }, - "from_method": [ - { - "name": "method (F) f()", - "pos": "testdata/src/implements-methods-json/main.go:13:2" - } - ], - "fromptr_method": [ - { - "name": "method (FG) f()", - "pos": "testdata/src/implements-methods-json/main.go:17:2" - } - ] - } + { + "name": "method (FG) f()", + "pos": "testdata/src/implements-methods-json/main.go:17:2" + } + ] } --------- @implements *D.g -------- +-------- @implements FG.f -------- { - "mode": "implements", - "implements": { - "type": { + "type": { + "name": "implements-methods-json.FG", + "pos": "testdata/src/implements-methods-json/main.go:16:6", + "kind": "interface" + }, + "to": [ + { "name": "*implements-methods-json.D", "pos": "testdata/src/implements-methods-json/main.go:22:6", "kind": "pointer" - }, - "from": [ - { - "name": "implements-methods-json.F", - "pos": "testdata/src/implements-methods-json/main.go:12:6", - "kind": "interface" - }, - { - "name": "implements-methods-json.FG", - "pos": "testdata/src/implements-methods-json/main.go:16:6", - "kind": "interface" - } - ], - "method": { - "name": "func (*D).g() []int", + } + ], + "from": [ + { + "name": "implements-methods-json.F", + "pos": "testdata/src/implements-methods-json/main.go:12:6", + "kind": "interface" + } + ], + "method": { + "name": "func (FG).f()", + "pos": "testdata/src/implements-methods-json/main.go:17:2" + }, + "to_method": [ + { + "name": "method (*D) f()", + "pos": "testdata/src/implements-methods-json/main.go:25:12" + } + ], + "from_method": [ + { + "name": "method (F) f()", + "pos": "testdata/src/implements-methods-json/main.go:13:2" + } + ] +} +-------- @implements FG.g -------- +{ + "type": { + "name": "implements-methods-json.FG", + "pos": "testdata/src/implements-methods-json/main.go:16:6", + "kind": "interface" + }, + "to": [ + { + "name": "*implements-methods-json.D", + "pos": "testdata/src/implements-methods-json/main.go:22:6", + "kind": "pointer" + } + ], + "from": [ + { + "name": "implements-methods-json.F", + "pos": "testdata/src/implements-methods-json/main.go:12:6", + "kind": "interface" + } + ], + "method": { + "name": "func (FG).g() []int", + "pos": "testdata/src/implements-methods-json/main.go:18:2" + }, + "to_method": [ + { + "name": "method (*D) g() []int", "pos": "testdata/src/implements-methods-json/main.go:27:13" + } + ], + "from_method": [ + { + "name": "", + "pos": "" + } + ] +} +-------- @implements *C.f -------- +{ + "type": { + "name": "*implements-methods-json.C", + "pos": "testdata/src/implements-methods-json/main.go:21:6", + "kind": "pointer" + }, + "from": [ + { + "name": "implements-methods-json.F", + "pos": "testdata/src/implements-methods-json/main.go:12:6", + "kind": "interface" + } + ], + "method": { + "name": "func (*C).f()", + "pos": "testdata/src/implements-methods-json/main.go:24:13" + }, + "from_method": [ + { + "name": "method (F) f()", + "pos": "testdata/src/implements-methods-json/main.go:13:2" + } + ] +} +-------- @implements D.f -------- +{ + "type": { + "name": "implements-methods-json.D", + "pos": "testdata/src/implements-methods-json/main.go:22:6", + "kind": "struct" + }, + "from": [ + { + "name": "implements-methods-json.F", + "pos": "testdata/src/implements-methods-json/main.go:12:6", + "kind": "interface" + } + ], + "fromptr": [ + { + "name": "implements-methods-json.FG", + "pos": "testdata/src/implements-methods-json/main.go:16:6", + "kind": "interface" + } + ], + "method": { + "name": "func (D).f()", + "pos": "testdata/src/implements-methods-json/main.go:25:12" + }, + "from_method": [ + { + "name": "method (F) f()", + "pos": "testdata/src/implements-methods-json/main.go:13:2" + } + ], + "fromptr_method": [ + { + "name": "method (FG) f()", + "pos": "testdata/src/implements-methods-json/main.go:17:2" + } + ] +} +-------- @implements *D.g -------- +{ + "type": { + "name": "*implements-methods-json.D", + "pos": "testdata/src/implements-methods-json/main.go:22:6", + "kind": "pointer" + }, + "from": [ + { + "name": "implements-methods-json.F", + "pos": "testdata/src/implements-methods-json/main.go:12:6", + "kind": "interface" }, - "from_method": [ - { - "name": "", - "pos": "" - }, - { - "name": "method (FG) g() []int", - "pos": "testdata/src/implements-methods-json/main.go:18:2" - } - ] - } + { + "name": "implements-methods-json.FG", + "pos": "testdata/src/implements-methods-json/main.go:16:6", + "kind": "interface" + } + ], + "method": { + "name": "func (*D).g() []int", + "pos": "testdata/src/implements-methods-json/main.go:27:13" + }, + "from_method": [ + { + "name": "", + "pos": "" + }, + { + "name": "method (FG) g() []int", + "pos": "testdata/src/implements-methods-json/main.go:18:2" + } + ] } -------- @implements Len -------- { - "mode": "implements", - "implements": { - "type": { - "name": "implements-methods-json.sorter", - "pos": "testdata/src/implements-methods-json/main.go:29:6", - "kind": "slice" - }, - "from": [ - { - "name": "lib.Sorter", - "pos": "testdata/src/lib/lib.go:16:6", - "kind": "interface" - } - ], - "method": { - "name": "func (sorter).Len() int", - "pos": "testdata/src/implements-methods-json/main.go:31:15" - }, - "from_method": [ - { - "name": "method (lib.Sorter) Len() int", - "pos": "testdata/src/lib/lib.go:17:2" - } - ] - } + "type": { + "name": "implements-methods-json.sorter", + "pos": "testdata/src/implements-methods-json/main.go:29:6", + "kind": "slice" + }, + "from": [ + { + "name": "lib.Sorter", + "pos": "testdata/src/lib/lib.go:16:6", + "kind": "interface" + } + ], + "method": { + "name": "func (sorter).Len() int", + "pos": "testdata/src/implements-methods-json/main.go:31:15" + }, + "from_method": [ + { + "name": "method (lib.Sorter) Len() int", + "pos": "testdata/src/lib/lib.go:17:2" + } + ] } -------- @implements I.Method -------- { - "mode": "implements", - "implements": { - "type": { - "name": "implements-methods-json.I", - "pos": "testdata/src/implements-methods-json/main.go:35:6", - "kind": "interface" - }, - "to": [ - { - "name": "lib.Type", - "pos": "testdata/src/lib/lib.go:3:6", - "kind": "basic" - } - ], - "method": { - "name": "func (I).Method(*int) *int", - "pos": "testdata/src/implements-methods-json/main.go:36:2" - }, - "to_method": [ - { - "name": "method (lib.Type) Method(x *int) *int", - "pos": "testdata/src/lib/lib.go:5:13" - } - ] - } + "type": { + "name": "implements-methods-json.I", + "pos": "testdata/src/implements-methods-json/main.go:35:6", + "kind": "interface" + }, + "to": [ + { + "name": "lib.Type", + "pos": "testdata/src/lib/lib.go:3:6", + "kind": "basic" + } + ], + "method": { + "name": "func (I).Method(*int) *int", + "pos": "testdata/src/implements-methods-json/main.go:36:2" + }, + "to_method": [ + { + "name": "method (lib.Type) Method(x *int) *int", + "pos": "testdata/src/lib/lib.go:5:13" + } + ] } diff --git a/cmd/guru/testdata/src/peers-json/main.golden b/cmd/guru/testdata/src/peers-json/main.golden index 8c2d06c7..50d57160 100644 --- a/cmd/guru/testdata/src/peers-json/main.golden +++ b/cmd/guru/testdata/src/peers-json/main.golden @@ -1,15 +1,12 @@ -------- @peers peer-recv-chA -------- { - "mode": "peers", - "peers": { - "pos": "testdata/src/peers-json/main.go:11:7", - "type": "chan *int", - "allocs": [ - "testdata/src/peers-json/main.go:8:13" - ], - "receives": [ - "testdata/src/peers-json/main.go:9:2", - "testdata/src/peers-json/main.go:11:7" - ] - } + "pos": "testdata/src/peers-json/main.go:11:7", + "type": "chan *int", + "allocs": [ + "testdata/src/peers-json/main.go:8:13" + ], + "receives": [ + "testdata/src/peers-json/main.go:9:2", + "testdata/src/peers-json/main.go:11:7" + ] } diff --git a/cmd/guru/testdata/src/pointsto-json/main.golden b/cmd/guru/testdata/src/pointsto-json/main.golden index 13ac1df9..06a2204a 100644 --- a/cmd/guru/testdata/src/pointsto-json/main.golden +++ b/cmd/guru/testdata/src/pointsto-json/main.golden @@ -1,35 +1,29 @@ -------- @pointsto val-p -------- -{ - "mode": "pointsto", - "pointsto": [ - { - "type": "*int", - "labels": [ - { - "pos": "testdata/src/pointsto-json/main.go:8:6", - "desc": "s.x[*]" - } - ] - } - ] -} +[ + { + "type": "*int", + "labels": [ + { + "pos": "testdata/src/pointsto-json/main.go:8:6", + "desc": "s.x[*]" + } + ] + } +] -------- @pointsto val-i -------- -{ - "mode": "pointsto", - "pointsto": [ - { - "type": "*D", - "namepos": "testdata/src/pointsto-json/main.go:24:6", - "labels": [ - { - "pos": "testdata/src/pointsto-json/main.go:14:10", - "desc": "new" - } - ] - }, - { - "type": "C", - "namepos": "testdata/src/pointsto-json/main.go:23:6" - } - ] -} +[ + { + "type": "*D", + "namepos": "testdata/src/pointsto-json/main.go:24:6", + "labels": [ + { + "pos": "testdata/src/pointsto-json/main.go:14:10", + "desc": "new" + } + ] + }, + { + "type": "C", + "namepos": "testdata/src/pointsto-json/main.go:23:6" + } +] diff --git a/cmd/guru/testdata/src/referrers-json/main.golden b/cmd/guru/testdata/src/referrers-json/main.golden index 0b29003a..3fcd288a 100644 --- a/cmd/guru/testdata/src/referrers-json/main.golden +++ b/cmd/guru/testdata/src/referrers-json/main.golden @@ -1,64 +1,184 @@ -------- @referrers ref-package -------- { - "mode": "referrers", - "referrers": { - "desc": "package lib", - "refs": [ - "testdata/src/describe/main.go:91:8", - "testdata/src/imports/main.go:18:12", - "testdata/src/imports/main.go:19:2", - "testdata/src/imports/main.go:20:2", - "testdata/src/imports/main.go:21:8", - "testdata/src/imports/main.go:26:8", - "testdata/src/referrers-json/main.go:14:8", - "testdata/src/referrers-json/main.go:14:19", - "testdata/src/referrers/ext_test.go:10:7", - "testdata/src/referrers/int_test.go:7:7", - "testdata/src/referrers/main.go:16:8", - "testdata/src/referrers/main.go:16:19" - ] - } + "desc": "package lib" +} +{ + "package": "describe", + "refs": [ + { + "pos": "testdata/src/describe/main.go:91:8", + "text": "\tvar _ lib.Outer // @describe lib-outer \"Outer\"" + } + ] +} +{ + "package": "imports", + "refs": [ + { + "pos": "testdata/src/imports/main.go:18:12", + "text": "\tconst c = lib.Const // @describe ref-const \"Const\"" + }, + { + "pos": "testdata/src/imports/main.go:19:2", + "text": "\tlib.Func() // @describe ref-func \"Func\"" + }, + { + "pos": "testdata/src/imports/main.go:20:2", + "text": "\tlib.Var++ // @describe ref-var \"Var\"" + }, + { + "pos": "testdata/src/imports/main.go:21:8", + "text": "\tvar t lib.Type // @describe ref-type \"Type\"" + }, + { + "pos": "testdata/src/imports/main.go:26:8", + "text": "\tvar _ lib.Type // @describe ref-pkg \"lib\"" + } + ] +} +{ + "package": "referrers", + "refs": [ + { + "pos": "testdata/src/referrers/int_test.go:7:7", + "text": "\t_ = (lib.Type).Method // ref from internal test package" + } + ] +} +{ + "package": "referrers", + "refs": [ + { + "pos": "testdata/src/referrers/main.go:16:8", + "text": "\tvar v lib.Type = lib.Const // @referrers ref-package \"lib\"" + }, + { + "pos": "testdata/src/referrers/main.go:16:19", + "text": "\tvar v lib.Type = lib.Const // @referrers ref-package \"lib\"" + } + ] +} +{ + "package": "referrers-json", + "refs": [ + { + "pos": "testdata/src/referrers-json/main.go:14:8", + "text": "\tvar v lib.Type = lib.Const // @referrers ref-package \"lib\"" + }, + { + "pos": "testdata/src/referrers-json/main.go:14:19", + "text": "\tvar v lib.Type = lib.Const // @referrers ref-package \"lib\"" + } + ] +} +{ + "package": "referrers_test", + "refs": [ + { + "pos": "testdata/src/referrers/ext_test.go:10:7", + "text": "\t_ = (lib.Type).Method // ref from external test package" + } + ] } -------- @referrers ref-method -------- { - "mode": "referrers", - "referrers": { - "objpos": "testdata/src/lib/lib.go:5:13", - "desc": "func (lib.Type).Method(x *int) *int", - "refs": [ - "testdata/src/imports/main.go:22:9", - "testdata/src/referrers-json/main.go:15:8", - "testdata/src/referrers-json/main.go:16:8", - "testdata/src/referrers/ext_test.go:10:17", - "testdata/src/referrers/int_test.go:7:17", - "testdata/src/referrers/main.go:17:8", - "testdata/src/referrers/main.go:18:8" - ] - } + "objpos": "testdata/src/lib/lib.go:5:13", + "desc": "func (lib.Type).Method(x *int) *int" +} +{ + "package": "imports", + "refs": [ + { + "pos": "testdata/src/imports/main.go:22:9", + "text": "\tp := t.Method(\u0026a) // @describe ref-method \"Method\"" + } + ] +} +{ + "package": "referrers", + "refs": [ + { + "pos": "testdata/src/referrers/int_test.go:7:17", + "text": "\t_ = (lib.Type).Method // ref from internal test package" + } + ] +} +{ + "package": "referrers", + "refs": [ + { + "pos": "testdata/src/referrers/main.go:17:8", + "text": "\t_ = v.Method // @referrers ref-method \"Method\"" + }, + { + "pos": "testdata/src/referrers/main.go:18:8", + "text": "\t_ = v.Method" + } + ] +} +{ + "package": "referrers-json", + "refs": [ + { + "pos": "testdata/src/referrers-json/main.go:15:8", + "text": "\t_ = v.Method // @referrers ref-method \"Method\"" + }, + { + "pos": "testdata/src/referrers-json/main.go:16:8", + "text": "\t_ = v.Method" + } + ] +} +{ + "package": "referrers_test", + "refs": [ + { + "pos": "testdata/src/referrers/ext_test.go:10:17", + "text": "\t_ = (lib.Type).Method // ref from external test package" + } + ] } -------- @referrers ref-local -------- { - "mode": "referrers", - "referrers": { - "objpos": "testdata/src/referrers-json/main.go:14:6", - "desc": "var v lib.Type", - "refs": [ - "testdata/src/referrers-json/main.go:15:6", - "testdata/src/referrers-json/main.go:16:6", - "testdata/src/referrers-json/main.go:17:2", - "testdata/src/referrers-json/main.go:18:2" - ] - } + "objpos": "testdata/src/referrers-json/main.go:14:6", + "desc": "var v lib.Type" +} +{ + "package": "referrers-json", + "refs": [ + { + "pos": "testdata/src/referrers-json/main.go:15:6", + "text": "\t_ = v.Method // @referrers ref-method \"Method\"" + }, + { + "pos": "testdata/src/referrers-json/main.go:16:6", + "text": "\t_ = v.Method" + }, + { + "pos": "testdata/src/referrers-json/main.go:17:2", + "text": "\tv++ //@referrers ref-local \"v\"" + }, + { + "pos": "testdata/src/referrers-json/main.go:18:2", + "text": "\tv++" + } + ] } -------- @referrers ref-field -------- { - "mode": "referrers", - "referrers": { - "objpos": "testdata/src/referrers-json/main.go:10:2", - "desc": "field f int", - "refs": [ - "testdata/src/referrers-json/main.go:20:10", - "testdata/src/referrers-json/main.go:23:5" - ] - } + "objpos": "testdata/src/referrers-json/main.go:10:2", + "desc": "field f int" +} +{ + "package": "referrers-json", + "refs": [ + { + "pos": "testdata/src/referrers-json/main.go:20:10", + "text": "\t_ = s{}.f // @referrers ref-field \"f\"" + }, + { + "pos": "testdata/src/referrers-json/main.go:23:5", + "text": "\ts2.f = 1" + } + ] } diff --git a/cmd/guru/testdata/src/referrers/main.golden b/cmd/guru/testdata/src/referrers/main.golden index a719e670..044c54c7 100644 --- a/cmd/guru/testdata/src/referrers/main.golden +++ b/cmd/guru/testdata/src/referrers/main.golden @@ -1,50 +1,50 @@ -------- @referrers package-decl -------- -1 references to package main ("referrers") +references to package main ("referrers") var _ renamed.T -------- @referrers type -------- -2 references to type s struct{f int} +references to type s struct{f int} _ = s{}.f // @referrers ref-field "f" var s2 s -------- @referrers ref-package -------- -12 references to package lib +references to package lib + var v lib.Type = lib.Const // @referrers ref-package "lib" + var v lib.Type = lib.Const // @referrers ref-package "lib" + var v lib.Type = lib.Const // @referrers ref-package "lib" + var v lib.Type = lib.Const // @referrers ref-package "lib" var _ lib.Outer // @describe lib-outer "Outer" const c = lib.Const // @describe ref-const "Const" lib.Func() // @describe ref-func "Func" lib.Var++ // @describe ref-var "Var" var t lib.Type // @describe ref-type "Type" var _ lib.Type // @describe ref-pkg "lib" - var v lib.Type = lib.Const // @referrers ref-package "lib" - var v lib.Type = lib.Const // @referrers ref-package "lib" - _ = (lib.Type).Method // ref from external test package _ = (lib.Type).Method // ref from internal test package - var v lib.Type = lib.Const // @referrers ref-package "lib" - var v lib.Type = lib.Const // @referrers ref-package "lib" + _ = (lib.Type).Method // ref from external test package -------- @referrers ref-method -------- -7 references to func (Type).Method(x *int) *int +references to func (Type).Method(x *int) *int + _ = v.Method // @referrers ref-method "Method" + _ = v.Method + _ = v.Method // @referrers ref-method "Method" + _ = v.Method p := t.Method(&a) // @describe ref-method "Method" - _ = v.Method // @referrers ref-method "Method" - _ = v.Method - _ = (lib.Type).Method // ref from external test package _ = (lib.Type).Method // ref from internal test package - _ = v.Method // @referrers ref-method "Method" - _ = v.Method + _ = (lib.Type).Method // ref from external test package -------- @referrers ref-local -------- -4 references to var v lib.Type +references to var v lib.Type _ = v.Method // @referrers ref-method "Method" _ = v.Method v++ //@referrers ref-local "v" v++ -------- @referrers ref-field -------- -2 references to field f int +references to field f int _ = s{}.f // @referrers ref-field "f" s2.f = 1 -------- @referrers ref-type-U -------- -2 references to type U int +references to type U int open testdata/src/referrers/nosuchfile.y: no such file or directory (+ 1 more refs in this file) diff --git a/cmd/guru/testdata/src/what-json/main.golden b/cmd/guru/testdata/src/what-json/main.golden index ee1e3c7c..5d750bd9 100644 --- a/cmd/guru/testdata/src/what-json/main.golden +++ b/cmd/guru/testdata/src/what-json/main.golden @@ -1,51 +1,48 @@ -------- @what call -------- { - "mode": "what", - "what": { - "enclosing": [ - { - "desc": "identifier", - "start": 175, - "end": 176 - }, - { - "desc": "function call (or conversion)", - "start": 175, - "end": 178 - }, - { - "desc": "expression statement", - "start": 175, - "end": 178 - }, - { - "desc": "block", - "start": 172, - "end": 198 - }, - { - "desc": "function declaration", - "start": 160, - "end": 198 - }, - { - "desc": "source file", - "start": 0, - "end": 198 - } - ], - "modes": [ - "callees", - "callers", - "callstack", - "definition", - "describe", - "freevars", - "implements", - "pointsto", - "referrers" - ], - "srcdir": "testdata/src", - "importpath": "what-json" - } + "enclosing": [ + { + "desc": "identifier", + "start": 175, + "end": 176 + }, + { + "desc": "function call (or conversion)", + "start": 175, + "end": 178 + }, + { + "desc": "expression statement", + "start": 175, + "end": 178 + }, + { + "desc": "block", + "start": 172, + "end": 198 + }, + { + "desc": "function declaration", + "start": 160, + "end": 198 + }, + { + "desc": "source file", + "start": 0, + "end": 198 + } + ], + "modes": [ + "callees", + "callers", + "callstack", + "definition", + "describe", + "freevars", + "implements", + "pointsto", + "referrers" + ], + "srcdir": "testdata/src", + "importpath": "what-json" } diff --git a/cmd/guru/what.go b/cmd/guru/what.go index deddcb21..06f44dc0 100644 --- a/cmd/guru/what.go +++ b/cmd/guru/what.go @@ -29,10 +29,9 @@ func what(q *Query) error { if err != nil { return err } - q.Fset = qpos.fset // (ignore errors) - srcdir, importPath, _ := guessImportPath(q.Fset.File(qpos.start).Name(), q.Build) + srcdir, importPath, _ := guessImportPath(qpos.fset.File(qpos.start).Name(), q.Build) // Determine which query modes are applicable to the selection. enable := map[string]bool{ @@ -129,14 +128,14 @@ func what(q *Query) error { }) } - q.result = &whatResult{ + q.Output(qpos.fset, &whatResult{ path: qpos.path, srcdir: srcdir, importPath: importPath, modes: modes, object: object, sameids: sameids, - } + }) return nil } @@ -211,7 +210,7 @@ type whatResult struct { sameids []token.Pos } -func (r *whatResult) display(printf printfFunc) { +func (r *whatResult) PrintPlain(printf printfFunc) { for _, n := range r.path { printf(n, "%s", astutil.NodeDescription(n)) } @@ -223,7 +222,7 @@ func (r *whatResult) display(printf printfFunc) { } } -func (r *whatResult) toSerial(res *serial.Result, fset *token.FileSet) { +func (r *whatResult) JSON(fset *token.FileSet) []byte { var enclosing []serial.SyntaxNode for _, n := range r.path { enclosing = append(enclosing, serial.SyntaxNode{ @@ -238,12 +237,12 @@ func (r *whatResult) toSerial(res *serial.Result, fset *token.FileSet) { sameids = append(sameids, fset.Position(pos).String()) } - res.What = &serial.What{ + return toJSON(&serial.What{ Modes: r.modes, SrcDir: r.srcdir, ImportPath: r.importPath, Enclosing: enclosing, Object: r.object, SameIDs: sameids, - } + }) } diff --git a/cmd/guru/whicherrs.go b/cmd/guru/whicherrs.go index ff8c1e51..7cc7e5f8 100644 --- a/cmd/guru/whicherrs.go +++ b/cmd/guru/whicherrs.go @@ -41,7 +41,6 @@ func whicherrs(q *Query) error { if err != nil { return err } - q.Fset = lprog.Fset qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos if err != nil { @@ -209,7 +208,7 @@ func whicherrs(q *Query) error { sort.Sort(membersByPosAndString(res.consts)) sort.Sort(sorterrorType(res.types)) - q.result = res + q.Output(lprog.Fset, res) return nil } @@ -290,7 +289,7 @@ type whicherrsResult struct { types []*errorType } -func (r *whicherrsResult) display(printf printfFunc) { +func (r *whicherrsResult) PrintPlain(printf printfFunc) { if len(r.globals) > 0 { printf(r.qpos, "this error may point to these globals:") for _, g := range r.globals { @@ -311,7 +310,7 @@ func (r *whicherrsResult) display(printf printfFunc) { } } -func (r *whicherrsResult) toSerial(res *serial.Result, fset *token.FileSet) { +func (r *whicherrsResult) JSON(fset *token.FileSet) []byte { we := &serial.WhichErrs{} we.ErrPos = fset.Position(r.errpos).String() for _, g := range r.globals { @@ -326,5 +325,5 @@ func (r *whicherrsResult) toSerial(res *serial.Result, fset *token.FileSet) { et.Position = fset.Position(t.obj.Pos()).String() we.Types = append(we.Types, et) } - res.WhichErrs = we + return toJSON(we) }