2015-07-25 25 views
21

Następujący test próbuje użyć AST do dodania pól do struktury. Pola są dodawane poprawnie, ale komentarze są dodawane poza kolejnością. Zbieram pozycję, która może wymagać ręcznego określenia, ale do tej pory narysowałem pustkę znajdującą odpowiedź.Komentarze są nieczynne po dodaniu pozycji do wersji AST

Oto braku testu: http://play.golang.org/p/RID4N30FZK

Oto kod:

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

Podejrzewam, że musisz zaktualizować [Comment Map] (http://golang.org/pkg/go/ast/#NewCommentMap), aby to działało poprawnie. –

+2

Tutaj można zobaczyć niektóre szczegóły rzeczywistych i oczekiwanych drzew: https://play.golang.org/p/qv63Hu1xmP dzięki https://golang.org/pkg/go/ast/#Fprint. Główne różnice, które widzę, to 'Slash',' NamePos' i 'Obj' nie ustawione. Próbowałem skrzywdzić pozycje, ale nie mogłem tego naprawić ... – HectorJ

+1

Ten mnie zaskoczył ... Wygląda na to, że trzeba coś zrobić, ponieważ udało mi się zdobyć Slasha i NamePos, aby dopasować (zrównoważyć o 100) w tym: http://play.golang.org/p/pQodZncMjA - a nawet dodanie w AddLine i CommentMap nie wydaje się pomóc: http: //play.golang. org/p/GGj2eDwDF- –

Odpowiedz

4

wierzę Dostałem go do pracy. Jak stwierdzono w moim komentarzu powyżej, główne punkty wymagane są:

  1. szczegółowo określone lokalizacje buforowe tym Slash i NamePos
  2. Zastosowanie token.File.AddLine aby dodać nowe linie w określonych przesunięć (obliczonej na podstawie pozycji z pkt 1)
  3. Overallocate bufor źródło tak token.File.Position (używany przez printer.Printer i token.File.Addline nie powiedzie kontrole asortymencie w buforze źródłowym

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) 
} 

Przykład: http://play.golang.org/p/_q1xh3giHm

Dla punkcie (3), ważne jest również, aby ustawić wszystkie overallocated bajtów do przestrzeni (0x20), dzięki czemu drukarka nie narzekają wartości null bajtów podczas ich przetwarzania.

+0

Myślę, że go złamałeś! Dobra robota. Na pewno będę mieć pytania uzupełniające, ponieważ dodam więcej funkcjonalności, ale to jest świetna odpowiedź. Dzięki! –