mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-07 08:10:17 +08:00

* Support variadic arguments and slice, pointer types * Fix computation of type namespaces * Improve comments and general formatting * Set default values correctly for composite types * Add templates for bindings Additionally: * fixes generation of tuple return type * improves imports and namespacing in JS mode * general cleanup of generated code * Simplify import list construction * Refactor type generation code Improves support for unknown types (encoded as any) and maps (using Typescript index signatures) * Support slices with pointer elements * Match encoding/json behaviour in struct parser * Update tests and example * Add tests for complex method signatures and json tag parsing * Add test `function_multiple_files` * Attempt looking up idents with missing denotation * Update test data * fix quoted bool field * Test quoted booleans * Delete old parser code * Remove old test data * Update bindgen flags * Makes call by ID the default * Add package loading code * Add static analyser * Temporarily ignore binding generation code * Add complex slice expressions test * Fix variable reference analysis * Unwrap casts to interface types * Complete code comments * Refactor static analyser * Restrict options struct usage * Update tests * Fix method selector sink and source processing * Improve Set API * Add package info collector * Rename analyser package to analyse * Improve template functions * Add index file templates * Add glue code for binding generation * Refactor collection and rendering code * Implement binding generator * Implement global index generation * Improve marshaler and alias handling * Use package path in binding calls by name * Implement model collection and rendering * Fix wrong exit condition in analyser * Fix enum rendering * Generate shortcuts for all packages. * Implement generator tests * Ignore non-pointer bound types * Treat main package specially * Compute stats * Plug new API into generate command * Support all named types * Update JS runtime * Report dual role types * Remove go1.22 syntax * Fix type assertion in TS bindings * encoding/json compliance for arrays and slices * Ignore got files in testdata * Cleanup type rendering mechanism * Update JS runtime * Implement generic models * Add missing field in renderer initialisation * Improve generic creation code * Add generic model test * Add error reporting infrastructure * Support configurable file names * Detect file naming collisions * Print final error report * New shortcut file structure + collision detection * Update test layout and data * Autoconfiguration for analyser tests * Live progress reporting * Update code comments * Fix model doc rendering * Simplify name resolution * Add test for out of tree types * Fix generic creation code * Fix potential collisions between methods and models * Fix generic class alias rendering * Report model discovery in debug mode * Add interface mode for JS * Collect interface method comments * Add interface methods test * Unwrap generic instantiations in method receivers * Fix rendering of nullable types in interface mode * Fix rendering of class aliases * Expose promise cancel method to typescript * Update test data * Update binding example * Fix rendering of aliased quoted type params * Move to strongly typed bindings * Implement lightweight analyser * Update test cases * Update binding example * Add complex instantiation test * Load full dependency tree * Rewrite collector * Update renderer to match new collector * Update generator to match new collector * Update test data * Update binding example * Configure includes and injections by language * Improve system path resolution * Support rich conditions in inject/include directives * Fix error handling in Generator.Generate * Retrieve compiled go file paths from fileset * Do not rely on struct info in struct flattening algorithm * Fix doc comment for findDeclaraion * Fix bugs in embedded field handling * Fix bugs and comments in package collection * Remove useless fields from ServiceInfo * Fix empty line at the beginning of TS indexes * Remove global index and shortcuts * Remove generation tests for individual packages * Enforce lower-case file names * Update test data * Improve error reporting * Update binding example * Reintroduce go1.22 syntax * Improve relative import path computation * Improve alias support * Add alias test * Update test data * Remove no services error * Rename global analyser test * Add workaround and test for bug in typeutil.Map * Update test data * Do not split fully qualified names * Update typeutil package and remove workaround * Unify alias/named type handling * Fix rendering of generic named class aliases * Fix rendering of array types * Minor tweaks and cleanups * Rmove namespaced export construct * Update test data * Update binding example * Break type cycles * Fix typo in comment * Fix creation code for cyclic types * Fix type of variadic params in interface mode * Update test data * Fix bad whitespace * Refactor type assertions inside bound methods * Update test data * Rename field application.Options.Bind to Services * Rename parser package to generator * Update binding example * Update test data * Update generator readme * Add typescript test harness * Move test output to new subfolder * Fix code generation bugs * Use .js extensions in TS mode imports * Update test data * Revert default generator output dir to frontend/bindings * Bump runtime package version * Update templates * Update changelog * Improve newline handling --------- Co-authored-by: Andreas Bichinger <andreas.bichinger@gmail.com>
294 lines
8.1 KiB
Go
294 lines
8.1 KiB
Go
package collect
|
|
|
|
import (
|
|
"cmp"
|
|
"go/ast"
|
|
"go/token"
|
|
"go/types"
|
|
"path/filepath"
|
|
"slices"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
"golang.org/x/tools/go/packages"
|
|
)
|
|
|
|
// PackageInfo records information about a package.
|
|
//
|
|
// Read accesses to fields Path, Name, Types, TypesInfo, Fset
|
|
// are safe at any time without any synchronisation.
|
|
//
|
|
// Read accesses to all other fields are only safe
|
|
// if a call to [PackageInfo.Collect] has completed before the access,
|
|
// for example by calling it in the accessing goroutine
|
|
// or before spawning the accessing goroutine.
|
|
//
|
|
// Concurrent write accesses are only allowed through the provided methods.
|
|
type PackageInfo struct {
|
|
// Path holds the canonical path of the described package.
|
|
Path string
|
|
|
|
// Name holds the import name of the described package.
|
|
Name string
|
|
|
|
// Types and TypesInfo hold type information for this package.
|
|
Types *types.Package
|
|
TypesInfo *types.Info
|
|
|
|
// Fset holds the FileSet that was used to parse this package.
|
|
Fset *token.FileSet
|
|
|
|
// Files holds parsed files for this package,
|
|
// ordered by start position to support binary search.
|
|
Files []*ast.File
|
|
|
|
// Docs holds package doc comments.
|
|
Docs []*ast.CommentGroup
|
|
|
|
// Includes holds a list of additional files to include
|
|
// with the generated bindings.
|
|
// It maps file names to their paths on disk.
|
|
Includes map[string]string
|
|
|
|
// Injections holds a list of code lines to be injected
|
|
// into the package index file.
|
|
Injections []string
|
|
|
|
// services records service types that have to be generated for this package.
|
|
// We rely upon [sync.Map] for atomic swapping support.
|
|
// Keys are *types.TypeName, values are *ServiceInfo.
|
|
services sync.Map
|
|
|
|
// models records model types that have to be generated for this package.
|
|
// We rely upon [sync.Map] for atomic swapping support.
|
|
// Keys are *types.TypeName, values are *ModelInfo.
|
|
models sync.Map
|
|
|
|
// stats caches statistics about this package.
|
|
stats atomic.Pointer[Stats]
|
|
|
|
collector *Collector
|
|
once sync.Once
|
|
}
|
|
|
|
func newPackageInfo(pkg *packages.Package, collector *Collector) *PackageInfo {
|
|
return &PackageInfo{
|
|
Path: pkg.PkgPath,
|
|
Name: pkg.Name,
|
|
|
|
Types: pkg.Types,
|
|
TypesInfo: pkg.TypesInfo,
|
|
|
|
Fset: pkg.Fset,
|
|
Files: pkg.Syntax,
|
|
|
|
collector: collector,
|
|
}
|
|
}
|
|
|
|
// Package retrieves the the unique [PackageInfo] instance, if any,
|
|
// associated to the given package object within a Collector.
|
|
//
|
|
// Package is safe for concurrent use.
|
|
func (collector *Collector) Package(pkg *types.Package) *PackageInfo {
|
|
return collector.pkgs[pkg]
|
|
}
|
|
|
|
// Iterate calls yield sequentially for each [PackageInfo] instance
|
|
// registered with the collector. If yield returns false,
|
|
// Iterate stops the iteration.
|
|
//
|
|
// Iterate is safe for concurrent use.
|
|
func (collector *Collector) Iterate(yield func(pkg *PackageInfo) bool) {
|
|
for _, pkg := range collector.pkgs {
|
|
if !yield(pkg) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// Stats returns cached statistics for this package.
|
|
// If [PackageInfo.Index] has not been called yet, it returns nil.
|
|
//
|
|
// Stats is safe for unsynchronised concurrent calls.
|
|
func (info *PackageInfo) Stats() *Stats {
|
|
return info.stats.Load()
|
|
}
|
|
|
|
// Collect gathers information about the package described by its receiver.
|
|
// It can be called concurrently by multiple goroutines;
|
|
// the computation will be performed just once.
|
|
//
|
|
// Collect returns the receiver for chaining.
|
|
// It is safe to call Collect with nil receiver.
|
|
//
|
|
// After Collect returns, the calling goroutine and all goroutines
|
|
// it might spawn afterwards are free to access
|
|
// the receiver's fields indefinitely.
|
|
func (info *PackageInfo) Collect() *PackageInfo {
|
|
if info == nil {
|
|
return nil
|
|
}
|
|
|
|
info.once.Do(func() {
|
|
collector := info.collector
|
|
|
|
// Sort files by source position.
|
|
if !slices.IsSortedFunc(info.Files, compareAstFiles) {
|
|
info.Files = slices.Clone(info.Files)
|
|
slices.SortFunc(info.Files, compareAstFiles)
|
|
}
|
|
|
|
// Collect docs and parse directives.
|
|
for _, file := range info.Files {
|
|
if file.Doc == nil {
|
|
continue
|
|
}
|
|
|
|
info.Docs = append(info.Docs, file.Doc)
|
|
|
|
// Retrieve file directory.
|
|
pos := info.Fset.Position(file.Pos())
|
|
if !pos.IsValid() {
|
|
collector.logger.Errorf(
|
|
"package %s: found AST file with unknown path: `wails:include` directives from that file will be ignored",
|
|
info.Path,
|
|
)
|
|
}
|
|
dir := filepath.Dir(pos.Filename)
|
|
|
|
// Parse directives.
|
|
if info.Includes == nil {
|
|
info.Includes = make(map[string]string)
|
|
}
|
|
for _, comment := range file.Doc.List {
|
|
switch {
|
|
case IsDirective(comment.Text, "inject"):
|
|
// Check condition.
|
|
line, cond, err := ParseCondition(ParseDirective(comment.Text, "inject"))
|
|
if err != nil {
|
|
collector.logger.Errorf(
|
|
"%s: in `wails:inject` directive: %v",
|
|
info.Fset.Position(comment.Pos()),
|
|
err,
|
|
)
|
|
continue
|
|
}
|
|
|
|
if !cond.Satisfied(collector.options) {
|
|
continue
|
|
}
|
|
|
|
// Record injected line.
|
|
info.Injections = append(info.Injections, line)
|
|
|
|
case pos.IsValid() && IsDirective(comment.Text, "include"):
|
|
// Check condition.
|
|
pattern, cond, err := ParseCondition(ParseDirective(comment.Text, "include"))
|
|
if err != nil {
|
|
collector.logger.Errorf(
|
|
"%s: in `wails:include` directive: %v",
|
|
info.Fset.Position(comment.Pos()),
|
|
err,
|
|
)
|
|
continue
|
|
}
|
|
|
|
if !cond.Satisfied(collector.options) {
|
|
continue
|
|
}
|
|
|
|
// Collect matching files.
|
|
paths, err := filepath.Glob(filepath.Join(dir, pattern))
|
|
if err != nil {
|
|
collector.logger.Errorf(
|
|
"%s: invalid pattern '%s' in `wails:include` directive: %v",
|
|
info.Fset.Position(comment.Pos()),
|
|
pattern,
|
|
err,
|
|
)
|
|
continue
|
|
} else if len(paths) == 0 {
|
|
collector.logger.Warningf(
|
|
"%s: pattern '%s' in `wails:include` directive matched no files",
|
|
info.Fset.Position(comment.Pos()),
|
|
pattern,
|
|
)
|
|
continue
|
|
}
|
|
|
|
// Announce and record matching files.
|
|
for _, path := range paths {
|
|
name := strings.ToLower(filepath.Base(path))
|
|
if old, ok := info.Includes[name]; ok {
|
|
collector.logger.Errorf(
|
|
"%s: duplicate included file name '%s' in package %s; old path: '%s'; new path: '%s'",
|
|
info.Fset.Position(comment.Pos()),
|
|
name,
|
|
info.Path,
|
|
old,
|
|
path,
|
|
)
|
|
continue
|
|
}
|
|
|
|
collector.logger.Debugf(
|
|
"including file '%s' as '%s' in package %s",
|
|
path,
|
|
name,
|
|
info.Path,
|
|
)
|
|
|
|
info.Includes[name] = path
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
return info
|
|
}
|
|
|
|
// recordService adds the given service type object
|
|
// to the set of bindings generated for this package.
|
|
// It returns the unique [ServiceInfo] instance associated
|
|
// with the given type object.
|
|
//
|
|
// It is an error to pass in here a type whose parent package
|
|
// is not the one described by the receiver.
|
|
//
|
|
// recordService is safe for unsynchronised concurrent calls.
|
|
func (info *PackageInfo) recordService(obj *types.TypeName) *ServiceInfo {
|
|
// Fetch current value, then add if not already present.
|
|
service, _ := info.services.Load(obj)
|
|
if service == nil {
|
|
service, _ = info.services.LoadOrStore(obj, newServiceInfo(info.collector, obj))
|
|
}
|
|
return service.(*ServiceInfo)
|
|
}
|
|
|
|
// recordModel adds the given model type object
|
|
// to the set of models generated for this package.
|
|
// It returns the unique [ModelInfo] instance associated
|
|
// with the given type object. The present result is true
|
|
// if the model was already registered.
|
|
//
|
|
// It is an error to pass in here a type whose parent package
|
|
// is not the one described by the receiver.
|
|
//
|
|
// recordModel is safe for unsynchronised concurrent calls.
|
|
func (info *PackageInfo) recordModel(obj *types.TypeName) (model *ModelInfo, present bool) {
|
|
// Fetch current value, then add if not already present.
|
|
imodel, present := info.models.Load(obj)
|
|
if imodel == nil {
|
|
imodel, present = info.models.LoadOrStore(obj, newModelInfo(info.collector, obj))
|
|
}
|
|
return imodel.(*ModelInfo), present
|
|
}
|
|
|
|
// compareAstFiles compares two AST files by starting position.
|
|
func compareAstFiles(f1 *ast.File, f2 *ast.File) int {
|
|
return cmp.Compare(f1.FileStart, f2.FileStart)
|
|
}
|