forked from mvdan/sh
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
syntax: add a hidden api_dump program
See the documentation at the top of the file for its intended use.
- Loading branch information
Showing
1 changed file
with
214 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,214 @@ | ||
// Copyright (c) 2019, Daniel Martí <[email protected]> | ||
// See LICENSE for licensing information | ||
|
||
// +build ignore | ||
|
||
// api_dump is a simple program to create a description of what the syntax | ||
// package's API is. The main purpose is so that the JS package users can | ||
// inspect the API programmatically, to generate documentation, code, etc. | ||
// | ||
// To run with Go 1.11 or later: GO111MODULE=on go run api_dump.go | ||
|
||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"go/types" | ||
"os" | ||
"reflect" | ||
|
||
"golang.org/x/tools/go/packages" | ||
) | ||
|
||
type Package struct { | ||
Types map[string]NamedType `json:"types"` | ||
Funcs map[string]DocType `json:"funcs"` | ||
} | ||
|
||
type NamedType struct { | ||
Doc string `json:"doc"` | ||
Type interface{} `json:"type"` | ||
|
||
EnumValues []string `json:"enumvalues,omitempty"` | ||
Implementers []string `json:"implementers,omitempty"` | ||
|
||
Methods map[string]DocType `json:"methods"` | ||
} | ||
|
||
type DocType struct { | ||
Doc string `json:"doc"` | ||
Type interface{} `json:"type"` | ||
} | ||
|
||
func main() { | ||
cfg := &packages.Config{Mode: packages.LoadSyntax} | ||
pkgs, err := packages.Load(cfg, ".") | ||
if err != nil { | ||
fmt.Fprintln(os.Stderr, err) | ||
os.Exit(1) | ||
} | ||
if packages.PrintErrors(pkgs) > 0 { | ||
os.Exit(1) | ||
} | ||
if len(pkgs) != 1 { | ||
panic("expected exactly one package") | ||
} | ||
|
||
dump := &Package{ | ||
Types: map[string]NamedType{}, | ||
Funcs: map[string]DocType{}, | ||
} | ||
|
||
pkg := pkgs[0] | ||
scope := pkg.Types.Scope() | ||
var allImpls []*types.Pointer | ||
var allConsts []*types.Const | ||
|
||
for _, name := range scope.Names() { | ||
obj := scope.Lookup(name) | ||
if !obj.Exported() { | ||
continue | ||
} | ||
switch obj := obj.(type) { | ||
case *types.TypeName: | ||
// not interfaces | ||
if _, ok := obj.Type().(*types.Interface); !ok { | ||
// include pointer receivers too | ||
allImpls = append(allImpls, types.NewPointer(obj.Type())) | ||
} | ||
case *types.Const: | ||
allConsts = append(allConsts, obj) | ||
} | ||
} | ||
|
||
for _, name := range scope.Names() { | ||
obj := scope.Lookup(name) | ||
if !obj.Exported() { | ||
continue | ||
} | ||
if fn, ok := obj.(*types.Func); ok { | ||
dump.Funcs[fn.Name()] = DocType{ | ||
Type: dumpType(fn.Type()), | ||
} | ||
continue | ||
} | ||
tname, ok := obj.(*types.TypeName) | ||
if !ok { | ||
continue | ||
} | ||
name := tname.Name() | ||
named, ok := obj.Type().(*types.Named) | ||
if !ok { | ||
continue | ||
} | ||
|
||
under := named.Underlying() | ||
dumpNamed := NamedType{ | ||
Type: dumpType(under), | ||
Methods: map[string]DocType{}, | ||
} | ||
switch under := under.(type) { | ||
case *types.Basic: | ||
if under.Info()&types.IsInteger == 0 { | ||
break | ||
} | ||
for _, cnst := range allConsts { | ||
if cnst.Type() == named { | ||
dumpNamed.EnumValues = append(dumpNamed.EnumValues, cnst.Name()) | ||
} | ||
} | ||
case *types.Interface: | ||
for _, typ := range allImpls { | ||
if types.Implements(typ, under) { | ||
dumpNamed.Implementers = append(dumpNamed.Implementers, typ.Elem().String()) | ||
} | ||
} | ||
} | ||
for i := 0; i < named.NumMethods(); i++ { | ||
fn := named.Method(i) | ||
if !fn.Exported() { | ||
continue | ||
} | ||
dumpNamed.Methods[fn.Name()] = DocType{ | ||
Type: dumpType(fn.Type()), | ||
} | ||
} | ||
dump.Types[name] = dumpNamed | ||
} | ||
enc := json.NewEncoder(os.Stdout) | ||
enc.SetIndent("", "\t") | ||
if err := enc.Encode(dump); err != nil { | ||
fmt.Fprintln(os.Stderr, err) | ||
} | ||
} | ||
|
||
func dumpType(typ types.Type) interface{} { | ||
dump := map[string]interface{}{} | ||
switch typ := typ.(type) { | ||
case *types.Interface: | ||
dump["kind"] = "interface" | ||
methods := map[string]DocType{} | ||
for i := 0; i < typ.NumMethods(); i++ { | ||
fn := typ.Method(i) | ||
if !fn.Exported() { | ||
continue | ||
} | ||
methods[fn.Name()] = DocType{ | ||
Type: dumpType(fn.Type()), | ||
} | ||
} | ||
dump["methods"] = methods | ||
return dump | ||
case *types.Struct: | ||
dump["kind"] = "struct" | ||
type Field struct { | ||
Doc string `json:"doc"` | ||
Type interface{} `json:"type"` | ||
Embedded bool `json:"embedded"` | ||
} | ||
fields := map[string]Field{} | ||
for i := 0; i < typ.NumFields(); i++ { | ||
fd := typ.Field(i) | ||
if !fd.Exported() { | ||
continue | ||
} | ||
fields[fd.Name()] = Field{ | ||
Type: dumpType(fd.Type()), | ||
Embedded: fd.Embedded(), | ||
} | ||
} | ||
dump["fields"] = fields | ||
return dump | ||
case *types.Slice: | ||
dump["kind"] = "list" | ||
dump["elem"] = dumpType(typ.Elem()) | ||
return dump | ||
case *types.Pointer: | ||
dump["kind"] = "pointer" | ||
dump["elem"] = dumpType(typ.Elem()) | ||
return dump | ||
case *types.Signature: | ||
dump["kind"] = "function" | ||
dump["params"] = dumpTuple(typ.Params()) | ||
dump["results"] = dumpTuple(typ.Results()) | ||
return dump | ||
case *types.Basic: | ||
return typ.String() | ||
case *types.Named: | ||
return typ.String() | ||
} | ||
panic("TODO: " + reflect.TypeOf(typ).String()) | ||
} | ||
|
||
func dumpTuple(tuple *types.Tuple) []interface{} { | ||
typs := make([]interface{}, 0) | ||
for i := 0; i < tuple.Len(); i++ { | ||
vr := tuple.At(i) | ||
typs = append(typs, map[string]interface{}{ | ||
"name": vr.Name(), | ||
"type": dumpType(vr.Type()), | ||
}) | ||
} | ||
return typs | ||
} |