 9bdcbe0447
			
		
	
	9bdcbe0447
	
	
	
		
			
			Major integrations and fixes: - Added BACKBEAT SDK integration for P2P operation timing - Implemented beat-aware status tracking for distributed operations - Added Docker secrets support for secure license management - Resolved KACHING license validation via HTTPS/TLS - Updated docker-compose configuration for clean stack deployment - Disabled rollback policies to prevent deployment failures - Added license credential storage (CHORUS-DEV-MULTI-001) Technical improvements: - BACKBEAT P2P operation tracking with phase management - Enhanced configuration system with file-based secrets - Improved error handling for license validation - Clean separation of KACHING and CHORUS deployment stacks 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			803 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			803 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2012 Google Inc.
 | |
| //
 | |
| // Licensed under the Apache License, Version 2.0 (the "License");
 | |
| // you may not use this file except in compliance with the License.
 | |
| // You may obtain a copy of the License at
 | |
| //
 | |
| //     http://www.apache.org/licenses/LICENSE-2.0
 | |
| //
 | |
| // Unless required by applicable law or agreed to in writing, software
 | |
| // distributed under the License is distributed on an "AS IS" BASIS,
 | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| // See the License for the specific language governing permissions and
 | |
| // limitations under the License.
 | |
| 
 | |
| package main
 | |
| 
 | |
| // This file contains the model construction by parsing source files.
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"go/ast"
 | |
| 	"go/build"
 | |
| 	"go/importer"
 | |
| 	"go/parser"
 | |
| 	"go/token"
 | |
| 	"go/types"
 | |
| 	"log"
 | |
| 	"os"
 | |
| 	"path"
 | |
| 	"path/filepath"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 
 | |
| 	"go.uber.org/mock/mockgen/model"
 | |
| )
 | |
| 
 | |
| // sourceMode generates mocks via source file.
 | |
| func sourceMode(source string) (*model.Package, error) {
 | |
| 	srcDir, err := filepath.Abs(filepath.Dir(source))
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("failed getting source directory: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	packageImport, err := parsePackageImport(srcDir)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	fs := token.NewFileSet()
 | |
| 	file, err := parser.ParseFile(fs, source, nil, 0)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("failed parsing source file %v: %v", source, err)
 | |
| 	}
 | |
| 
 | |
| 	p := &fileParser{
 | |
| 		fileSet:            fs,
 | |
| 		imports:            make(map[string]importedPackage),
 | |
| 		importedInterfaces: newInterfaceCache(),
 | |
| 		auxInterfaces:      newInterfaceCache(),
 | |
| 		srcDir:             srcDir,
 | |
| 	}
 | |
| 
 | |
| 	// Handle -imports.
 | |
| 	dotImports := make(map[string]bool)
 | |
| 	if *imports != "" {
 | |
| 		for _, kv := range strings.Split(*imports, ",") {
 | |
| 			eq := strings.Index(kv, "=")
 | |
| 			k, v := kv[:eq], kv[eq+1:]
 | |
| 			if k == "." {
 | |
| 				dotImports[v] = true
 | |
| 			} else {
 | |
| 				p.imports[k] = importedPkg{path: v}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if *excludeInterfaces != "" {
 | |
| 		p.excludeNamesSet = parseExcludeInterfaces(*excludeInterfaces)
 | |
| 	}
 | |
| 
 | |
| 	// Handle -aux_files.
 | |
| 	if err := p.parseAuxFiles(*auxFiles); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	p.addAuxInterfacesFromFile(packageImport, file) // this file
 | |
| 
 | |
| 	pkg, err := p.parseFile(packageImport, file)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	for pkgPath := range dotImports {
 | |
| 		pkg.DotImports = append(pkg.DotImports, pkgPath)
 | |
| 	}
 | |
| 	return pkg, nil
 | |
| }
 | |
| 
 | |
| type importedPackage interface {
 | |
| 	Path() string
 | |
| 	Parser() *fileParser
 | |
| }
 | |
| 
 | |
| type importedPkg struct {
 | |
| 	path   string
 | |
| 	parser *fileParser
 | |
| }
 | |
| 
 | |
| func (i importedPkg) Path() string        { return i.path }
 | |
| func (i importedPkg) Parser() *fileParser { return i.parser }
 | |
| 
 | |
| // duplicateImport is a bit of a misnomer. Currently the parser can't
 | |
| // handle cases of multi-file packages importing different packages
 | |
| // under the same name. Often these imports would not be problematic,
 | |
| // so this type lets us defer raising an error unless the package name
 | |
| // is actually used.
 | |
| type duplicateImport struct {
 | |
| 	name       string
 | |
| 	duplicates []string
 | |
| }
 | |
| 
 | |
| func (d duplicateImport) Error() string {
 | |
| 	return fmt.Sprintf("%q is ambiguous because of duplicate imports: %v", d.name, d.duplicates)
 | |
| }
 | |
| 
 | |
| func (d duplicateImport) Path() string        { log.Fatal(d.Error()); return "" }
 | |
| func (d duplicateImport) Parser() *fileParser { log.Fatal(d.Error()); return nil }
 | |
| 
 | |
| type interfaceCache struct {
 | |
| 	m map[string]map[string]*namedInterface
 | |
| }
 | |
| 
 | |
| func newInterfaceCache() *interfaceCache {
 | |
| 	return &interfaceCache{
 | |
| 		m: make(map[string]map[string]*namedInterface),
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (i *interfaceCache) Set(pkg, name string, it *namedInterface) {
 | |
| 	if _, ok := i.m[pkg]; !ok {
 | |
| 		i.m[pkg] = make(map[string]*namedInterface)
 | |
| 	}
 | |
| 	i.m[pkg][name] = it
 | |
| }
 | |
| 
 | |
| func (i *interfaceCache) Get(pkg, name string) *namedInterface {
 | |
| 	if _, ok := i.m[pkg]; !ok {
 | |
| 		return nil
 | |
| 	}
 | |
| 	return i.m[pkg][name]
 | |
| }
 | |
| 
 | |
| func (i *interfaceCache) GetASTIface(pkg, name string) *ast.InterfaceType {
 | |
| 	if _, ok := i.m[pkg]; !ok {
 | |
| 		return nil
 | |
| 	}
 | |
| 	it, ok := i.m[pkg][name]
 | |
| 	if !ok {
 | |
| 		return nil
 | |
| 	}
 | |
| 	return it.it
 | |
| }
 | |
| 
 | |
| type fileParser struct {
 | |
| 	fileSet            *token.FileSet
 | |
| 	imports            map[string]importedPackage // package name => imported package
 | |
| 	importedInterfaces *interfaceCache
 | |
| 	auxFiles           []*ast.File
 | |
| 	auxInterfaces      *interfaceCache
 | |
| 	srcDir             string
 | |
| 	excludeNamesSet    map[string]struct{}
 | |
| }
 | |
| 
 | |
| func (p *fileParser) errorf(pos token.Pos, format string, args ...any) error {
 | |
| 	ps := p.fileSet.Position(pos)
 | |
| 	format = "%s:%d:%d: " + format
 | |
| 	args = append([]any{ps.Filename, ps.Line, ps.Column}, args...)
 | |
| 	return fmt.Errorf(format, args...)
 | |
| }
 | |
| 
 | |
| func (p *fileParser) parseAuxFiles(auxFiles string) error {
 | |
| 	auxFiles = strings.TrimSpace(auxFiles)
 | |
| 	if auxFiles == "" {
 | |
| 		return nil
 | |
| 	}
 | |
| 	for _, kv := range strings.Split(auxFiles, ",") {
 | |
| 		parts := strings.SplitN(kv, "=", 2)
 | |
| 		if len(parts) != 2 {
 | |
| 			return fmt.Errorf("bad aux file spec: %v", kv)
 | |
| 		}
 | |
| 		pkg, fpath := parts[0], parts[1]
 | |
| 
 | |
| 		file, err := parser.ParseFile(p.fileSet, fpath, nil, 0)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		p.auxFiles = append(p.auxFiles, file)
 | |
| 		p.addAuxInterfacesFromFile(pkg, file)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (p *fileParser) addAuxInterfacesFromFile(pkg string, file *ast.File) {
 | |
| 	for ni := range iterInterfaces(file) {
 | |
| 		p.auxInterfaces.Set(pkg, ni.name.Name, ni)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // parseFile loads all file imports and auxiliary files import into the
 | |
| // fileParser, parses all file interfaces and returns package model.
 | |
| func (p *fileParser) parseFile(importPath string, file *ast.File) (*model.Package, error) {
 | |
| 	allImports, dotImports := importsOfFile(file)
 | |
| 	// Don't stomp imports provided by -imports. Those should take precedence.
 | |
| 	for pkg, pkgI := range allImports {
 | |
| 		if _, ok := p.imports[pkg]; !ok {
 | |
| 			p.imports[pkg] = pkgI
 | |
| 		}
 | |
| 	}
 | |
| 	// Add imports from auxiliary files, which might be needed for embedded interfaces.
 | |
| 	// Don't stomp any other imports.
 | |
| 	for _, f := range p.auxFiles {
 | |
| 		auxImports, _ := importsOfFile(f)
 | |
| 		for pkg, pkgI := range auxImports {
 | |
| 			if _, ok := p.imports[pkg]; !ok {
 | |
| 				p.imports[pkg] = pkgI
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	var is []*model.Interface
 | |
| 	for ni := range iterInterfaces(file) {
 | |
| 		if _, ok := p.excludeNamesSet[ni.name.String()]; ok {
 | |
| 			continue
 | |
| 		}
 | |
| 		i, err := p.parseInterface(ni.name.String(), importPath, ni)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		is = append(is, i)
 | |
| 	}
 | |
| 	return &model.Package{
 | |
| 		Name:       file.Name.String(),
 | |
| 		PkgPath:    importPath,
 | |
| 		Interfaces: is,
 | |
| 		DotImports: dotImports,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // parsePackage loads package specified by path, parses it and returns
 | |
| // a new fileParser with the parsed imports and interfaces.
 | |
| func (p *fileParser) parsePackage(path string) (*fileParser, error) {
 | |
| 	newP := &fileParser{
 | |
| 		fileSet:            token.NewFileSet(),
 | |
| 		imports:            make(map[string]importedPackage),
 | |
| 		importedInterfaces: newInterfaceCache(),
 | |
| 		auxInterfaces:      newInterfaceCache(),
 | |
| 		srcDir:             p.srcDir,
 | |
| 	}
 | |
| 
 | |
| 	var pkgs map[string]*ast.Package
 | |
| 	if imp, err := build.Import(path, newP.srcDir, build.FindOnly); err != nil {
 | |
| 		return nil, err
 | |
| 	} else if pkgs, err = parser.ParseDir(newP.fileSet, imp.Dir, nil, 0); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	for _, pkg := range pkgs {
 | |
| 		file := ast.MergePackageFiles(pkg, ast.FilterFuncDuplicates|ast.FilterUnassociatedComments|ast.FilterImportDuplicates)
 | |
| 		for ni := range iterInterfaces(file) {
 | |
| 			newP.importedInterfaces.Set(path, ni.name.Name, ni)
 | |
| 		}
 | |
| 		imports, _ := importsOfFile(file)
 | |
| 		for pkgName, pkgI := range imports {
 | |
| 			newP.imports[pkgName] = pkgI
 | |
| 		}
 | |
| 	}
 | |
| 	return newP, nil
 | |
| }
 | |
| 
 | |
| func (p *fileParser) constructInstParams(pkg string, params []*ast.Field, instParams []model.Type, embeddedInstParams []ast.Expr, tps map[string]model.Type) ([]model.Type, error) {
 | |
| 	pm := make(map[string]int)
 | |
| 	var i int
 | |
| 	for _, v := range params {
 | |
| 		for _, n := range v.Names {
 | |
| 			pm[n.Name] = i
 | |
| 			instParams = append(instParams, model.PredeclaredType(n.Name))
 | |
| 			i++
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	var runtimeInstParams []model.Type
 | |
| 	for _, instParam := range embeddedInstParams {
 | |
| 		switch t := instParam.(type) {
 | |
| 		case *ast.Ident:
 | |
| 			if idx, ok := pm[t.Name]; ok {
 | |
| 				runtimeInstParams = append(runtimeInstParams, instParams[idx])
 | |
| 				continue
 | |
| 			}
 | |
| 		}
 | |
| 		modelType, err := p.parseType(pkg, instParam, tps)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		runtimeInstParams = append(runtimeInstParams, modelType)
 | |
| 	}
 | |
| 
 | |
| 	return runtimeInstParams, nil
 | |
| }
 | |
| 
 | |
| func (p *fileParser) constructTps(it *namedInterface) (tps map[string]model.Type) {
 | |
| 	tps = make(map[string]model.Type)
 | |
| 	n := 0
 | |
| 	for _, tp := range it.typeParams {
 | |
| 		for _, tm := range tp.Names {
 | |
| 			tps[tm.Name] = nil
 | |
| 			if len(it.instTypes) != 0 {
 | |
| 				tps[tm.Name] = it.instTypes[n]
 | |
| 				n++
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return tps
 | |
| }
 | |
| 
 | |
| // parseInterface loads interface specified by pkg and name, parses it and returns
 | |
| // a new model with the parsed.
 | |
| func (p *fileParser) parseInterface(name, pkg string, it *namedInterface) (*model.Interface, error) {
 | |
| 	iface := &model.Interface{Name: name}
 | |
| 	tps := p.constructTps(it)
 | |
| 	tp, err := p.parseFieldList(pkg, it.typeParams, tps)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("unable to parse interface type parameters: %v", name)
 | |
| 	}
 | |
| 
 | |
| 	iface.TypeParams = tp
 | |
| 	for _, field := range it.it.Methods.List {
 | |
| 		var methods []*model.Method
 | |
| 		if methods, err = p.parseMethod(field, it, iface, pkg, tps); err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		for _, m := range methods {
 | |
| 			iface.AddMethod(m)
 | |
| 		}
 | |
| 	}
 | |
| 	return iface, nil
 | |
| }
 | |
| 
 | |
| func (p *fileParser) parseMethod(field *ast.Field, it *namedInterface, iface *model.Interface, pkg string, tps map[string]model.Type) ([]*model.Method, error) {
 | |
| 	// {} for git diff
 | |
| 	{
 | |
| 		switch v := field.Type.(type) {
 | |
| 		case *ast.FuncType:
 | |
| 			if nn := len(field.Names); nn != 1 {
 | |
| 				return nil, fmt.Errorf("expected one name for interface %v, got %d", iface.Name, nn)
 | |
| 			}
 | |
| 			m := &model.Method{
 | |
| 				Name: field.Names[0].String(),
 | |
| 			}
 | |
| 			var err error
 | |
| 			m.In, m.Variadic, m.Out, err = p.parseFunc(pkg, v, tps)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			return []*model.Method{m}, nil
 | |
| 		case *ast.Ident:
 | |
| 			// Embedded interface in this package.
 | |
| 			embeddedIfaceType := p.auxInterfaces.Get(pkg, v.String())
 | |
| 			if embeddedIfaceType == nil {
 | |
| 				embeddedIfaceType = p.importedInterfaces.Get(pkg, v.String())
 | |
| 			}
 | |
| 
 | |
| 			var embeddedIface *model.Interface
 | |
| 			if embeddedIfaceType != nil {
 | |
| 				var err error
 | |
| 				embeddedIfaceType.instTypes, err = p.constructInstParams(pkg, it.typeParams, it.instTypes, it.embeddedInstTypeParams, tps)
 | |
| 				if err != nil {
 | |
| 					return nil, err
 | |
| 				}
 | |
| 				embeddedIface, err = p.parseInterface(v.String(), pkg, embeddedIfaceType)
 | |
| 				if err != nil {
 | |
| 					return nil, err
 | |
| 				}
 | |
| 
 | |
| 			} else {
 | |
| 				// This is built-in error interface.
 | |
| 				if v.String() == model.ErrorInterface.Name {
 | |
| 					embeddedIface = &model.ErrorInterface
 | |
| 				} else {
 | |
| 					ip, err := p.parsePackage(pkg)
 | |
| 					if err != nil {
 | |
| 						return nil, p.errorf(v.Pos(), "could not parse package %s: %v", pkg, err)
 | |
| 					}
 | |
| 
 | |
| 					if embeddedIfaceType = ip.importedInterfaces.Get(pkg, v.String()); embeddedIfaceType == nil {
 | |
| 						return nil, p.errorf(v.Pos(), "unknown embedded interface %s.%s", pkg, v.String())
 | |
| 					}
 | |
| 
 | |
| 					embeddedIfaceType.instTypes, err = p.constructInstParams(pkg, it.typeParams, it.instTypes, it.embeddedInstTypeParams, tps)
 | |
| 					if err != nil {
 | |
| 						return nil, err
 | |
| 					}
 | |
| 					embeddedIface, err = ip.parseInterface(v.String(), pkg, embeddedIfaceType)
 | |
| 					if err != nil {
 | |
| 						return nil, err
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 			return embeddedIface.Methods, nil
 | |
| 		case *ast.SelectorExpr:
 | |
| 			// Embedded interface in another package.
 | |
| 			filePkg, sel := v.X.(*ast.Ident).String(), v.Sel.String()
 | |
| 			embeddedPkg, ok := p.imports[filePkg]
 | |
| 			if !ok {
 | |
| 				return nil, p.errorf(v.X.Pos(), "unknown package %s", filePkg)
 | |
| 			}
 | |
| 
 | |
| 			var embeddedIface *model.Interface
 | |
| 			var err error
 | |
| 			embeddedIfaceType := p.auxInterfaces.Get(filePkg, sel)
 | |
| 			if embeddedIfaceType != nil {
 | |
| 				embeddedIfaceType.instTypes, err = p.constructInstParams(pkg, it.typeParams, it.instTypes, it.embeddedInstTypeParams, tps)
 | |
| 				if err != nil {
 | |
| 					return nil, err
 | |
| 				}
 | |
| 				embeddedIface, err = p.parseInterface(sel, filePkg, embeddedIfaceType)
 | |
| 				if err != nil {
 | |
| 					return nil, err
 | |
| 				}
 | |
| 			} else {
 | |
| 				path := embeddedPkg.Path()
 | |
| 				parser := embeddedPkg.Parser()
 | |
| 				if parser == nil {
 | |
| 					ip, err := p.parsePackage(path)
 | |
| 					if err != nil {
 | |
| 						return nil, p.errorf(v.Pos(), "could not parse package %s: %v", path, err)
 | |
| 					}
 | |
| 					parser = ip
 | |
| 					p.imports[filePkg] = importedPkg{
 | |
| 						path:   embeddedPkg.Path(),
 | |
| 						parser: parser,
 | |
| 					}
 | |
| 				}
 | |
| 				if embeddedIfaceType = parser.importedInterfaces.Get(path, sel); embeddedIfaceType == nil {
 | |
| 					return nil, p.errorf(v.Pos(), "unknown embedded interface %s.%s", path, sel)
 | |
| 				}
 | |
| 
 | |
| 				embeddedIfaceType.instTypes, err = p.constructInstParams(pkg, it.typeParams, it.instTypes, it.embeddedInstTypeParams, tps)
 | |
| 				if err != nil {
 | |
| 					return nil, err
 | |
| 				}
 | |
| 				embeddedIface, err = parser.parseInterface(sel, path, embeddedIfaceType)
 | |
| 				if err != nil {
 | |
| 					return nil, err
 | |
| 				}
 | |
| 			}
 | |
| 			// TODO: apply shadowing rules.
 | |
| 			return embeddedIface.Methods, nil
 | |
| 		default:
 | |
| 			return p.parseGenericMethod(field, it, iface, pkg, tps)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (p *fileParser) parseFunc(pkg string, f *ast.FuncType, tps map[string]model.Type) (inParam []*model.Parameter, variadic *model.Parameter, outParam []*model.Parameter, err error) {
 | |
| 	if f.Params != nil {
 | |
| 		regParams := f.Params.List
 | |
| 		if isVariadic(f) {
 | |
| 			n := len(regParams)
 | |
| 			varParams := regParams[n-1:]
 | |
| 			regParams = regParams[:n-1]
 | |
| 			vp, err := p.parseFieldList(pkg, varParams, tps)
 | |
| 			if err != nil {
 | |
| 				return nil, nil, nil, p.errorf(varParams[0].Pos(), "failed parsing variadic argument: %v", err)
 | |
| 			}
 | |
| 			variadic = vp[0]
 | |
| 		}
 | |
| 		inParam, err = p.parseFieldList(pkg, regParams, tps)
 | |
| 		if err != nil {
 | |
| 			return nil, nil, nil, p.errorf(f.Pos(), "failed parsing arguments: %v", err)
 | |
| 		}
 | |
| 	}
 | |
| 	if f.Results != nil {
 | |
| 		outParam, err = p.parseFieldList(pkg, f.Results.List, tps)
 | |
| 		if err != nil {
 | |
| 			return nil, nil, nil, p.errorf(f.Pos(), "failed parsing returns: %v", err)
 | |
| 		}
 | |
| 	}
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func (p *fileParser) parseFieldList(pkg string, fields []*ast.Field, tps map[string]model.Type) ([]*model.Parameter, error) {
 | |
| 	nf := 0
 | |
| 	for _, f := range fields {
 | |
| 		nn := len(f.Names)
 | |
| 		if nn == 0 {
 | |
| 			nn = 1 // anonymous parameter
 | |
| 		}
 | |
| 		nf += nn
 | |
| 	}
 | |
| 	if nf == 0 {
 | |
| 		return nil, nil
 | |
| 	}
 | |
| 	ps := make([]*model.Parameter, nf)
 | |
| 	i := 0 // destination index
 | |
| 	for _, f := range fields {
 | |
| 		t, err := p.parseType(pkg, f.Type, tps)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		if len(f.Names) == 0 {
 | |
| 			// anonymous arg
 | |
| 			ps[i] = &model.Parameter{Type: t}
 | |
| 			i++
 | |
| 			continue
 | |
| 		}
 | |
| 		for _, name := range f.Names {
 | |
| 			ps[i] = &model.Parameter{Name: name.Name, Type: t}
 | |
| 			i++
 | |
| 		}
 | |
| 	}
 | |
| 	return ps, nil
 | |
| }
 | |
| 
 | |
| func (p *fileParser) parseType(pkg string, typ ast.Expr, tps map[string]model.Type) (model.Type, error) {
 | |
| 	switch v := typ.(type) {
 | |
| 	case *ast.ArrayType:
 | |
| 		ln := -1
 | |
| 		if v.Len != nil {
 | |
| 			value, err := p.parseArrayLength(v.Len)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			ln, err = strconv.Atoi(value)
 | |
| 			if err != nil {
 | |
| 				return nil, p.errorf(v.Len.Pos(), "bad array size: %v", err)
 | |
| 			}
 | |
| 		}
 | |
| 		t, err := p.parseType(pkg, v.Elt, tps)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		return &model.ArrayType{Len: ln, Type: t}, nil
 | |
| 	case *ast.ChanType:
 | |
| 		t, err := p.parseType(pkg, v.Value, tps)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		var dir model.ChanDir
 | |
| 		if v.Dir == ast.SEND {
 | |
| 			dir = model.SendDir
 | |
| 		}
 | |
| 		if v.Dir == ast.RECV {
 | |
| 			dir = model.RecvDir
 | |
| 		}
 | |
| 		return &model.ChanType{Dir: dir, Type: t}, nil
 | |
| 	case *ast.Ellipsis:
 | |
| 		// assume we're parsing a variadic argument
 | |
| 		return p.parseType(pkg, v.Elt, tps)
 | |
| 	case *ast.FuncType:
 | |
| 		in, variadic, out, err := p.parseFunc(pkg, v, tps)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		return &model.FuncType{In: in, Out: out, Variadic: variadic}, nil
 | |
| 	case *ast.Ident:
 | |
| 		it, ok := tps[v.Name]
 | |
| 		if v.IsExported() && !ok {
 | |
| 			// `pkg` may be an aliased imported pkg
 | |
| 			// if so, patch the import w/ the fully qualified import
 | |
| 			maybeImportedPkg, ok := p.imports[pkg]
 | |
| 			if ok {
 | |
| 				pkg = maybeImportedPkg.Path()
 | |
| 			}
 | |
| 			// assume type in this package
 | |
| 			return &model.NamedType{Package: pkg, Type: v.Name}, nil
 | |
| 		}
 | |
| 		if ok && it != nil {
 | |
| 			return it, nil
 | |
| 		}
 | |
| 		// assume predeclared type
 | |
| 		return model.PredeclaredType(v.Name), nil
 | |
| 	case *ast.InterfaceType:
 | |
| 		if v.Methods != nil && len(v.Methods.List) > 0 {
 | |
| 			return nil, p.errorf(v.Pos(), "can't handle non-empty unnamed interface types")
 | |
| 		}
 | |
| 		return model.PredeclaredType("any"), nil
 | |
| 	case *ast.MapType:
 | |
| 		key, err := p.parseType(pkg, v.Key, tps)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		value, err := p.parseType(pkg, v.Value, tps)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		return &model.MapType{Key: key, Value: value}, nil
 | |
| 	case *ast.SelectorExpr:
 | |
| 		pkgName := v.X.(*ast.Ident).String()
 | |
| 		pkg, ok := p.imports[pkgName]
 | |
| 		if !ok {
 | |
| 			return nil, p.errorf(v.Pos(), "unknown package %q", pkgName)
 | |
| 		}
 | |
| 		return &model.NamedType{Package: pkg.Path(), Type: v.Sel.String()}, nil
 | |
| 	case *ast.StarExpr:
 | |
| 		t, err := p.parseType(pkg, v.X, tps)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		return &model.PointerType{Type: t}, nil
 | |
| 	case *ast.StructType:
 | |
| 		if v.Fields != nil && len(v.Fields.List) > 0 {
 | |
| 			return nil, p.errorf(v.Pos(), "can't handle non-empty unnamed struct types")
 | |
| 		}
 | |
| 		return model.PredeclaredType("struct{}"), nil
 | |
| 	case *ast.ParenExpr:
 | |
| 		return p.parseType(pkg, v.X, tps)
 | |
| 	default:
 | |
| 		mt, err := p.parseGenericType(pkg, typ, tps)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		if mt == nil {
 | |
| 			break
 | |
| 		}
 | |
| 		return mt, nil
 | |
| 	}
 | |
| 
 | |
| 	return nil, fmt.Errorf("don't know how to parse type %T", typ)
 | |
| }
 | |
| 
 | |
| func (p *fileParser) parseArrayLength(expr ast.Expr) (string, error) {
 | |
| 	switch val := expr.(type) {
 | |
| 	case (*ast.BasicLit):
 | |
| 		return val.Value, nil
 | |
| 	case (*ast.Ident):
 | |
| 		// when the length is a const defined locally
 | |
| 		return val.Obj.Decl.(*ast.ValueSpec).Values[0].(*ast.BasicLit).Value, nil
 | |
| 	case (*ast.SelectorExpr):
 | |
| 		// when the length is a const defined in an external package
 | |
| 		usedPkg, err := importer.Default().Import(fmt.Sprintf("%s", val.X))
 | |
| 		if err != nil {
 | |
| 			return "", p.errorf(expr.Pos(), "unknown package in array length: %v", err)
 | |
| 		}
 | |
| 		ev, err := types.Eval(token.NewFileSet(), usedPkg, token.NoPos, val.Sel.Name)
 | |
| 		if err != nil {
 | |
| 			return "", p.errorf(expr.Pos(), "unknown constant in array length: %v", err)
 | |
| 		}
 | |
| 		return ev.Value.String(), nil
 | |
| 	case (*ast.ParenExpr):
 | |
| 		return p.parseArrayLength(val.X)
 | |
| 	case (*ast.BinaryExpr):
 | |
| 		x, err := p.parseArrayLength(val.X)
 | |
| 		if err != nil {
 | |
| 			return "", err
 | |
| 		}
 | |
| 		y, err := p.parseArrayLength(val.Y)
 | |
| 		if err != nil {
 | |
| 			return "", err
 | |
| 		}
 | |
| 		biExpr := fmt.Sprintf("%s%v%s", x, val.Op, y)
 | |
| 		tv, err := types.Eval(token.NewFileSet(), nil, token.NoPos, biExpr)
 | |
| 		if err != nil {
 | |
| 			return "", p.errorf(expr.Pos(), "invalid expression in array length: %v", err)
 | |
| 		}
 | |
| 		return tv.Value.String(), nil
 | |
| 	default:
 | |
| 		return "", p.errorf(expr.Pos(), "invalid expression in array length: %v", val)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // importsOfFile returns a map of package name to import path
 | |
| // of the imports in file.
 | |
| func importsOfFile(file *ast.File) (normalImports map[string]importedPackage, dotImports []string) {
 | |
| 	var importPaths []string
 | |
| 	for _, is := range file.Imports {
 | |
| 		if is.Name != nil {
 | |
| 			continue
 | |
| 		}
 | |
| 		importPath := is.Path.Value[1 : len(is.Path.Value)-1] // remove quotes
 | |
| 		importPaths = append(importPaths, importPath)
 | |
| 	}
 | |
| 	packagesName := createPackageMap(importPaths)
 | |
| 	normalImports = make(map[string]importedPackage)
 | |
| 	dotImports = make([]string, 0)
 | |
| 	for _, is := range file.Imports {
 | |
| 		var pkgName string
 | |
| 		importPath := is.Path.Value[1 : len(is.Path.Value)-1] // remove quotes
 | |
| 
 | |
| 		if is.Name != nil {
 | |
| 			// Named imports are always certain.
 | |
| 			if is.Name.Name == "_" {
 | |
| 				continue
 | |
| 			}
 | |
| 			pkgName = is.Name.Name
 | |
| 		} else {
 | |
| 			pkg, ok := packagesName[importPath]
 | |
| 			if !ok {
 | |
| 				// Fallback to import path suffix. Note that this is uncertain.
 | |
| 				_, last := path.Split(importPath)
 | |
| 				// If the last path component has dots, the first dot-delimited
 | |
| 				// field is used as the name.
 | |
| 				pkgName = strings.SplitN(last, ".", 2)[0]
 | |
| 			} else {
 | |
| 				pkgName = pkg
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if pkgName == "." {
 | |
| 			dotImports = append(dotImports, importPath)
 | |
| 		} else {
 | |
| 			if pkg, ok := normalImports[pkgName]; ok {
 | |
| 				switch p := pkg.(type) {
 | |
| 				case duplicateImport:
 | |
| 					normalImports[pkgName] = duplicateImport{
 | |
| 						name:       p.name,
 | |
| 						duplicates: append([]string{importPath}, p.duplicates...),
 | |
| 					}
 | |
| 				case importedPkg:
 | |
| 					normalImports[pkgName] = duplicateImport{
 | |
| 						name:       pkgName,
 | |
| 						duplicates: []string{p.path, importPath},
 | |
| 					}
 | |
| 				}
 | |
| 			} else {
 | |
| 				normalImports[pkgName] = importedPkg{path: importPath}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return
 | |
| }
 | |
| 
 | |
| type namedInterface struct {
 | |
| 	name                   *ast.Ident
 | |
| 	it                     *ast.InterfaceType
 | |
| 	typeParams             []*ast.Field
 | |
| 	embeddedInstTypeParams []ast.Expr
 | |
| 	instTypes              []model.Type
 | |
| }
 | |
| 
 | |
| // Create an iterator over all interfaces in file.
 | |
| func iterInterfaces(file *ast.File) <-chan *namedInterface {
 | |
| 	ch := make(chan *namedInterface)
 | |
| 	go func() {
 | |
| 		for _, decl := range file.Decls {
 | |
| 			gd, ok := decl.(*ast.GenDecl)
 | |
| 			if !ok || gd.Tok != token.TYPE {
 | |
| 				continue
 | |
| 			}
 | |
| 			for _, spec := range gd.Specs {
 | |
| 				ts, ok := spec.(*ast.TypeSpec)
 | |
| 				if !ok {
 | |
| 					continue
 | |
| 				}
 | |
| 				it, ok := ts.Type.(*ast.InterfaceType)
 | |
| 				if !ok {
 | |
| 					continue
 | |
| 				}
 | |
| 
 | |
| 				ch <- &namedInterface{name: ts.Name, it: it, typeParams: getTypeSpecTypeParams(ts)}
 | |
| 			}
 | |
| 		}
 | |
| 		close(ch)
 | |
| 	}()
 | |
| 	return ch
 | |
| }
 | |
| 
 | |
| // isVariadic returns whether the function is variadic.
 | |
| func isVariadic(f *ast.FuncType) bool {
 | |
| 	nargs := len(f.Params.List)
 | |
| 	if nargs == 0 {
 | |
| 		return false
 | |
| 	}
 | |
| 	_, ok := f.Params.List[nargs-1].Type.(*ast.Ellipsis)
 | |
| 	return ok
 | |
| }
 | |
| 
 | |
| // packageNameOfDir get package import path via dir
 | |
| func packageNameOfDir(srcDir string) (string, error) {
 | |
| 	files, err := os.ReadDir(srcDir)
 | |
| 	if err != nil {
 | |
| 		log.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	var goFilePath string
 | |
| 	for _, file := range files {
 | |
| 		if !file.IsDir() && strings.HasSuffix(file.Name(), ".go") {
 | |
| 			goFilePath = file.Name()
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	if goFilePath == "" {
 | |
| 		return "", fmt.Errorf("go source file not found %s", srcDir)
 | |
| 	}
 | |
| 
 | |
| 	packageImport, err := parsePackageImport(srcDir)
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 	return packageImport, nil
 | |
| }
 | |
| 
 | |
| var errOutsideGoPath = errors.New("source directory is outside GOPATH")
 |