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()
|
||||
internalFile := filepath.Join(pkgDir, serviceName, serviceName+".go")
|
||||
internalDir := filepath.Join(pkgDir, serviceName)
|
||||
publicDir := serviceName
|
||||
publicTestFile := filepath.Join(publicDir, serviceName+"_test.go")
|
||||
|
||||
if !coreio.Local.IsFile(internalFile) {
|
||||
if !coreio.Local.Exists(internalDir) {
|
||||
continue
|
||||
}
|
||||
|
||||
symbols, err := getExportedSymbols(internalFile)
|
||||
symbols, err := getExportedSymbols(internalDir)
|
||||
if err != nil {
|
||||
return cli.Wrap(err, cli.Sprintf("error getting symbols for service '%s'", serviceName))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,16 @@ const Answer = 42
|
|||
var Value = Example{}
|
||||
|
||||
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())
|
||||
|
|
@ -44,9 +54,12 @@ func Run() {}
|
|||
require.Contains(t, content, `package 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.Another`)
|
||||
require.Contains(t, content, `const _ = impl.Answer`)
|
||||
require.Contains(t, content, `var _ = impl.Value`)
|
||||
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) {
|
||||
|
|
@ -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, `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/token"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"forge.lthn.ai/core/cli/pkg/cli" // Added
|
||||
"dappco.re/go/core/i18n" // Added
|
||||
"dappco.re/go/core/i18n"
|
||||
coreio "dappco.re/go/core/io"
|
||||
// Added
|
||||
|
||||
"forge.lthn.ai/core/cli/pkg/cli"
|
||||
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
|
@ -52,15 +55,15 @@ func runSync() error {
|
|||
}
|
||||
|
||||
serviceName := dir.Name()
|
||||
internalFile := filepath.Join(pkgDir, serviceName, serviceName+".go")
|
||||
internalDir := filepath.Join(pkgDir, serviceName)
|
||||
publicDir := serviceName
|
||||
publicFile := filepath.Join(publicDir, serviceName+".go")
|
||||
|
||||
if !coreio.Local.IsFile(internalFile) {
|
||||
if !coreio.Local.Exists(internalDir) {
|
||||
continue
|
||||
}
|
||||
|
||||
symbols, err := getExportedSymbols(internalFile)
|
||||
symbols, err := getExportedSymbols(internalDir)
|
||||
if err != nil {
|
||||
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) {
|
||||
// ParseFile expects a filename/path and reads it using os.Open by default if content is nil.
|
||||
// Since we want to use our Medium abstraction, we should read the file content first.
|
||||
content, err := coreio.Local.Read(path)
|
||||
files, err := listGoFiles(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
symbolsByName := make(map[string]symbolInfo)
|
||||
for _, file := range files {
|
||||
content, err := coreio.Local.Read(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fset := token.NewFileSet()
|
||||
// ParseFile can take content as string (src argument).
|
||||
node, err := parser.ParseFile(fset, path, content, parser.ParseComments)
|
||||
node, err := parser.ParseFile(fset, file, content, parser.ParseComments)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var symbols []symbolInfo
|
||||
for name, obj := range node.Scope.Objects {
|
||||
if ast.IsExported(name) {
|
||||
if !ast.IsExported(name) {
|
||||
continue
|
||||
}
|
||||
|
||||
kind := "unknown"
|
||||
switch obj.Kind {
|
||||
case ast.Con:
|
||||
|
|
@ -102,14 +111,59 @@ func getExportedSymbols(path string) ([]symbolInfo, error) {
|
|||
case ast.Typ:
|
||||
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
|
||||
}
|
||||
|
||||
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.
|
||||
package {{.ServiceName}}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue