diff --git a/pkg/formatters/ast/ast.go b/pkg/formatters/ast/ast.go new file mode 100644 index 0000000..daf41ec --- /dev/null +++ b/pkg/formatters/ast/ast.go @@ -0,0 +1,99 @@ +package ast + +import ( + "go/ast" + "go/format" + "go/parser" + "go/token" + "os" + + "errors" +) + +type ASTer struct { + pkg string + fname string + fset *token.FileSet + node *ast.File +} + +func NewASTer() (*ASTer, error) { + a := &ASTer{} + + pkg, err := получитьИмяGoПакетаВЭтойДире() + if err != nil { + return nil, err + } + a.pkg = pkg + + err = a.найтиТестовыйФайл() + if err != nil { + return nil, err + } + + return a, nil +} + +func (a *ASTer) ДобавитьТестовуюФункцию(func_name string) error { + return a.добавитьвФайлФункцию(func_name) +} + +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) error { + err := a.спарситьФайлиДобавитьФункцию(func_name) + if err != nil { + return err + } + + return a.перезаписатьФайл() +} +func (a *ASTer) спарситьФайлиДобавитьФункцию(func_name string) error { + err := a.спарситьФайл() + if err != nil { + return err + } + + fun := a.пустаяФункция(func_name) + a.node.Decls = append(a.node.Decls, fun) + return nil +} +func (a *ASTer) спарситьФайл() error { + fset := token.NewFileSet() + node, err := parser.ParseFile(fset, a.fname, nil, parser.ParseComments) + a.fset = fset + a.node = node + return err +} +func (a *ASTer) пустаяФункция(func_name string) *ast.FuncDecl { + return &ast.FuncDecl{ + Name: ast.NewIdent(func_name), + Type: &ast.FuncType{ + Params: &ast.FieldList{}, + Results: &ast.FieldList{}, + }, + Body: &ast.BlockStmt{}, + } +} + +func (a *ASTer) перезаписатьФайл() error { + err := переименоватьФайлСоВременем(a.fname) + if err != nil { + return errors.Join(err, errors.New("cant backup orig file")) + } + + f, err := os.Create(a.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) +} diff --git a/pkg/formatters/ast/ast_test.go b/pkg/formatters/ast/ast_test.go new file mode 100644 index 0000000..b5cbc5f --- /dev/null +++ b/pkg/formatters/ast/ast_test.go @@ -0,0 +1,68 @@ +package ast + +import ( + "os" + "path/filepath" + + "git.golang1.ru/softonik/godog" + "git.golang1.ru/softonik/godog/pkg/lib" + . "git.golang1.ru/softonik/godog_and_gomega" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/format" +) + +type testData struct { + tempdir string +} + +var ( + t *testData +) + +func resetTestData() { + t = &testData{} +} +func beforeSuite() { + CharactersAroundMismatchToInclude = 20 +} +func afterSuite() { +} + +func beforeScenario() { + resetTestData() + createTempDir() +} +func afterScenario() { + rmTempDir() +} + +func createTempDir() { + dir := filepath.Clean(os.TempDir()) + tempDir, err := os.MkdirTemp(dir, "test-") + Ok(err) + t.tempdir = tempDir + err = os.Chdir(t.tempdir) + Ok(err) +} + +func rmTempDir() { + err := os.RemoveAll(t.tempdir) + Ok(err) +} + +// ----------------------- + +func файл(fname string, content *godog.DocString) { + err := lib.WriteFile(fname, content.Content) + Ok(err) +} +func добавленаТестоваяФункция(func_name string) { + err := ДобавитьТестовуюФункцию(func_name) + Ok(err) +} +func файлДолженСодержать(fname string, content *godog.DocString) { + cont, err := lib.ReadFile(fname) + Ok(err) + + Ω(cont).To(Be(content.Content)) +} diff --git a/pkg/formatters/ast/features/app.feature b/pkg/formatters/ast/features/app.feature new file mode 100644 index 0000000..b4a1492 --- /dev/null +++ b/pkg/formatters/ast/features/app.feature @@ -0,0 +1,18 @@ +# language: ru +Функционал: AST-редактир go-файлов + Сгенерированные функции автоматически добавляются в тест-файл текущего пакета + + Сценарий: Добавление сгенерированных функций в тест-файл + Дано Файл "mypkg_test.go": + ``` + package mypkg + ``` + Когда Добавлена тестовая функция "ПриветМир" + То Файл "mypkg_test.go" должен содержать: + ``` + package mypkg + + func ПриветМир() { + } + + ``` diff --git a/pkg/formatters/ast/global.go b/pkg/formatters/ast/global.go new file mode 100644 index 0000000..e254d17 --- /dev/null +++ b/pkg/formatters/ast/global.go @@ -0,0 +1,10 @@ +package ast + +func ДобавитьТестовуюФункцию(f string) error { + a, err := NewASTer() + if err != nil { + return err + } + + return a.ДобавитьТестовуюФункцию(f) +} diff --git a/pkg/formatters/ast/helpers.go b/pkg/formatters/ast/helpers.go new file mode 100644 index 0000000..77c3922 --- /dev/null +++ b/pkg/formatters/ast/helpers.go @@ -0,0 +1,50 @@ +package ast + +import ( + "fmt" + "go/parser" + "go/token" + "os" + "path/filepath" + "time" + + "errors" +) + +func получитьИмяGoПакетаВЭтойДире() (pkg_name string, err_ret error) { + err_ret = errors.New("not found") + + filepath.WalkDir(".", func(path string, info os.DirEntry, err error) error { + if err != nil { + return err + } + + if !info.IsDir() && filepath.Ext(path) == ".go" { + pkg, err := получитьИмяGoПакетаФайла(path) + if err != nil { + return err + } + pkg_name = pkg + err_ret = nil + return filepath.SkipDir + } + return nil + }) + + return +} +func получитьИмяGoПакетаФайла(fname string) (string, error) { + fset := token.NewFileSet() + node, err := parser.ParseFile(fset, fname, nil, parser.PackageClauseOnly) + if err != nil { + return "", err + } + return node.Name.Name, nil +} + +func переименоватьФайлСоВременем(fname string) error { + currentTime := time.Now() + timestamp := currentTime.Format("20060102_150405") + new_fname := fmt.Sprintf("%v_%v", fname, timestamp) + return os.Rename(fname, new_fname) +} diff --git a/pkg/formatters/ast/init_test.go b/pkg/formatters/ast/init_test.go new file mode 100644 index 0000000..5b82c6f --- /dev/null +++ b/pkg/formatters/ast/init_test.go @@ -0,0 +1,48 @@ +package ast + +import ( + "context" + "os" + "testing" + + "git.golang1.ru/softonik/godog" + "git.golang1.ru/softonik/godog/colors" + . "git.golang1.ru/softonik/godog_and_gomega" +) + +func InitializeScenario(ctx *godog.ScenarioContext) { + ctx.Step(`^Файл "([^"]*)":$`, файл) + 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) +} + +func InitializeSuite(tsc *godog.TestSuiteContext) { + tsc.BeforeSuite(beforeSuite) + tsc.AfterSuite(afterSuite) +} + +func TestMain(m *testing.M) { + var opts = godog.Options{ + Output: colors.Colored(os.Stdout), + Strict: true, + StopOnFailure: true, + } + r := godog.TestSuite{ + Name: "app", + TestSuiteInitializer: InitializeSuite, + ScenarioInitializer: InitializeScenario, + Options: &opts, + }.Run() + os.Exit(r) +}