From 3e3d1cb988b457a55425a7334f8f5a91cc939666 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Mon, 2 Mar 2026 14:31:26 +0200 Subject: gotest: extract Go codegen heuristics from lsp handlers (task 406) --- internal/gotest/heuristics.go | 113 +++++++++++++++++++++++++++++++++++++ internal/gotest/heuristics_test.go | 39 +++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 internal/gotest/heuristics.go create mode 100644 internal/gotest/heuristics_test.go (limited to 'internal/gotest') diff --git a/internal/gotest/heuristics.go b/internal/gotest/heuristics.go new file mode 100644 index 0000000..d7bc8b7 --- /dev/null +++ b/internal/gotest/heuristics.go @@ -0,0 +1,113 @@ +package gotest + +import "strings" + +// ParsePackageName returns the package name from file lines, or empty when missing. +func ParsePackageName(lines []string) string { + for _, ln := range lines { + t := strings.TrimSpace(ln) + if !strings.HasPrefix(t, "package ") { + continue + } + name := strings.TrimSpace(strings.TrimPrefix(t, "package ")) + if i := strings.Index(name, " "); i >= 0 { + name = name[:i] + } + if i := strings.Index(name, "\t"); i >= 0 { + name = name[:i] + } + if i := strings.Index(name, "//"); i >= 0 { + name = strings.TrimSpace(name[:i]) + } + return name + } + return "" +} + +// FindFunctionAtLine finds the function enclosing or preceding idx. +// It returns start/end line indexes or -1/-1 when no function is found. +func FindFunctionAtLine(lines []string, idx int) (int, int) { + if len(lines) == 0 { + return -1, -1 + } + if idx < 0 { + idx = 0 + } + if idx >= len(lines) { + idx = len(lines) - 1 + } + + start := -1 + for i := idx; i >= 0; i-- { + if strings.Contains(lines[i], "func ") { + start = i + break + } + if strings.Contains(lines[i], "}") { + break + } + } + if start == -1 { + return -1, -1 + } + + depth := 0 + seenOpen := false + for i := start; i < len(lines); i++ { + line := lines[i] + for j := 0; j < len(line); j++ { + switch line[j] { + case '{': + depth++ + seenOpen = true + case '}': + if depth > 0 { + depth-- + } + if seenOpen && depth == 0 { + return start, i + } + } + } + } + if !seenOpen { + return start, start + } + return start, -1 +} + +// DeriveFuncName extracts function or method name from Go source snippet. +func DeriveFuncName(code string) string { + line := strings.TrimSpace(firstLine(code)) + if !strings.HasPrefix(line, "func ") { + return "" + } + rest := strings.TrimSpace(strings.TrimPrefix(line, "func ")) + if strings.HasPrefix(rest, "(") { + if i := strings.Index(rest, ")"); i >= 0 && i+1 < len(rest) { + rest = strings.TrimSpace(rest[i+1:]) + } + } + if i := strings.Index(rest, "("); i > 0 { + return strings.TrimSpace(rest[:i]) + } + return "" +} + +// ExportName upper-cases the first character for use in Test* names. +func ExportName(name string) string { + if name == "" { + return "" + } + r := []rune(name) + first := string(r[0]) + r[0] = []rune(strings.ToUpper(first))[0] + return string(r) +} + +func firstLine(s string) string { + if i := strings.IndexByte(s, '\n'); i >= 0 { + return s[:i] + } + return s +} diff --git a/internal/gotest/heuristics_test.go b/internal/gotest/heuristics_test.go new file mode 100644 index 0000000..831262d --- /dev/null +++ b/internal/gotest/heuristics_test.go @@ -0,0 +1,39 @@ +package gotest + +import "testing" + +func TestParsePackageName(t *testing.T) { + lines := []string{"// comment", "package mypkg // trailing"} + if got := ParsePackageName(lines); got != "mypkg" { + t.Fatalf("got %q", got) + } + if got := ParsePackageName([]string{"no package"}); got != "" { + t.Fatalf("expected empty package name") + } +} + +func TestFindFunctionAtLine_NoBody(t *testing.T) { + lines := []string{"func X(a int)", "// comment"} + start, end := FindFunctionAtLine(lines, 0) + if start != 0 || end != 0 { + t.Fatalf("expected single-line prototype, got %d,%d", start, end) + } +} + +func TestDeriveFuncName(t *testing.T) { + if got := DeriveFuncName("func Sum(a int) int { return a }"); got != "Sum" { + t.Fatalf("got %q", got) + } + if got := DeriveFuncName("func (t *Type) Method(x int) {}"); got != "Method" { + t.Fatalf("got %q", got) + } +} + +func TestExportName(t *testing.T) { + if got := ExportName("sum"); got != "Sum" { + t.Fatalf("got %q", got) + } + if got := ExportName("Sum"); got != "Sum" { + t.Fatalf("got %q", got) + } +} -- cgit v1.2.3