我写了一个可以自动静音的程序。在运行时,我读取源代码,使用 go/parser 对其进行解析,在 ast 中搜索正确的位置,创建一些 go/ast.Expr,将它们插入到 ast 中并使用 go/format 格式化新的 ast。
我的问题是代码没有出现在我插入的位置。
这是我插入的地方:
var passwd = []*PwEntry{}
// line 1 after
// line 2 after
我正在搜索 ast.CompositeLit 并将新的 Exprs 插入 Elts 中。我的期望是,新代码显示在大括号之间,并且注释“line 1 after”保留在右大括号后面。但事实并非如此。这是 go/format 生成的代码:
var passwd = []*PwEntry{{Login:
// line 1 after
// line 2 after
"abc", Salt: []byte{214, 194, 249, 8, 11, 40, 37, 195, 65, 130, 142, 86, 68, 78, 185, 33}, Algo: 1, Hash: []byte{70, 8, 114, 178, 255, 193, 204, 112, 83, 209, 249, 153, 213, 253, 151, 47, 49, 99, 133, 139, 203, 184, 243, 15, 203, 16, 6, 235, 29, 236, 249, 57}}}
如何插入新的 ast.Expr 以使以下行保留在后面?
这是完整的示例:
package main
import (
"crypto/rand"
"fmt"
"github.com/peterh/liner"
"go/ast"
"go/format"
"go/parser"
"go/token"
"golang.org/x/crypto/argon2"
"os"
"runtime"
)
//======================================================================
// Terminal input
//----------------------------------------------------------------------
func ReadLogin(term *liner.State) string {
login, err := term.Prompt("Login: ")
if err != nil {
panic(err)
}
return login
}
func ReadPassword(term *liner.State) string {
password, err := term.PasswordPrompt("Password: ")
if err != nil {
panic(err)
}
return password
}
//======================================================================
// Mutate source file
//----------------------------------------------------------------------
type Source struct {
FileName string
FileSet *token.FileSet
AstFile *ast.File
}
func NewSource() (this *Source) {
this = new(Source)
_, fn, _, ok := runtime.Caller(0)
if !ok {
panic("Can not read source.")
}
this.FileName = fn
this.FileSet = token.NewFileSet()
return
}
func (this *Source) Read() {
var err error
this.AstFile, err = parser.ParseFile(
this.FileSet, this.FileName, nil, parser.ParseComments)
if err != nil {
panic(err)
}
}
func (this *Source) Dump() {
ast.Print(this.FileSet, this.AstFile)
}
// Search for top level decleration "var passwd = []*PwEntry{...}".
func (this *Source) FindPasswdDecl() *ast.CompositeLit {
for _, node := range this.AstFile.Decls {
switch decl := node.(type) {
case *ast.GenDecl:
if decl.Tok == token.VAR {
// fmt.Println("found: var")
for _, spec := range decl.Specs {
switch vspec := spec.(type) {
case *ast.ValueSpec:
if len(vspec.Names) == 1 && vspec.Names[0].Name == "passwd" {
// fmt.Println("found: passwd")
if len(vspec.Values) == 1 {
switch cl := vspec.Values[0].(type) {
case *ast.CompositeLit:
switch clt := cl.Type.(type) {
case *ast.ArrayType:
// fmt.Println("found: []")
switch se := clt.Elt.(type) {
case *ast.StarExpr:
// fmt.Println("found: *")
switch sx := se.X.(type) {
case *ast.Ident:
if sx.Name == "PwEntry" {
// fmt.Println("found: PwEntry")
return cl
}
}
}
}
}
}
}
}
}
}
}
}
return nil
}
func AstString(s string) ast.Expr {
return &ast.BasicLit{
Kind: token.STRING,
Value: fmt.Sprintf("%q", s)}
}
func AstInt(i int) ast.Expr {
return &ast.BasicLit{
Kind: token.INT,
Value: fmt.Sprintf("%d", i)}
}
func AstByteArray(ba []byte) ast.Expr {
cl := &ast.CompositeLit{
Type: &ast.ArrayType{
Elt: ast.NewIdent("byte")}}
cl.Elts = make([]ast.Expr, len(ba))
for i, b := range ba {
cl.Elts[i] = AstInt(int(b))
}
return cl
}
func AstPwEntryElts(entry *PwEntry) []ast.Expr {
return []ast.Expr{
&ast.CompositeLit{
Elts: []ast.Expr{
&ast.KeyValueExpr{
Key: ast.NewIdent("Login"),
Value: AstString(entry.Login)},
&ast.KeyValueExpr{
Key: ast.NewIdent("Salt"),
Value: AstByteArray(entry.Salt)},
&ast.KeyValueExpr{
Key: ast.NewIdent("Algo"),
Value: AstInt(entry.Algo)},
&ast.KeyValueExpr{
Key: ast.NewIdent("Hash"),
Value: AstByteArray(entry.Hash)}}}}
}
func (this *Source) SetPwEntry(entry *PwEntry) {
cl := this.FindPasswdDecl()
if cl.Elts == nil {
cl.Elts = AstPwEntryElts(entry)
} else {
for i, elt := range cl.Elts {
switch nelt := elt.(type) {
case *ast.CompositeLit:
if len(nelt.Elts) == 4 {
fmt.Println("found: 4 in %d", i)
return
}
}
}
}
}
func (this *Source) Write() {
output, err := os.Create("tmp.go")
if err != nil {
panic(err)
}
err = format.Node(output, this.FileSet, this.AstFile)
if err != nil {
panic(err)
}
}
//======================================================================
// Password entry
//----------------------------------------------------------------------
const SaltLength = 16
const (
AlgoNil = iota
AlgoArgon2id
)
type PwEntry struct {
Login string
Salt []byte
Algo int
Hash []byte
}
func NewPwEntry() *PwEntry {
this := new(PwEntry)
this.Salt = make([]byte, SaltLength)
_, err := rand.Read(this.Salt)
if err != nil {
panic(err)
}
return this
}
func (this *PwEntry) SetPassword(password []byte) {
this.Algo = AlgoArgon2id
this.Hash = argon2.IDKey(password, this.Salt, 1, 64*1024, 4, 32)
}
func (this *PwEntry) AlgoName() string {
switch this.Algo {
case AlgoNil:
return "nil"
case AlgoArgon2id:
return "Argon2id"
default:
return "undefined"
}
}
func (this *PwEntry) String() string {
return fmt.Sprintf(`{"%s":"%s","%s":"%x","%s":"%s","%s":"%x"}`,
"login", this.Login,
"salt", this.Salt,
"algo", this.AlgoName(),
"hash", this.Hash,
)
}
//======================================================================
// Password entries
//----------------------------------------------------------------------
var passwd = []*PwEntry{}
// line 1 after
// line 2 after
func SearchPwEntry(login string) *PwEntry {
for _, entry := range passwd {
if entry.Login == login {
return entry
}
}
return nil
}
func GetPwEntry(login string) *PwEntry {
entry := SearchPwEntry(login)
if entry == nil {
entry = NewPwEntry()
entry.Login = login
}
return entry
}
//======================================================================
// Main
//----------------------------------------------------------------------
func Passwd() {
term := liner.NewLiner()
defer term.Close()
src := NewSource()
defer src.Write()
src.Read()
//src.Dump()
login := ReadLogin(term)
entry := GetPwEntry(login)
entry.SetPassword([]byte(ReadPassword(term)))
fmt.Println(entry)
src.SetPwEntry(entry)
}
func main() {
Passwd()
}
这似乎是一个未解决的错误:https://github.com/golang/go/issues/20744
123