diff --git a/pkg/formatters/ast/ast.go b/pkg/formatters/ast/ast.go index d7293b9..7f2b187 100644 --- a/pkg/formatters/ast/ast.go +++ b/pkg/formatters/ast/ast.go @@ -10,11 +10,17 @@ import ( "errors" ) +const ( + INIT_TEST_GO_FNAME = "init_test.go" +) + type ASTer struct { - pkg string - fname string - fset *token.FileSet - node *ast.File + pkg string + pkg_test_go_fname string + init_test_fset *token.FileSet + init_test_node *ast.File + pkg_test_fset *token.FileSet + pkg_test_node *ast.File } func NewASTer() (*ASTer, error) { @@ -34,26 +40,69 @@ func NewASTer() (*ASTer, error) { return a, nil } -func (a *ASTer) ДобавитьТестовуюФункцию(func_name string, ps Параметры) error { - return a.добавитьвФайлФункцию(func_name, ps) +func (a *ASTer) ДобавитьШаг(шаг, f string, ps Параметры) error { + return a.добавитьШаг(шаг, f, ps) } func (a *ASTer) найтиТестовыйФайл() error { a.сгенеритьИмяФайла() - _, err := os.Stat(a.fname) - return err -} -func (a *ASTer) сгенеритьИмяФайла() { - a.fname = a.pkg + "_test.go" -} - -func (a *ASTer) добавитьвФайлФункцию(func_name string, ps Параметры) error { - err := a.спарситьФайлиДобавитьФункцию(func_name, ps) + _, err := os.Stat(INIT_TEST_GO_FNAME) if err != nil { return err } - return a.перезаписатьФайл() + _, err = os.Stat(a.pkg_test_go_fname) + return err +} +func (a *ASTer) сгенеритьИмяФайла() { + a.pkg_test_go_fname = a.pkg + "_test.go" +} + +func (a *ASTer) добавитьШаг(шаг, f string, ps Параметры) error { + err := a.добавитьШагвInitФайл(шаг, f, ps) + if err != nil { + return err + } + + err = a.добавитьФункциювТестовыйФайл(f, ps) + if err != nil { + return err + } + + return nil +} +func (a *ASTer) добавитьШагвInitФайл(шаг, f string, ps Параметры) error { + err := a.спарситьInitФайлиДобавитьШаг(шаг, f, ps) + if err != nil { + return err + } + err = a.перезаписатьInitФайл() + if err != nil { + return err + } + return nil +} +func (a *ASTer) добавитьФункциювТестовыйФайл(f string, ps Параметры) error { + err := a.спарситьФайлиДобавитьФункцию(f, ps) + if err != nil { + return err + } + err = a.перезаписатьФайл() + if err != nil { + return err + } + return nil +} + +func (a *ASTer) спарситьInitФайлиДобавитьШаг(шаг, func_name string, ps Параметры) error { + err := a.спарситьInitФайл() + if err != nil { + return err + } + + f := a.создатьШаг(шаг, func_name, ps) + a.добавитьШагвИнициализаторЕслиНету(f) + return nil } func (a *ASTer) спарситьФайлиДобавитьФункцию(func_name string, ps Параметры) error { err := a.спарситьФайл() @@ -61,13 +110,41 @@ func (a *ASTer) спарситьФайлиДобавитьФункцию(func_na return err } - fun := a.создатьФункцию(func_name, ps) - a.добавитьФункциюЕслиНету(fun) + f := a.создатьФункцию(func_name, ps) + a.добавитьФункцию(f) return nil } -func (a *ASTer) добавитьФункциюЕслиНету(fun *ast.FuncDecl) { - for _, d := range a.node.Decls { +func (a *ASTer) добавитьШагвИнициализаторЕслиНету(step *ast.ExprStmt) { + for _, d := range a.init_test_node.Decls { + f, ok := d.(*ast.FuncDecl) + if !ok { + continue + } + if f.Name == nil { + continue + } + if f.Name.Name == "InitializeScenario" { + a.добавитьШагвФункциюInitializeScenario(f, step) + return + } + } +} +func (a *ASTer) добавитьШагвФункциюInitializeScenario(f *ast.FuncDecl, step *ast.ExprStmt) { + for i, stmt := range f.Body.List { + if являетсяЛиШагомсТакойЖеФункцией(stmt, step) { + return + } + + if !являетсяЛиШагом(stmt) { + n := append([]ast.Stmt{step}, f.Body.List[i:]...) + f.Body.List = append(f.Body.List[0:i], n...) + return + } + } +} +func (a *ASTer) добавитьФункцию(fun *ast.FuncDecl) { + for _, d := range a.pkg_test_node.Decls { f, ok := d.(*ast.FuncDecl) if !ok { continue @@ -80,16 +157,43 @@ func (a *ASTer) добавитьФункциюЕслиНету(fun *ast.FuncDecl } } - a.node.Decls = append(a.node.Decls, fun) + a.pkg_test_node.Decls = append(a.pkg_test_node.Decls, fun) +} + +func (a *ASTer) спарситьInitФайл() error { + fset := token.NewFileSet() + node, err := parser.ParseFile(fset, INIT_TEST_GO_FNAME, nil, parser.AllErrors|parser.ParseComments) + a.init_test_fset = fset + a.init_test_node = node + return err } func (a *ASTer) спарситьФайл() error { fset := token.NewFileSet() - node, err := parser.ParseFile(fset, a.fname, nil, parser.ParseComments) - a.fset = fset - a.node = node + node, err := parser.ParseFile(fset, a.pkg_test_go_fname, nil, parser.AllErrors|parser.ParseComments) + a.pkg_test_fset = fset + a.pkg_test_node = node return err } +func (a *ASTer) создатьШаг(шаг, func_name string, ps Параметры) *ast.ExprStmt { + st := &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("ctx"), + Sel: ast.NewIdent("Step"), + }, + Args: []ast.Expr{ + &ast.BasicLit{ + Kind: token.STRING, + Value: шаг, + }, + ast.NewIdent(func_name), + }, + }, + } + + return st +} func (a *ASTer) создатьФункцию(func_name string, ps Параметры) *ast.FuncDecl { f := &ast.FuncDecl{ Name: ast.NewIdent(func_name), @@ -113,17 +217,31 @@ func (a *ASTer) добавитьПараметрыФункции(ps Параме } } -func (a *ASTer) перезаписатьФайл() error { - err := переименоватьФайлСоВременем(a.fname) +func (a *ASTer) перезаписатьInitФайл() error { + err := переименоватьФайлСоВременем(INIT_TEST_GO_FNAME) if err != nil { return errors.Join(err, errors.New("cant backup orig file")) } - f, err := os.Create(a.fname) + f, err := os.Create(INIT_TEST_GO_FNAME) if err != nil { return errors.Join(err, errors.New("cant rewrite orig file")) } defer f.Close() - return format.Node(f, a.fset, a.node) + return format.Node(f, a.init_test_fset, a.init_test_node) +} +func (a *ASTer) перезаписатьФайл() error { + err := переименоватьФайлСоВременем(a.pkg_test_go_fname) + if err != nil { + return errors.Join(err, errors.New("cant backup orig file")) + } + + f, err := os.Create(a.pkg_test_go_fname) + if err != nil { + return errors.Join(err, errors.New("cant rewrite orig file")) + } + defer f.Close() + + return format.Node(f, a.pkg_test_fset, a.pkg_test_node) } diff --git a/pkg/formatters/ast/ast_test.go b/pkg/formatters/ast/ast_test.go index aca0ba5..6dd0d1f 100644 --- a/pkg/formatters/ast/ast_test.go +++ b/pkg/formatters/ast/ast_test.go @@ -24,7 +24,7 @@ func resetTestData() { t = &testData{} } func beforeSuite() { - CharactersAroundMismatchToInclude = 50 + CharactersAroundMismatchToInclude = 100 } func afterSuite() { } @@ -57,13 +57,16 @@ func файл(fname string, content *godog.DocString) { err := lib.WriteFile(fname, content.Content) Ok(err) } + func параметр(имя, тип string) { t.params = append(t.params, Параметр{имя, тип}) } -func добавляетсяФункция(func_name string) { - err := ДобавитьТестовуюФункцию(func_name, t.params) + +func добавляетсяШагСФункцией(шаг, func_name string) { + err := ДобавитьШаг("`"+шаг+"`", func_name, t.params) Ok(err) } + func файлДолженСодержать(fname string, content *godog.DocString) { cont, err := lib.ReadFile(fname) Ok(err) diff --git a/pkg/formatters/ast/features/app.feature b/pkg/formatters/ast/features/app.feature index f9fbaad..d809d14 100644 --- a/pkg/formatters/ast/features/app.feature +++ b/pkg/formatters/ast/features/app.feature @@ -2,12 +2,49 @@ Функционал: AST-редактир go-файлов Сгенерированные функции автоматически добавляются в тест-файл текущего пакета - Сценарий: Добавление функции + Сценарий: Добавление шага + Дано Файл "init_test.go": + ``` + package mypkg + + func InitializeScenario(ctx *godog.ScenarioContext) { + + ctx.Before(func(ctx context.Context, sc *godog.Scenario) (context.Context, error) { + beforeScenario() + return ctx, nil + }) + ctx.After(func(ctx context.Context, sc *godog.Scenario, err error) (context.Context, error) { + afterScenario() + return ctx, nil + }) + InitializeGomegaForGodog(ctx) + } + + ``` Дано Файл "mypkg_test.go": ``` package mypkg ``` - Когда Добавляется функция "ПриветМир" + Когда Добавляется шаг: "^Привет Мир!$" с функцией "ПриветМир" + То Файл "init_test.go" должен содержать: + ``` + package mypkg + + func InitializeScenario(ctx *godog.ScenarioContext) { + ctx.Step(`^Привет Мир!$`, ПриветМир) + + ctx.Before(func(ctx context.Context, sc *godog.Scenario) (context.Context, error) { + beforeScenario() + return ctx, nil + }) + ctx.After(func(ctx context.Context, sc *godog.Scenario, err error) (context.Context, error) { + afterScenario() + return ctx, nil + }) + InitializeGomegaForGodog(ctx) + } + + ``` То Файл "mypkg_test.go" должен содержать: ``` package mypkg @@ -17,13 +54,31 @@ ``` - Сценарий: Добавление функции с параметрами: int + Сценарий: Добавление шага с параметрами: int + Дано Файл "init_test.go": + ``` + package mypkg + + func InitializeScenario(ctx *godog.ScenarioContext) { + + ctx.Before(func(ctx context.Context, sc *godog.Scenario) (context.Context, error) { + beforeScenario() + return ctx, nil + }) + ctx.After(func(ctx context.Context, sc *godog.Scenario, err error) (context.Context, error) { + afterScenario() + return ctx, nil + }) + InitializeGomegaForGodog(ctx) + } + + ``` Дано Файл "mypkg_test.go": ``` package mypkg ``` Дано Параметр: "arg1", "int" - Когда Добавляется функция "ПриветМир" + Когда Добавляется шаг: "^Привет Мир!$" с функцией "ПриветМир" То Файл "mypkg_test.go" должен содержать: ``` package mypkg @@ -33,14 +88,32 @@ ``` - Сценарий: Добавление функции с параметрами: int, string + Сценарий: Добавление шага с параметрами: int, string + Дано Файл "init_test.go": + ``` + package mypkg + + func InitializeScenario(ctx *godog.ScenarioContext) { + + ctx.Before(func(ctx context.Context, sc *godog.Scenario) (context.Context, error) { + beforeScenario() + return ctx, nil + }) + ctx.After(func(ctx context.Context, sc *godog.Scenario, err error) (context.Context, error) { + afterScenario() + return ctx, nil + }) + InitializeGomegaForGodog(ctx) + } + + ``` Дано Файл "mypkg_test.go": ``` package mypkg ``` Дано Параметр: "arg1", "int" Дано Параметр: "arg2", "string" - Когда Добавляется функция "ПриветМир" + Когда Добавляется шаг: "^Привет Мир!$" с функцией "ПриветМир" То Файл "mypkg_test.go" должен содержать: ``` package mypkg @@ -50,14 +123,32 @@ ``` - Сценарий: Добавление функции с параметрами: string, int + Сценарий: Добавление шага с параметрами: string, int + Дано Файл "init_test.go": + ``` + package mypkg + + func InitializeScenario(ctx *godog.ScenarioContext) { + + ctx.Before(func(ctx context.Context, sc *godog.Scenario) (context.Context, error) { + beforeScenario() + return ctx, nil + }) + ctx.After(func(ctx context.Context, sc *godog.Scenario, err error) (context.Context, error) { + afterScenario() + return ctx, nil + }) + InitializeGomegaForGodog(ctx) + } + + ``` Дано Файл "mypkg_test.go": ``` package mypkg ``` Дано Параметр: "arg1", "string" Дано Параметр: "arg2", "int" - Когда Добавляется функция "ПриветМир" + Когда Добавляется шаг: "^Привет Мир!$" с функцией "ПриветМир" То Файл "mypkg_test.go" должен содержать: ``` package mypkg @@ -67,7 +158,26 @@ ``` - Сценарий: Добавление функции к существующей + Сценарий: Добавление шага к существующему + Дано Файл "init_test.go": + ``` + package mypkg + + func InitializeScenario(ctx *godog.ScenarioContext) { + ctx.Step(`сделать чтото`, СделатьЧтото) + + ctx.Before(func(ctx context.Context, sc *godog.Scenario) (context.Context, error) { + beforeScenario() + return ctx, nil + }) + ctx.After(func(ctx context.Context, sc *godog.Scenario, err error) (context.Context, error) { + afterScenario() + return ctx, nil + }) + InitializeGomegaForGodog(ctx) + } + + ``` Дано Файл "mypkg_test.go": ``` package mypkg @@ -78,7 +188,27 @@ ``` Дано Параметр: "arg1", "int" - Когда Добавляется функция "ПриветМир" + Когда Добавляется шаг: "^Привет Мир!$" с функцией "ПриветМир" + То Файл "init_test.go" должен содержать: + ``` + package mypkg + + func InitializeScenario(ctx *godog.ScenarioContext) { + ctx.Step(`сделать чтото`, СделатьЧтото) + ctx.Step(`^Привет Мир!$`, ПриветМир) + + ctx.Before(func(ctx context.Context, sc *godog.Scenario) (context.Context, error) { + beforeScenario() + return ctx, nil + }) + ctx.After(func(ctx context.Context, sc *godog.Scenario, err error) (context.Context, error) { + afterScenario() + return ctx, nil + }) + InitializeGomegaForGodog(ctx) + } + + ``` То Файл "mypkg_test.go" должен содержать: ``` package mypkg @@ -91,7 +221,26 @@ ``` - Сценарий: Не добавляет если такая функция уже есть + Сценарий: Не добавляет если шаг с такой функцией уже есть + Дано Файл "init_test.go": + ``` + package mypkg + + func InitializeScenario(ctx *godog.ScenarioContext) { + ctx.Step(`^Привет Мир!$`, ПриветМир) + + ctx.Before(func(ctx context.Context, sc *godog.Scenario) (context.Context, error) { + beforeScenario() + return ctx, nil + }) + ctx.After(func(ctx context.Context, sc *godog.Scenario, err error) (context.Context, error) { + afterScenario() + return ctx, nil + }) + InitializeGomegaForGodog(ctx) + } + + ``` Дано Файл "mypkg_test.go": ``` package mypkg @@ -102,8 +251,27 @@ ``` Дано Параметр: "arg1", "int" - Когда Добавляется функция "ПриветМир" - Когда Добавляется функция "ПриветМир" + Когда Добавляется шаг: "^Привет Мир!$" с функцией "ПриветМир" + Когда Добавляется шаг: "^Привет Мир!$" с функцией "ПриветМир" + То Файл "init_test.go" должен содержать: + ``` + package mypkg + + func InitializeScenario(ctx *godog.ScenarioContext) { + ctx.Step(`^Привет Мир!$`, ПриветМир) + + ctx.Before(func(ctx context.Context, sc *godog.Scenario) (context.Context, error) { + beforeScenario() + return ctx, nil + }) + ctx.After(func(ctx context.Context, sc *godog.Scenario, err error) (context.Context, error) { + afterScenario() + return ctx, nil + }) + InitializeGomegaForGodog(ctx) + } + + ``` То Файл "mypkg_test.go" должен содержать: ``` package mypkg @@ -114,15 +282,52 @@ ``` - Сценарий: Не добавляет второй раз + Сценарий: Второй раз - не добавляет + Дано Файл "init_test.go": + ``` + package mypkg + + func InitializeScenario(ctx *godog.ScenarioContext) { + + ctx.Before(func(ctx context.Context, sc *godog.Scenario) (context.Context, error) { + beforeScenario() + return ctx, nil + }) + ctx.After(func(ctx context.Context, sc *godog.Scenario, err error) (context.Context, error) { + afterScenario() + return ctx, nil + }) + InitializeGomegaForGodog(ctx) + } + + ``` Дано Файл "mypkg_test.go": ``` package mypkg ``` Дано Параметр: "arg1", "int" - Когда Добавляется функция "ПриветМир" - Когда Добавляется функция "ПриветМир" + Когда Добавляется шаг: "^Привет Мир!$" с функцией "ПриветМир" + Когда Добавляется шаг: "^Привет Мир!$" с функцией "ПриветМир" + То Файл "init_test.go" должен содержать: + ``` + package mypkg + + func InitializeScenario(ctx *godog.ScenarioContext) { + ctx.Step(`^Привет Мир!$`, ПриветМир) + + ctx.Before(func(ctx context.Context, sc *godog.Scenario) (context.Context, error) { + beforeScenario() + return ctx, nil + }) + ctx.After(func(ctx context.Context, sc *godog.Scenario, err error) (context.Context, error) { + afterScenario() + return ctx, nil + }) + InitializeGomegaForGodog(ctx) + } + + ``` То Файл "mypkg_test.go" должен содержать: ``` package mypkg diff --git a/pkg/formatters/ast/global.go b/pkg/formatters/ast/global.go index c419afe..0a10f16 100644 --- a/pkg/formatters/ast/global.go +++ b/pkg/formatters/ast/global.go @@ -4,11 +4,11 @@ package ast type Параметр []string type Параметры []Параметр -func ДобавитьТестовуюФункцию(f string, ps Параметры) error { +func ДобавитьШаг(шаг, f string, ps Параметры) error { a, err := NewASTer() if err != nil { return err } - return a.ДобавитьТестовуюФункцию(f, ps) + return a.ДобавитьШаг(шаг, f, ps) } diff --git a/pkg/formatters/ast/helpers.go b/pkg/formatters/ast/helpers.go index 1e57b44..63fc189 100644 --- a/pkg/formatters/ast/helpers.go +++ b/pkg/formatters/ast/helpers.go @@ -2,6 +2,7 @@ package ast import ( "fmt" + "go/ast" "go/parser" "go/token" "os" @@ -11,6 +12,10 @@ import ( "errors" ) +const ( + BACKUP_DIR = ".back" +) + func получитьИмяGoПакетаВЭтойДире() (pkg_name string, err_ret error) { err_ret = errors.New("not found") @@ -43,8 +48,79 @@ func получитьИмяGoПакетаФайла(fname string) (string, error } func переименоватьФайлСоВременем(fname string) error { + os.MkdirAll(BACKUP_DIR, 0755) currentTime := time.Now() timestamp := currentTime.Format("20060102_150405") - new_fname := fmt.Sprintf("%v_%v", fname, timestamp) + new_fname := fmt.Sprintf(BACKUP_DIR+"/%v_%v", fname, timestamp) return os.Rename(fname, new_fname) } + +func являетсяЛиШагомсТакойЖеФункцией(stmt ast.Stmt, step *ast.ExprStmt) bool { + e, ok := stmt.(*ast.ExprStmt) + if !ok { + return false + } + + e_name, err := имяШага(e) + if err != nil { + return false + } + + step_name, err := имяШага(step) + if err != nil { + return false + } + + return e_name == step_name +} +func являетсяЛиШагом(stmt ast.Stmt) bool { + e, ok := stmt.(*ast.ExprStmt) + if !ok { + return false + } + + e_name, err := имяШага(e) + if err != nil { + return false + } + + return len(e_name) > 0 +} + +func имяШага(step *ast.ExprStmt) (res string, err error) { + err = errors.New("not found") + + call, ok := step.X.(*ast.CallExpr) + if !ok { + return + } + + selector, ok := call.Fun.(*ast.SelectorExpr) + if !ok { + return + } + + ctx, ok := selector.X.(*ast.Ident) + if !ok { + return + } + + if ctx.Name != "ctx" { + return + } + + if selector.Sel.Name != "Step" { + return + } + + if len(call.Args) < 2 { + return + } + + fn, ok := call.Args[1].(*ast.Ident) + if !ok { + return + } + + return fn.Name, nil +} diff --git a/pkg/formatters/ast/init_test.go b/pkg/formatters/ast/init_test.go index 6d60406..6546f05 100644 --- a/pkg/formatters/ast/init_test.go +++ b/pkg/formatters/ast/init_test.go @@ -13,7 +13,7 @@ import ( func InitializeScenario(ctx *godog.ScenarioContext) { ctx.Step(`^Файл "([^"]*)":$`, файл) ctx.Step(`^Параметр: "([^"]*)", "([^"]*)"$`, параметр) - ctx.Step(`^Добавляется функция "([^"]*)"$`, добавляетсяФункция) + ctx.Step(`^Добавляется шаг: "([^"]*)" с функцией "([^"]*)"$`, добавляетсяШагСФункцией) ctx.Step(`^Файл "([^"]*)" должен содержать:$`, файлДолженСодержать) // -----------------------