Files
Patricio Whittingslow ca2694c5d9 add tgdoc tool
2026-02-25 12:21:09 -03:00

125 lines
2.7 KiB
Go

package main
import (
"bufio"
"bytes"
"fmt"
"go/ast"
"go/build/constraint"
"go/doc"
"go/parser"
"go/token"
"os"
"path/filepath"
"strings"
)
// buildTagSet creates the full set of tags that should evaluate to true
// for a given target, matching TinyGo's Config.BuildTags() behavior.
func buildTagSet(t Target) map[string]bool {
tags := make(map[string]bool)
for _, tag := range t.BuildTags {
tags[tag] = true
}
if t.GOOS != "" {
tags[t.GOOS] = true
}
if t.GOARCH != "" {
tags[t.GOARCH] = true
}
tags["tinygo"] = true
tags["purego"] = true
tags["osusergo"] = true
tags["math_big_pure_go"] = true
if t.GC != "" {
tags["gc."+t.GC] = true
}
if t.Scheduler != "" {
tags["scheduler."+t.Scheduler] = true
}
if t.Serial != "" {
tags["serial."+t.Serial] = true
}
// Go version tags — TinyGo currently tracks Go 1.22.
for i := 1; i <= 22; i++ {
tags[fmt.Sprintf("go1.%d", i)] = true
}
return tags
}
// extractBuildConstraint reads the file header (before the package clause)
// looking for a //go:build constraint line.
func extractBuildConstraint(data []byte) (constraint.Expr, error) {
scanner := bufio.NewScanner(bytes.NewReader(data))
for scanner.Scan() {
line := scanner.Text()
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, "package ") {
break
}
if constraint.IsGoBuild(trimmed) {
return constraint.Parse(trimmed)
}
}
return nil, nil
}
// ExtractDocs filters .go files in pkgDir by the target's build tags,
// parses them, and returns a *doc.Package via go/doc.NewFromFiles.
func ExtractDocs(t Target, pkgDir string, allDecls bool) (*doc.Package, *token.FileSet, error) {
tags := buildTagSet(t)
entries, err := os.ReadDir(pkgDir)
if err != nil {
return nil, nil, err
}
fset := token.NewFileSet()
var files []*ast.File
for _, e := range entries {
name := e.Name()
if !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") {
continue
}
path := filepath.Join(pkgDir, name)
data, err := os.ReadFile(path)
if err != nil {
return nil, nil, err
}
expr, err := extractBuildConstraint(data)
if err != nil {
continue // skip files with unparseable constraints
}
if expr != nil && !expr.Eval(func(tag string) bool { return tags[tag] }) {
continue
}
f, err := parser.ParseFile(fset, path, data, parser.ParseComments)
if err != nil {
continue // skip files with syntax errors
}
files = append(files, f)
}
if len(files) == 0 {
return nil, nil, fmt.Errorf("no matching files for target %s", t.Name)
}
var opts []any
if allDecls {
opts = append(opts, doc.AllDecls)
}
docPkg, err := doc.NewFromFiles(fset, files, "machine", opts...)
if err != nil {
return nil, nil, err
}
return docPkg, fset, nil
}