feat(kiosk): resolve constant plugin IDs, overrides and the CheckT helper
- kiosk.ReadIdentity: ID()/Requires() as literals or constants across packages and modules, read with go/parser, never run - plugin minimums from go.mod requires; v0.0.0 is an error, a pseudo-version warns - kiosk.Validate, kiosk.CheckT (no testing import) - plugin:add reads IDs through kiosk.ReadIdentity, so const-ID plugins register
This commit is contained in:
@@ -7,13 +7,10 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/yuin/goldmark"
|
||||
@@ -97,17 +94,19 @@ func Derive(ctx context.Context, dir string, opts DeriveOptions) (Manifest, []st
|
||||
}
|
||||
m.Framework = fw
|
||||
|
||||
ident, err := readLiteralIdentity(abs)
|
||||
r := newResolver(ctx, abs)
|
||||
r.modLoaded, r.root, r.mod = true, root, mod
|
||||
ident, err := r.identity(abs)
|
||||
if err != nil {
|
||||
return Manifest{}, nil, err
|
||||
}
|
||||
m.ID = ident.ID
|
||||
if len(ident.Requires) > 0 {
|
||||
m.Requires = Requires{}
|
||||
for _, id := range ident.Requires {
|
||||
m.Requires[id] = ""
|
||||
}
|
||||
reqs, w, err := r.minimums(ident.Requires)
|
||||
warnings = append(warnings, w...)
|
||||
if err != nil {
|
||||
return Manifest{}, warnings, err
|
||||
}
|
||||
m.Requires = reqs
|
||||
|
||||
existing, err := readExisting(abs)
|
||||
if err != nil {
|
||||
@@ -289,73 +288,6 @@ func plainText(n gast.Node, src []byte) string {
|
||||
return strings.Join(strings.Fields(b.String()), " ")
|
||||
}
|
||||
|
||||
// identity is what Task 2's reader returns: the literals of ID() and
|
||||
// Requires().
|
||||
type identity struct {
|
||||
ID string
|
||||
Requires []string
|
||||
}
|
||||
|
||||
// readLiteralIdentity reads the string literals returned by ID() and
|
||||
// Requires() in dir/plugin.go.
|
||||
func readLiteralIdentity(dir string) (identity, error) {
|
||||
path := filepath.Join(dir, "plugin.go")
|
||||
fset := token.NewFileSet()
|
||||
file, err := parser.ParseFile(fset, path, nil, 0)
|
||||
if err != nil {
|
||||
return identity{}, fmt.Errorf("kiosk: parse %s: %w", path, err)
|
||||
}
|
||||
var id identity
|
||||
var idErr, reqErr error
|
||||
foundID := false
|
||||
for _, decl := range file.Decls {
|
||||
fn, ok := decl.(*ast.FuncDecl)
|
||||
if !ok || fn.Recv == nil || fn.Body == nil {
|
||||
continue
|
||||
}
|
||||
ret := lastReturn(fn)
|
||||
switch fn.Name.Name {
|
||||
case "ID":
|
||||
foundID = true
|
||||
lit, ok := ret.(*ast.BasicLit)
|
||||
if !ok || lit.Kind != token.STRING {
|
||||
idErr = fmt.Errorf("kiosk: %s: ID() must return a string literal", path)
|
||||
continue
|
||||
}
|
||||
id.ID, _ = strconv.Unquote(lit.Value)
|
||||
case "Requires":
|
||||
switch e := ret.(type) {
|
||||
case *ast.Ident:
|
||||
if e.Name != "nil" {
|
||||
reqErr = fmt.Errorf("kiosk: %s: Requires() must return nil or a []string literal", path)
|
||||
}
|
||||
case *ast.CompositeLit:
|
||||
for _, el := range e.Elts {
|
||||
lit, ok := el.(*ast.BasicLit)
|
||||
if !ok || lit.Kind != token.STRING {
|
||||
reqErr = fmt.Errorf("kiosk: %s: Requires() must return string literals", path)
|
||||
break
|
||||
}
|
||||
s, _ := strconv.Unquote(lit.Value)
|
||||
id.Requires = append(id.Requires, s)
|
||||
}
|
||||
default:
|
||||
reqErr = fmt.Errorf("kiosk: %s: Requires() must return nil or a []string literal", path)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !foundID {
|
||||
return identity{}, fmt.Errorf("kiosk: %s has no ID() method", path)
|
||||
}
|
||||
if idErr != nil {
|
||||
return identity{}, idErr
|
||||
}
|
||||
if reqErr != nil {
|
||||
return identity{}, reqErr
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// lastReturn is the single result of fn's last top-level return, or nil.
|
||||
func lastReturn(fn *ast.FuncDecl) ast.Expr {
|
||||
var res ast.Expr
|
||||
|
||||
Reference in New Issue
Block a user