Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@ Makefile
# local test directory
docker-compose.yml
tmp

staticlint
179 changes: 179 additions & 0 deletions cmd/reset/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package main

import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"log"
"os"
"path/filepath"
"strings"

"golang.org/x/tools/go/packages"
)

func main() {
// Настройка конфигурации.
cfg := &packages.Config{
Mode: packages.NeedName | packages.NeedFiles | packages.NeedSyntax | packages.NeedTypes,
Tests: false,
Fset: token.NewFileSet(),
// заставляет парсер сохранять комментарии
ParseFile: func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) {
return parser.ParseFile(fset, filename, src, parser.ParseComments)
},
}

fmt.Println("Сканирую пакеты...")
// parser.ParseDir устарел
pkgs, err := packages.Load(cfg, "./...")
if err != nil {
log.Fatal(err)
}

for _, pkg := range pkgs {
if len(pkg.Errors) > 0 {
fmt.Printf("Ошибки в пакете %s: %v\n", pkg.PkgPath, pkg.Errors)
continue
}
processPackage(pkg)
}
}

func hasResetComment(doc *ast.CommentGroup) bool {
if doc == nil {
return false
}
for _, c := range doc.List {
if strings.Contains(c.Text, "// generate:reset") {
return true
}
}
return false
}

func processPackage(pkg *packages.Package) {
var buf bytes.Buffer
hasMatches := false

// Перебираем файлы в пакете
for _, file := range pkg.Syntax {
for _, decl := range file.Decls {
genDecl, ok := decl.(*ast.GenDecl)
// Ищем объявления TYPE
if !ok || genDecl.Tok != token.TYPE {
continue
}

// Проверяем наличие комментария
if !hasResetComment(genDecl.Doc) {
continue
}

for _, spec := range genDecl.Specs {
typeSpec, ok := spec.(*ast.TypeSpec)
if !ok {
continue
}

structType, ok := typeSpec.Type.(*ast.StructType)
if !ok {
continue
}

if !hasMatches {
buf.WriteString(fmt.Sprintf("// Code generated by reset-gen. DO NOT EDIT.\npackage %s\n\n", pkg.Name))
hasMatches = true
}

structName := typeSpec.Name.Name
buf.WriteString(fmt.Sprintf("func (s *%s) Reset() {\n", structName))

// Генерация логики для каждого поля
for _, field := range structType.Fields.List {
for _, name := range field.Names {
genFieldReset(&buf, name.Name, field.Type)
}
}

buf.WriteString("}\n\n")
}
}
}

if hasMatches {
formatted, err := format.Source(buf.Bytes())
if err != nil {
fmt.Printf("Ошибка форматирования: %v\n", err)
formatted = buf.Bytes()
}

dir := filepath.Dir(pkg.GoFiles[0])
outputPath := filepath.Join(dir, "reset.gen.go")
os.WriteFile(outputPath, formatted, 0644)
fmt.Printf("Сгенерирован файл: %s\n", outputPath)
}
}

// genFieldReset записывает логику сброса в зависимости от типа поля
func genFieldReset(buf *bytes.Buffer, fieldName string, fieldType ast.Expr) {
switch t := fieldType.(type) {
case *ast.ArrayType:
// Правило: слайсы обрезаются, но не зануляются
buf.WriteString(fmt.Sprintf("\ts.%s = s.%s[:0]\n", fieldName, fieldName))

case *ast.MapType:
// Правило: мапы очищаются через clear()
buf.WriteString(fmt.Sprintf("\tclear(s.%s)\n", fieldName))

case *ast.StarExpr:
// Правило: не nil указатели сбрасывают свои значения
buf.WriteString(fmt.Sprintf("\tif s.%s != nil {\n\t\ts.%s.Reset()\n\t}\n", fieldName, fieldName))

case *ast.Ident:
// Примитивы (int, string, bool) или вложенные структуры в том же пакете
if isPrimitive(t.Name) {
buf.WriteString(fmt.Sprintf("\ts.%s = %s\n", fieldName, getZeroValue(t.Name)))
} else {
// Reset() должен быть реализован для вложенных структур
buf.WriteString(fmt.Sprintf("\ts.%s.Reset()\n", fieldName))
}

case *ast.SelectorExpr:
// Другие пакеты (например, time.Time)
// Вариантов типа бесконечно много, предполагаю что у них свой Reset
buf.WriteString(fmt.Sprintf("\ts.%s.Reset()\n", fieldName))

default:
// Универсальный сброс к zero value для всего остального
buf.WriteString(fmt.Sprintf("\ts.%s = *new(%s)\n", fieldName, "interface{}"))
}
}

func isPrimitive(name string) bool {
switch name {
case "bool", "string", "int", "int8", "int16", "int32", "int64",
"uint", "uint8", "uint16", "uint32", "uint64", "uintptr",
"byte", "rune", "float32", "float64", "complex64", "complex128":
return true
}
return false
}

func getZeroValue(name string) string {
switch name {
case "bool":
return "false"
case "string":
return `""`
case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "uintptr", "byte", "rune":
return "0"
case "float32", "float64":
return "0.0"
default:
return "nil"
}
}
15 changes: 15 additions & 0 deletions cmd/shortener/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"context"
"fmt"
"log"
"os"
"os/signal"
Expand All @@ -11,7 +12,21 @@ import (
"github.com/anon-d/urlshortener/internal/app"
)

var (
buildVersion string
buildDate string
buildCommit string
)

func checkLinkVar(variable string) string {
if variable == "" {
return "N/A"
}
return variable
}

func main() {
fmt.Printf("Build version: %s\nBuild date: %s\nBuild commit: %s\n", checkLinkVar(buildVersion), checkLinkVar(buildDate), checkLinkVar(buildCommit))
application, err := app.New()
if err != nil {
log.Fatalf("Error initializing application.\n%s", err.Error())
Expand Down
20 changes: 15 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@ require (
github.com/gin-contrib/cors v1.7.6
github.com/gin-gonic/gin v1.11.0
github.com/jackc/pgx/v5 v5.8.0
github.com/kisielk/errcheck v1.10.0
github.com/pressly/goose/v3 v3.26.0
github.com/timakin/bodyclose v0.0.0-20260129054331-73d1f95b84b4
go.uber.org/zap v1.27.1
golang.org/x/tools v0.43.0
honnef.co/go/tools v0.7.0
)

require (
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic v1.14.2 // indirect
github.com/bytedance/sonic/loader v0.4.0 // indirect
Expand All @@ -22,6 +27,8 @@ require (
github.com/go-playground/validator/v10 v10.28.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
github.com/gostaticanalysis/comment v1.4.2 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
Expand All @@ -41,10 +48,13 @@ require (
go.uber.org/mock v0.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.23.0 // indirect
golang.org/x/crypto v0.46.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.32.0 // indirect
golang.org/x/crypto v0.49.0 // indirect
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 // indirect
golang.org/x/mod v0.34.0 // indirect
golang.org/x/net v0.52.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect
google.golang.org/protobuf v1.36.10 // indirect
)
Loading
Loading