feat(dev): scan full service packages for API stubs
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
fa20cb8aa5
commit
f3c5fe9a7b
3 changed files with 122 additions and 23 deletions
|
|
@ -40,15 +40,15 @@ func runTestGen() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceName := dir.Name()
|
serviceName := dir.Name()
|
||||||
internalFile := filepath.Join(pkgDir, serviceName, serviceName+".go")
|
internalDir := filepath.Join(pkgDir, serviceName)
|
||||||
publicDir := serviceName
|
publicDir := serviceName
|
||||||
publicTestFile := filepath.Join(publicDir, serviceName+"_test.go")
|
publicTestFile := filepath.Join(publicDir, serviceName+"_test.go")
|
||||||
|
|
||||||
if !coreio.Local.IsFile(internalFile) {
|
if !coreio.Local.Exists(internalDir) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
symbols, err := getExportedSymbols(internalFile)
|
symbols, err := getExportedSymbols(internalDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.Wrap(err, cli.Sprintf("error getting symbols for service '%s'", serviceName))
|
return cli.Wrap(err, cli.Sprintf("error getting symbols for service '%s'", serviceName))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,16 @@ const Answer = 42
|
||||||
var Value = Example{}
|
var Value = Example{}
|
||||||
|
|
||||||
func Run() {}
|
func Run() {}
|
||||||
|
`))
|
||||||
|
require.NoError(t, io.Local.Write(filepath.Join(serviceDir, "extra.go"), `package demo
|
||||||
|
|
||||||
|
type Another struct{}
|
||||||
|
|
||||||
|
func Extra() {}
|
||||||
|
`))
|
||||||
|
require.NoError(t, io.Local.Write(filepath.Join(serviceDir, "demo_test.go"), `package demo
|
||||||
|
|
||||||
|
func Ignored() {}
|
||||||
`))
|
`))
|
||||||
|
|
||||||
require.NoError(t, runTestGen())
|
require.NoError(t, runTestGen())
|
||||||
|
|
@ -44,9 +54,12 @@ func Run() {}
|
||||||
require.Contains(t, content, `package demo`)
|
require.Contains(t, content, `package demo`)
|
||||||
require.Contains(t, content, `impl "forge.lthn.ai/core/cli/demo"`)
|
require.Contains(t, content, `impl "forge.lthn.ai/core/cli/demo"`)
|
||||||
require.Contains(t, content, `type _ = impl.Example`)
|
require.Contains(t, content, `type _ = impl.Example`)
|
||||||
|
require.Contains(t, content, `type _ = impl.Another`)
|
||||||
require.Contains(t, content, `const _ = impl.Answer`)
|
require.Contains(t, content, `const _ = impl.Answer`)
|
||||||
require.Contains(t, content, `var _ = impl.Value`)
|
require.Contains(t, content, `var _ = impl.Value`)
|
||||||
require.Contains(t, content, `var _ = impl.Run`)
|
require.Contains(t, content, `var _ = impl.Run`)
|
||||||
|
require.Contains(t, content, `var _ = impl.Extra`)
|
||||||
|
require.NotContains(t, content, `Ignored`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGeneratePublicAPITestFile_Good(t *testing.T) {
|
func TestGeneratePublicAPITestFile_Good(t *testing.T) {
|
||||||
|
|
@ -68,3 +81,35 @@ func TestGeneratePublicAPITestFile_Good(t *testing.T) {
|
||||||
require.True(t, strings.Contains(content, `type _ = impl.Example`))
|
require.True(t, strings.Contains(content, `type _ = impl.Example`))
|
||||||
require.True(t, strings.Contains(content, `const _ = impl.Answer`))
|
require.True(t, strings.Contains(content, `const _ = impl.Answer`))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetExportedSymbols_Good_MultiFile(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
|
||||||
|
serviceDir := filepath.Join(tmpDir, "demo")
|
||||||
|
require.NoError(t, io.Local.EnsureDir(serviceDir))
|
||||||
|
require.NoError(t, io.Local.Write(filepath.Join(serviceDir, "demo.go"), `package demo
|
||||||
|
|
||||||
|
type Example struct{}
|
||||||
|
|
||||||
|
const Answer = 42
|
||||||
|
`))
|
||||||
|
require.NoError(t, io.Local.Write(filepath.Join(serviceDir, "extra.go"), `package demo
|
||||||
|
|
||||||
|
var Value = Example{}
|
||||||
|
|
||||||
|
func Run() {}
|
||||||
|
`))
|
||||||
|
require.NoError(t, io.Local.Write(filepath.Join(serviceDir, "demo_test.go"), `package demo
|
||||||
|
|
||||||
|
type Ignored struct{}
|
||||||
|
`))
|
||||||
|
|
||||||
|
symbols, err := getExportedSymbols(serviceDir)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, []symbolInfo{
|
||||||
|
{Name: "Answer", Kind: "const"},
|
||||||
|
{Name: "Example", Kind: "type"},
|
||||||
|
{Name: "Run", Kind: "func"},
|
||||||
|
{Name: "Value", Kind: "var"},
|
||||||
|
}, symbols)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,15 @@ import (
|
||||||
"go/parser"
|
"go/parser"
|
||||||
"go/token"
|
"go/token"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli" // Added
|
"dappco.re/go/core/i18n"
|
||||||
"dappco.re/go/core/i18n" // Added
|
|
||||||
coreio "dappco.re/go/core/io"
|
coreio "dappco.re/go/core/io"
|
||||||
// Added
|
|
||||||
|
"forge.lthn.ai/core/cli/pkg/cli"
|
||||||
|
|
||||||
"golang.org/x/text/cases"
|
"golang.org/x/text/cases"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
)
|
)
|
||||||
|
|
@ -52,15 +55,15 @@ func runSync() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceName := dir.Name()
|
serviceName := dir.Name()
|
||||||
internalFile := filepath.Join(pkgDir, serviceName, serviceName+".go")
|
internalDir := filepath.Join(pkgDir, serviceName)
|
||||||
publicDir := serviceName
|
publicDir := serviceName
|
||||||
publicFile := filepath.Join(publicDir, serviceName+".go")
|
publicFile := filepath.Join(publicDir, serviceName+".go")
|
||||||
|
|
||||||
if !coreio.Local.IsFile(internalFile) {
|
if !coreio.Local.Exists(internalDir) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
symbols, err := getExportedSymbols(internalFile)
|
symbols, err := getExportedSymbols(internalDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.Wrap(err, cli.Sprintf("error getting symbols for service '%s'", serviceName))
|
return cli.Wrap(err, cli.Sprintf("error getting symbols for service '%s'", serviceName))
|
||||||
}
|
}
|
||||||
|
|
@ -74,23 +77,29 @@ func runSync() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getExportedSymbols(path string) ([]symbolInfo, error) {
|
func getExportedSymbols(path string) ([]symbolInfo, error) {
|
||||||
// ParseFile expects a filename/path and reads it using os.Open by default if content is nil.
|
files, err := listGoFiles(path)
|
||||||
// Since we want to use our Medium abstraction, we should read the file content first.
|
|
||||||
content, err := coreio.Local.Read(path)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fset := token.NewFileSet()
|
symbolsByName := make(map[string]symbolInfo)
|
||||||
// ParseFile can take content as string (src argument).
|
for _, file := range files {
|
||||||
node, err := parser.ParseFile(fset, path, content, parser.ParseComments)
|
content, err := coreio.Local.Read(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
node, err := parser.ParseFile(fset, file, content, parser.ParseComments)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, obj := range node.Scope.Objects {
|
||||||
|
if !ast.IsExported(name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
var symbols []symbolInfo
|
|
||||||
for name, obj := range node.Scope.Objects {
|
|
||||||
if ast.IsExported(name) {
|
|
||||||
kind := "unknown"
|
kind := "unknown"
|
||||||
switch obj.Kind {
|
switch obj.Kind {
|
||||||
case ast.Con:
|
case ast.Con:
|
||||||
|
|
@ -102,14 +111,59 @@ func getExportedSymbols(path string) ([]symbolInfo, error) {
|
||||||
case ast.Typ:
|
case ast.Typ:
|
||||||
kind = "type"
|
kind = "type"
|
||||||
}
|
}
|
||||||
if kind != "unknown" {
|
|
||||||
symbols = append(symbols, symbolInfo{Name: name, Kind: kind})
|
if kind == "unknown" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := symbolsByName[name]; !exists {
|
||||||
|
symbolsByName[name] = symbolInfo{Name: name, Kind: kind}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
symbols := make([]symbolInfo, 0, len(symbolsByName))
|
||||||
|
for _, symbol := range symbolsByName {
|
||||||
|
symbols = append(symbols, symbol)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(symbols, func(i, j int) bool {
|
||||||
|
if symbols[i].Name == symbols[j].Name {
|
||||||
|
return symbols[i].Kind < symbols[j].Kind
|
||||||
|
}
|
||||||
|
return symbols[i].Name < symbols[j].Name
|
||||||
|
})
|
||||||
|
|
||||||
return symbols, nil
|
return symbols, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func listGoFiles(path string) ([]string, error) {
|
||||||
|
entries, err := coreio.Local.List(path)
|
||||||
|
if err == nil {
|
||||||
|
files := make([]string, 0, len(entries))
|
||||||
|
for _, entry := range entries {
|
||||||
|
if entry.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
name := entry.Name()
|
||||||
|
if !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
files = append(files, filepath.Join(path, name))
|
||||||
|
}
|
||||||
|
sort.Strings(files)
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if coreio.Local.IsFile(path) {
|
||||||
|
return []string{path}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
const publicAPITemplate = `// package {{.ServiceName}} provides the public API for the {{.ServiceName}} service.
|
const publicAPITemplate = `// package {{.ServiceName}} provides the public API for the {{.ServiceName}} service.
|
||||||
package {{.ServiceName}}
|
package {{.ServiceName}}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue