2015-07-25 5 views
21

Il seguente test tenta di utilizzare AST per aggiungere campi a una struttura. I campi vengono aggiunti correttamente, ma i commenti vengono aggiunti fuori ordine. Ho capito che la posizione potrebbe dover essere specificata manualmente, ma finora ho trovato un vuoto trovando una risposta.Commenti fuori servizio dopo aver aggiunto l'articolo a Vai AST

Ecco un test in mancanza: http://play.golang.org/p/RID4N30FZK

Ecco il codice:

package generator 

import (
    "bytes" 
    "fmt" 
    "go/ast" 
    "go/parser" 
    "go/printer" 
    "go/token" 
    "testing" 
) 

func TestAst(t *testing.T) { 

    source := `package a 

// B comment 
type B struct { 
    // C comment 
    C string 
}` 

    fset := token.NewFileSet() 
    file, err := parser.ParseFile(fset, "", []byte(source), parser.ParseComments) 
    if err != nil { 
     t.Error(err) 
    } 

    v := &visitor{ 
     file: file, 
    } 
    ast.Walk(v, file) 

    var output []byte 
    buf := bytes.NewBuffer(output) 
    if err := printer.Fprint(buf, fset, file); err != nil { 
     t.Error(err) 
    } 

    expected := `package a 

// B comment 
type B struct { 
    // C comment 
    C string 
    // D comment 
    D int 
    // E comment 
    E float64 
} 
` 

    if buf.String() != expected { 
     t.Error(fmt.Sprintf("Test failed. Expected:\n%s\nGot:\n%s", expected, buf.String())) 
    } 

    /* 
    actual output = `package a 

// B comment 
type B struct { 
    // C comment 
    // D comment 
    // E comment 
    C string 
    D int 
    E float64 
} 
` 
    */ 

} 

type visitor struct { 
    file *ast.File 
} 

func (v *visitor) Visit(node ast.Node) (w ast.Visitor) { 

    if node == nil { 
     return v 
    } 

    switch n := node.(type) { 
    case *ast.GenDecl: 
     if n.Tok != token.TYPE { 
      break 
     } 
     ts := n.Specs[0].(*ast.TypeSpec) 
     if ts.Name.Name == "B" { 
      fields := ts.Type.(*ast.StructType).Fields 
      addStructField(fields, v.file, "int", "D", "D comment") 
      addStructField(fields, v.file, "float64", "E", "E comment") 
     } 
    } 

    return v 
} 

func addStructField(fields *ast.FieldList, file *ast.File, typ string, name string, comment string) { 
    c := &ast.Comment{Text: fmt.Sprint("// ", comment)} 
    cg := &ast.CommentGroup{List: []*ast.Comment{c}} 
    f := &ast.Field{ 
     Doc: cg, 
     Names: []*ast.Ident{ast.NewIdent(name)}, 
     Type: ast.NewIdent(typ), 
    } 
    fields.List = append(fields.List, f) 
    file.Comments = append(file.Comments, cg) 
} 
+1

Sospetto che sia necessario aggiornare la [Mappa commenti] (http://golang.org/pkg/go/ast/#NewCommentMap) affinché funzioni correttamente. –

+2

Qui puoi vedere alcuni dettagli degli alberi effettivi e previsti: https://play.golang.org/p/qv63Hu1xmP grazie a https://golang.org/pkg/go/ast/#Fprint. Le principali differenze che vedo sono 'Slash',' NamePos' e 'Obj' non sono impostate. Ho provato a giocherellare con le posizioni, ma non sono riuscito a farlo bene ... – HectorJ

+1

Questo mi ha bloccato ... Sembra che ci sia una sorta di altro tipo di bookeeping che deve essere fatto, dato che sono stato in grado di ottenere Slash e NamePos da abbinare (a prescindere dall'offset 100) in questo: http://play.golang.org/p/pQodZncMjA - e persino aggiungere Addline e CommentMap non sembrano aiutare: http: //play.golang. org/p/GGj2eDwDF- –

risposta

4

credo di aver ottenuto di lavorare. Come indicato nel mio commento di cui sopra, i punti principali richieste sono:

  1. In particolare impostare le posizioni di buffer tra cui il Slash e NamePos
  2. Usa token.File.AddLine per aggiungere nuove linee a offset specifici (calcolati utilizzando le posizioni dal punto 1)
  3. Overallocate il buffer di origine in modo token.File.Position (usato da printer.Printer e token.File.Addline non mancano test di ricezione sul buffer fonte

Co de:

package main 

import (
    "bytes" 
    "fmt" 
    "go/ast" 
    "go/parser" 
    "go/printer" 
    "go/token" 
    "testing" 
) 

func main() { 
    tests := []testing.InternalTest{{"TestAst", TestAst}} 
    matchAll := func(t string, pat string) (bool, error) { return true, nil } 
    testing.Main(matchAll, tests, nil, nil) 
} 

func TestAst(t *testing.T) { 

    source := `package a 

// B comment 
type B struct { 
    // C comment 
    C string 
}` 

    buffer := make([]byte, 1024, 1024) 
    for idx,_ := range buffer { 
     buffer[idx] = 0x20 
    } 
    copy(buffer[:], source) 
    fset := token.NewFileSet() 
    file, err := parser.ParseFile(fset, "", buffer, parser.ParseComments) 
    if err != nil { 
     t.Error(err) 
    } 

    v := &visitor{ 
     file: file, 
     fset: fset, 
    } 
    ast.Walk(v, file) 

    var output []byte 
    buf := bytes.NewBuffer(output) 
    if err := printer.Fprint(buf, fset, file); err != nil { 
     t.Error(err) 
    } 

    expected := `package a 

// B comment 
type B struct { 
    // C comment 
    C string 
    // D comment 
    D int 
    // E comment 
    E float64 
} 
` 
    if buf.String() != expected { 
     t.Error(fmt.Sprintf("Test failed. Expected:\n%s\nGot:\n%s", expected, buf.String())) 
    } 

} 

type visitor struct { 
    file *ast.File 
    fset *token.FileSet 
} 

func (v *visitor) Visit(node ast.Node) (w ast.Visitor) { 

    if node == nil { 
     return v 
    } 

    switch n := node.(type) { 
    case *ast.GenDecl: 
     if n.Tok != token.TYPE { 
      break 
     } 
     ts := n.Specs[0].(*ast.TypeSpec) 
     if ts.Name.Name == "B" { 
      fields := ts.Type.(*ast.StructType).Fields 
      addStructField(v.fset, fields, v.file, "int", "D", "D comment") 
      addStructField(v.fset, fields, v.file, "float64", "E", "E comment") 
     } 
    } 

    return v 
} 

func addStructField(fset *token.FileSet, fields *ast.FieldList, file *ast.File, typ string, name string, comment string) { 
    prevField := fields.List[fields.NumFields()-1] 

    c := &ast.Comment{Text: fmt.Sprint("// ", comment), Slash: prevField.End() + 1} 
    cg := &ast.CommentGroup{List: []*ast.Comment{c}} 
    o := ast.NewObj(ast.Var, name) 
    f := &ast.Field{ 
     Doc: cg, 
     Names: []*ast.Ident{&ast.Ident{Name: name, Obj: o, NamePos: cg.End() + 1}}, 
    } 
    o.Decl = f 
    f.Type = &ast.Ident{Name: typ, NamePos: f.Names[0].End() + 1} 

    fset.File(c.End()).AddLine(int(c.End())) 
    fset.File(f.End()).AddLine(int(f.End())) 

    fields.List = append(fields.List, f) 
    file.Comments = append(file.Comments, cg) 
} 

Esempio: http://play.golang.org/p/_q1xh3giHm

Per articolo (3), è anche importante per impostare tutti i byte sovrassegnate spazi (0x20), in modo che la stampante non lamenta nullo byte durante l'elaborazione.

+0

Penso che tu l'abbia rotto! Molto bene. Sono sicuro che avrò domande di follow-up mentre aggiungo più funzionalità, ma questa è un'ottima risposta. Grazie! –