Compare commits

...
2 Commits
Author SHA1 Message Date
Mzack9999 43ed8eb8fc Merge pull request #8100 from projectdiscovery/dwisiswant0/fix/js-tsgen/report-package-load-errors-instead-of-ignoring
fix(js/tsgen): report package load errors instead of ignoring them
2026-11-09 18:12:11 +01:00
Dwi Siswanto ec6794c2b4 fix(js/tsgen): report package load errors instead of ignoring them
packages.Load records parse, type, and list errors in Package.Errors
and still returns a nil error. NewEntityParser and loadPackage checked
only the returned error, so tsgen accepted packages that do not
compile. Parse then panicked in extractParameters on the recovered
syntax tree, or looked up types in an empty package and reported a
misleading "type not found" error.

Both call sites now load through loadPackage, which returns the
package errors with the package ID and file positions.

Fixes: #8098
Signed-off-by: Dwi Siswanto <[email protected]>
2026-11-09 23:29:03 +07:00
2 changed files with 109 additions and 12 deletions

No files matched your search

+23 -12
View File
@@ -38,14 +38,11 @@ func NewEntityParser(dir string) (*EntityParser, error) {
return parser.ParseFile(fset, filename, src, parser.ParseComments)
},
}
pkgs, err := packages.Load(cfg, ".")
pkg, err := loadPackage(cfg, ".")
if err != nil {
return nil, err
}
if len(pkgs) == 0 {
return nil, errors.New("no packages found")
}
pkg := pkgs[0]
return &EntityParser{
syntax: pkg.Syntax,
@@ -513,6 +510,7 @@ func (p *EntityParser) extractVarsNConstants() {
// loadImportedPackages loads all imported packages
func (p *EntityParser) loadImportedPackages() error {
cfg := &packages.Config{Mode: packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo}
// get all import statements
// iterate over all imports
for _, file := range p.syntax {
@@ -522,7 +520,7 @@ func (p *EntityParser) loadImportedPackages() error {
// remove the quotes from the path
path = path[1 : len(path)-1]
// load the package
pkg, err := loadPackage(path)
pkg, err := loadPackage(cfg, path)
if err != nil {
return err
}
@@ -543,18 +541,31 @@ func (p *EntityParser) loadImportedPackages() error {
return nil
}
// Load the package containing the type definition
// TODO: we don't support named imports yet
func loadPackage(pkgPath string) (*packages.Package, error) {
cfg := &packages.Config{Mode: packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo}
pkgs, err := packages.Load(cfg, pkgPath)
// loadPackage loads the package that pattern names.
//
// packages.Load reports parse, type, and list errors in Package.Errors
// rather than in its returned error, so loadPackage converts them into an
// error. The parser assumes a complete syntax tree and type information.
func loadPackage(cfg *packages.Config, pattern string) (*packages.Package, error) {
pkgs, err := packages.Load(cfg, pattern)
if err != nil {
return nil, err
}
if len(pkgs) == 0 {
return nil, errors.New("no packages found")
}
return pkgs[0], nil
pkg := pkgs[0]
if len(pkg.Errors) > 0 {
errs := make([]error, len(pkg.Errors))
for i, e := range pkg.Errors {
errs[i] = e
}
return nil, fmt.Errorf("package %s: %w", pkg.ID, errors.Join(errs...))
}
return pkg, nil
}
// exprToString converts an expression to a string
+86
View File
@@ -0,0 +1,86 @@
package tsgen
import (
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
"golang.org/x/tools/go/packages"
)
// writeModule writes src as the only package of a new Go module and
// returns the module directory.
func writeModule(t *testing.T, src string) string {
t.Helper()
dir := t.TempDir()
files := map[string]string{
"go.mod": "module example.com/tsgentest\n\ngo 1.21\n",
"example.go": src,
}
for name, content := range files {
require.NoError(t, os.WriteFile(filepath.Join(dir, name), []byte(content), 0o600))
}
return dir
}
func TestNewEntityParser(t *testing.T) {
const src = `package example
import "strings"
// Upper returns s in upper case.
func Upper(s string) string { return strings.ToUpper(s) }
`
p, err := NewEntityParser(writeModule(t, src))
require.NoError(t, err)
require.NoError(t, p.Parse())
entities := p.GetEntities()
names := make([]string, 0, len(entities))
for _, e := range entities {
names = append(names, e.Name)
}
require.Contains(t, names, "Upper")
}
func TestNewEntityParserPackageErrors(t *testing.T) {
tests := []struct {
name string
src string
}{
{name: "syntax error", src: "package example\n\nfunc Broken( {\n\treturn 42\n}\n"},
{name: "type error", src: "package example\n\nfunc Broken() int { return undefined }\n"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := NewEntityParser(writeModule(t, tt.src))
require.ErrorContains(t, err, "example.go:")
})
}
}
func TestLoadImportedPackagesMissing(t *testing.T) {
const src = `package example; import "example.com/tsgentest/missing"`
file, err := parser.ParseFile(token.NewFileSet(), "example.go", src, parser.ImportsOnly)
require.NoError(t, err)
p := &EntityParser{
syntax: []*ast.File{file},
imports: map[string]*packages.Package{},
}
var pkgErr packages.Error
require.ErrorAs(t, p.loadImportedPackages(), &pkgErr)
require.Contains(t, pkgErr.Msg, "example.com/tsgentest/missing")
}