summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-09-03 17:15:04 +0300
committerPaul Buetow <paul@buetow.org>2025-09-03 17:15:04 +0300
commit23ea1749d303c1263e8a3d2393dee95d7914ddf7 (patch)
tree39ed6756070345420ca6efa7d414575b0c9efd94
parentd2ee730256c6ecfad7dd2a164d2bb822236b7b44 (diff)
chore: add scripts/scan_uncovered.go and generated UNITTESTSTOADD.md with recommendations for missing tests
-rw-r--r--UNITTESTSTOADD.md511
-rw-r--r--scripts/scan_uncovered.go187
2 files changed, 698 insertions, 0 deletions
diff --git a/UNITTESTSTOADD.md b/UNITTESTSTOADD.md
new file mode 100644
index 0000000..2ebcd02
--- /dev/null
+++ b/UNITTESTSTOADD.md
@@ -0,0 +1,511 @@
+# Unit tests to consider adding
+
+This report lists functions that do not appear to be referenced in test files in the same package directory. Recommendations are heuristic.
+
+## main (cmd/hexai-lsp)
+
+- cmd/hexai-lsp/main.go — func main
+ - exported: false complexity: 5 has-control: true
+ - recommendation: add test (non-trivial logic)
+
+## hexaicli (internal/hexaicli)
+
+- internal/hexaicli/run.go — func Run
+ - exported: true complexity: 5 has-control: true
+ - recommendation: add test (exported)
+- internal/hexaicli/run.go — func newClientFromConfig
+ - exported: false complexity: 6 has-control: true
+ - recommendation: add test (non-trivial logic)
+
+## llm (internal/llm)
+
+- internal/llm/copilot.go — method (copilotClient).Chat
+ - exported: true complexity: 24 has-control: true
+ - recommendation: add test (exported)
+- internal/llm/copilot.go — method (copilotClient).CodeCompletion
+ - exported: true complexity: 18 has-control: true
+ - recommendation: add test (exported)
+- internal/llm/copilot.go — method (copilotClient).DefaultModel
+ - exported: true complexity: 1 has-control: false
+ - recommendation: add test (exported)
+- internal/llm/copilot.go — method (copilotClient).Name
+ - exported: true complexity: 1 has-control: false
+ - recommendation: add test (exported)
+- internal/llm/copilot.go — func decodeCopilotChat
+ - exported: false complexity: 3 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/llm/copilot.go — method (copilotClient).ensureSession
+ - exported: false complexity: 19 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/llm/copilot.go — func handleCopilotNon2xx
+ - exported: false complexity: 6 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/llm/copilot.go — method (copilotClient).headersChat
+ - exported: false complexity: 3 has-control: false
+ - recommendation: add test (non-trivial logic)
+- internal/llm/copilot.go — method (copilotClient).headersGhost
+ - exported: false complexity: 3 has-control: false
+ - recommendation: add test (non-trivial logic)
+- internal/llm/copilot.go — func newCopilot
+ - exported: false complexity: 3 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/llm/copilot.go — func parseInt64
+ - exported: false complexity: 3 has-control: false
+ - recommendation: add test (non-trivial logic)
+- internal/llm/copilot.go — func parseJWTExp
+ - exported: false complexity: 8 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/llm/copilot.go — method (copilotClient).postJSON
+ - exported: false complexity: 4 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/llm/copilot.go — func randHex
+ - exported: false complexity: 4 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/llm/ollama.go — method (ollamaClient).Chat
+ - exported: true complexity: 20 has-control: true
+ - recommendation: add test (exported)
+- internal/llm/ollama.go — method (ollamaClient).ChatStream
+ - exported: true complexity: 18 has-control: true
+ - recommendation: add test (exported)
+- internal/llm/ollama.go — method (ollamaClient).DefaultModel
+ - exported: true complexity: 1 has-control: false
+ - recommendation: add test (exported)
+- internal/llm/ollama.go — method (ollamaClient).Name
+ - exported: true complexity: 1 has-control: false
+ - recommendation: add test (exported)
+- internal/llm/ollama.go — method (ollamaClient).doJSON
+ - exported: false complexity: 4 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/llm/ollama.go — func handleOllamaNon2xx
+ - exported: false complexity: 6 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/llm/ollama.go — method (ollamaClient).logStart
+ - exported: false complexity: 3 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/llm/ollama.go — func newOllama
+ - exported: false complexity: 3 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/llm/openai.go — method (openAIClient).Chat
+ - exported: true complexity: 21 has-control: true
+ - recommendation: add test (exported)
+- internal/llm/openai.go — method (openAIClient).ChatStream
+ - exported: true complexity: 18 has-control: true
+ - recommendation: add test (exported)
+- internal/llm/openai.go — method (openAIClient).DefaultModel
+ - exported: true complexity: 1 has-control: false
+ - recommendation: add test (exported)
+- internal/llm/openai.go — method (openAIClient).Name
+ - exported: true complexity: 1 has-control: false
+ - recommendation: add test (exported)
+- internal/llm/openai.go — func decodeOpenAIChat
+ - exported: false complexity: 3 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/llm/openai.go — method (openAIClient).doJSON
+ - exported: false complexity: 5 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/llm/openai.go — method (openAIClient).doJSONWithAccept
+ - exported: false complexity: 6 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/llm/openai.go — method (openAIClient).logStart
+ - exported: false complexity: 3 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/llm/openai.go — method (openAIClient).logf
+ - exported: false complexity: 1 has-control: false
+ - recommendation: optional (helper/trivial)
+- internal/llm/openai.go — func newOpenAI
+ - exported: false complexity: 3 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/llm/provider.go — func NewFromConfig
+ - exported: true complexity: 3 has-control: true
+ - recommendation: add test (exported)
+- internal/llm/provider.go — func WithMaxTokens
+ - exported: true complexity: 1 has-control: false
+ - recommendation: add test (exported)
+- internal/llm/provider.go — func WithModel
+ - exported: true complexity: 1 has-control: false
+ - recommendation: add test (exported)
+- internal/llm/provider.go — func WithStop
+ - exported: true complexity: 1 has-control: false
+ - recommendation: add test (exported)
+- internal/llm/provider.go — func WithTemperature
+ - exported: true complexity: 1 has-control: false
+ - recommendation: add test (exported)
+- internal/llm/util.go — func nilStringErr
+ - exported: false complexity: 1 has-control: false
+ - recommendation: optional (helper/trivial)
+
+## lsp (internal/lsp)
+
+- internal/lsp/context.go — method (Server).fullFileContext
+ - exported: false complexity: 3 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/document.go — method (Server).deleteDocument
+ - exported: false complexity: 3 has-control: false
+ - recommendation: add test (exported receiver)
+- internal/lsp/document.go — method (Server).getDocument
+ - exported: false complexity: 3 has-control: false
+ - recommendation: add test (exported receiver)
+- internal/lsp/document.go — func hasAny
+ - exported: false complexity: 2 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/lsp/document.go — method (Server).isDefiningNewFunction
+ - exported: false complexity: 10 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers.go — method (Server).compCacheTouchLocked
+ - exported: false complexity: 4 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers.go — method (Server).completionCacheGet
+ - exported: false complexity: 6 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers.go — method (Server).completionCacheKey
+ - exported: false complexity: 13 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers.go — method (Server).completionCachePut
+ - exported: false complexity: 6 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers.go — method (Server).fallbackCompletionItems
+ - exported: false complexity: 1 has-control: false
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers.go — method (Server).handle
+ - exported: false complexity: 2 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers.go — func instructionFromSelection
+ - exported: false complexity: 3 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/lsp/handlers.go — method (Server).isTriggerEvent
+ - exported: false complexity: 7 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers.go — method (Server).makeCompletionItems
+ - exported: false complexity: 6 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers.go — method (Server).reply
+ - exported: false complexity: 2 has-control: false
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_codeaction.go — method (Server).buildDocumentCodeAction
+ - exported: false complexity: 6 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_codeaction.go — method (Server).buildGoUnitTestCodeAction
+ - exported: false complexity: 9 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_codeaction.go — func deriveGoFuncName
+ - exported: false complexity: 7 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/lsp/handlers_codeaction.go — method (Server).diagnosticsInRange
+ - exported: false complexity: 7 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_codeaction.go — func exportName
+ - exported: false complexity: 4 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/lsp/handlers_codeaction.go — func fileExists
+ - exported: false complexity: 2 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/lsp/handlers_codeaction.go — func findGoFunctionAtLine
+ - exported: false complexity: 10 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/lsp/handlers_codeaction.go — method (Server).generateGoTestFunction
+ - exported: false complexity: 4 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_codeaction.go — func greaterPos
+ - exported: false complexity: 2 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/lsp/handlers_codeaction.go — method (Server).handleCodeAction
+ - exported: false complexity: 11 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_codeaction.go — method (Server).handleCodeActionResolve
+ - exported: false complexity: 4 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_codeaction.go — func lessPos
+ - exported: false complexity: 2 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/lsp/handlers_codeaction.go — method (Server).loadFileText
+ - exported: false complexity: 6 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_codeaction.go — func parseGoPackageName
+ - exported: false complexity: 2 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/lsp/handlers_codeaction.go — func rangesOverlap
+ - exported: false complexity: 5 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/lsp/handlers_codeaction.go — method (Server).resolveGoTest
+ - exported: false complexity: 30 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_completion.go — method (Server).buildCompletionMessages
+ - exported: false complexity: 5 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_completion.go — method (Server).buildDocString
+ - exported: false complexity: 1 has-control: false
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_completion.go — func extractTriggerInfo
+ - exported: false complexity: 4 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/lsp/handlers_completion.go — method (Server).handleCompletion
+ - exported: false complexity: 5 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_completion.go — method (Server).logCompletionContext
+ - exported: false complexity: 1 has-control: false
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_completion.go — func parseManualInvoke
+ - exported: false complexity: 4 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/lsp/handlers_completion.go — method (Server).postProcessCompletion
+ - exported: false complexity: 6 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_completion.go — method (Server).prefixHeuristicAllows
+ - exported: false complexity: 11 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_completion.go — method (Server).shouldSuppressForChatTriggerEOL
+ - exported: false complexity: 2 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_completion.go — method (Server).tryProviderNativeCompletion
+ - exported: false complexity: 18 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_completion.go — method (Server).waitForDebounce
+ - exported: false complexity: 3 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_completion.go — method (Server).waitForThrottle
+ - exported: false complexity: 4 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_document.go — method (Server).applyChatEdits
+ - exported: false complexity: 10 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_document.go — method (Server).buildChatHistory
+ - exported: false complexity: 10 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_document.go — method (Server).clientApplyEdit
+ - exported: false complexity: 6 has-control: false
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_document.go — method (Server).clientShowDocument
+ - exported: false complexity: 9 has-control: false
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_document.go — method (Server).deferShowDocument
+ - exported: false complexity: 1 has-control: false
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_document.go — method (Server).detectAndHandleChat
+ - exported: false complexity: 4 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_document.go — method (Server).docBeforeAfter
+ - exported: false complexity: 16 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_document.go — method (Server).handleDidChange
+ - exported: false complexity: 2 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_document.go — method (Server).handleDidClose
+ - exported: false complexity: 2 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_document.go — method (Server).handleDidOpen
+ - exported: false complexity: 2 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_document.go — method (Server).nextReqID
+ - exported: false complexity: 6 has-control: false
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_document.go — func stripTrailingTrigger
+ - exported: false complexity: 6 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/lsp/handlers_execute.go — method (Server).handleExecuteCommand
+ - exported: false complexity: 3 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_init.go — method (Server).handleExit
+ - exported: false complexity: 2 has-control: false
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_init.go — method (Server).handleInitialize
+ - exported: false complexity: 4 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_init.go — method (Server).handleInitialized
+ - exported: false complexity: 1 has-control: false
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_init.go — method (Server).handleShutdown
+ - exported: false complexity: 1 has-control: false
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_utils.go — func applyIndent
+ - exported: false complexity: 4 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/lsp/handlers_utils.go — func buildPrompts
+ - exported: false complexity: 4 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/lsp/handlers_utils.go — method (Server).collectPromptRemovalEdits
+ - exported: false complexity: 5 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_utils.go — func computeTextEditAndFilter
+ - exported: false complexity: 5 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/lsp/handlers_utils.go — func computeWordStart
+ - exported: false complexity: 3 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/lsp/handlers_utils.go — func extractRangeText
+ - exported: false complexity: 13 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/lsp/handlers_utils.go — func inParamList
+ - exported: false complexity: 4 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/lsp/handlers_utils.go — method (Server).incRecvCounters
+ - exported: false complexity: 4 has-control: false
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_utils.go — method (Server).incSentCounters
+ - exported: false complexity: 4 has-control: false
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_utils.go — func isBareDoubleSemicolon
+ - exported: false complexity: 5 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/lsp/handlers_utils.go — func isIdentBoundary
+ - exported: false complexity: 1 has-control: false
+ - recommendation: optional (helper/trivial)
+- internal/lsp/handlers_utils.go — func isIdentChar
+ - exported: false complexity: 1 has-control: false
+ - recommendation: optional (helper/trivial)
+- internal/lsp/handlers_utils.go — func labelForCompletion
+ - exported: false complexity: 3 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/lsp/handlers_utils.go — func leadingIndent
+ - exported: false complexity: 4 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/lsp/handlers_utils.go — func lineHasInlinePrompt
+ - exported: false complexity: 2 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/lsp/handlers_utils.go — method (Server).llmRequestOpts
+ - exported: false complexity: 3 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/handlers_utils.go — method (Server).logLLMStats
+ - exported: false complexity: 13 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/server.go — method (Server).Run
+ - exported: true complexity: 1 has-control: true
+ - recommendation: add test (exported)
+- internal/lsp/transport.go — method (Server).readMessage
+ - exported: false complexity: 7 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/lsp/transport.go — method (Server).writeMessage
+ - exported: false complexity: 5 has-control: true
+ - recommendation: add test (exported receiver)
+
+## main (scripts)
+
+- scripts/scan_uncovered.go — func isExportedIdent
+ - exported: false complexity: 3 has-control: true
+ - recommendation: add test (non-trivial logic)
+- scripts/scan_uncovered.go — func main
+ - exported: false complexity: 12 has-control: true
+ - recommendation: add test (non-trivial logic)
+- scripts/scan_uncovered.go — func recommend
+ - exported: false complexity: 4 has-control: true
+ - recommendation: add test (non-trivial logic)
+- scripts/scan_uncovered.go — func relPath
+ - exported: false complexity: 2 has-control: true
+ - recommendation: add test (non-trivial logic)
+- scripts/scan_uncovered.go — func typeString
+ - exported: false complexity: 1 has-control: false
+ - recommendation: optional (helper/trivial)
+
+## appconfig (internal/appconfig)
+
+- internal/appconfig/config.go — func Load
+ - exported: true complexity: 6 has-control: true
+ - recommendation: add test (exported)
+- internal/appconfig/config.go — func getConfigPath
+ - exported: false complexity: 3 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/appconfig/config.go — func loadFromEnv
+ - exported: false complexity: 27 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/appconfig/config.go — func loadFromFile
+ - exported: false complexity: 7 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/appconfig/config.go — method (App).mergeBasics
+ - exported: false complexity: 11 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/appconfig/config.go — method (App).mergeProviderFields
+ - exported: false complexity: 9 has-control: true
+ - recommendation: add test (exported receiver)
+- internal/appconfig/config.go — method (App).mergeWith
+ - exported: false complexity: 2 has-control: false
+ - recommendation: add test (exported receiver)
+- internal/appconfig/config.go — func newDefaultConfig
+ - exported: false complexity: 2 has-control: false
+ - recommendation: optional (helper/trivial)
+
+## hexailsp (internal/hexailsp)
+
+- internal/hexailsp/run.go — func buildClientIfNil
+ - exported: false complexity: 7 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/hexailsp/run.go — func ensureFactory
+ - exported: false complexity: 2 has-control: true
+ - recommendation: add test (non-trivial logic)
+- internal/hexailsp/run.go — func makeServerOptions
+ - exported: false complexity: 1 has-control: false
+ - recommendation: optional (helper/trivial)
+- internal/hexailsp/run.go — func normalizeLoggingConfig
+ - exported: false complexity: 2 has-control: true
+ - recommendation: add test (non-trivial logic)
+
+## logging (internal/logging)
+
+- internal/logging/chatlogger.go — method (ChatLogger).LogStart
+ - exported: true complexity: 4 has-control: true
+ - recommendation: add test (exported)
+- internal/logging/chatlogger.go — func NewChatLogger
+ - exported: true complexity: 1 has-control: false
+ - recommendation: add test (exported)
+- internal/logging/logging.go — func Bind
+ - exported: true complexity: 1 has-control: false
+ - recommendation: add test (exported)
+- internal/logging/logging.go — func Logf
+ - exported: true complexity: 3 has-control: true
+ - recommendation: add test (exported)
+- internal/logging/logging.go — func PreviewForLog
+ - exported: true complexity: 2 has-control: true
+ - recommendation: add test (exported)
+- internal/logging/logging.go — func SetLogPreviewLimit
+ - exported: true complexity: 1 has-control: false
+ - recommendation: add test (exported)
+
+## main (.)
+
+- Magefile.go — func Build
+ - exported: true complexity: 3 has-control: false
+ - recommendation: add test (exported)
+- Magefile.go — func BuildHexaiCLI
+ - exported: true complexity: 1 has-control: false
+ - recommendation: add test (exported)
+- Magefile.go — func BuildHexaiLSP
+ - exported: true complexity: 1 has-control: false
+ - recommendation: add test (exported)
+- Magefile.go — func Cover
+ - exported: true complexity: 10 has-control: true
+ - recommendation: add test (exported)
+- Magefile.go — func CoverAll
+ - exported: true complexity: 10 has-control: true
+ - recommendation: add test (exported)
+- Magefile.go — func Dev
+ - exported: true complexity: 3 has-control: true
+ - recommendation: add test (exported)
+- Magefile.go — func DevInstall
+ - exported: true complexity: 2 has-control: true
+ - recommendation: add test (exported)
+- Magefile.go — func Install
+ - exported: true complexity: 7 has-control: true
+ - recommendation: add test (exported)
+- Magefile.go — func Lint
+ - exported: true complexity: 1 has-control: false
+ - recommendation: add test (exported)
+- Magefile.go — func RunCLI
+ - exported: true complexity: 3 has-control: false
+ - recommendation: add test (exported)
+- Magefile.go — func Test
+ - exported: true complexity: 2 has-control: true
+ - recommendation: add test (exported)
+- Magefile.go — func Vet
+ - exported: true complexity: 1 has-control: false
+ - recommendation: add test (exported)
+- Magefile.go — func totalCoveragePercent
+ - exported: false complexity: 8 has-control: true
+ - recommendation: add test (non-trivial logic)
+- Magefile.go — func warnIfLowCoverage
+ - exported: false complexity: 6 has-control: true
+ - recommendation: add test (non-trivial logic)
+
+## main (cmd/hexai)
+
+- cmd/hexai/main.go — func main
+ - exported: false complexity: 4 has-control: true
+ - recommendation: add test (non-trivial logic)
+
diff --git a/scripts/scan_uncovered.go b/scripts/scan_uncovered.go
new file mode 100644
index 0000000..9eb22fd
--- /dev/null
+++ b/scripts/scan_uncovered.go
@@ -0,0 +1,187 @@
+// Command scan_uncovered analyzes Go source to find functions without tests
+// and recommends whether to add tests based on export status and basic complexity.
+package main
+
+import (
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+)
+
+type FuncInfo struct {
+ File string
+ Package string
+ Name string
+ Recv string // receiver type for methods
+ Exported bool
+ Complexity int // naive: number of statements in body
+ HasControl bool // has if/for/switch/select
+}
+
+func main() {
+ root := "."
+ dirs := map[string][]string{}
+ _ = filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
+ if err != nil { return nil }
+ // Skip vendor, tmp, .git, node_modules, build caches
+ base := filepath.Base(path)
+ if d.IsDir() {
+ if (path != "." && strings.HasPrefix(base, ".")) || base == "vendor" || base == "tmp" || base == "node_modules" || base == ".gocache" || base == ".gomodcache" {
+ return filepath.SkipDir
+ }
+ return nil
+ }
+ if strings.HasSuffix(path, ".go") && !strings.HasSuffix(path, "_test.go") {
+ dir := filepath.Dir(path)
+ dirs[dir] = append(dirs[dir], path)
+ }
+ return nil
+ })
+
+ fset := token.NewFileSet()
+ type missing struct{
+ pkg string
+ dir string
+ items []FuncInfo
+ }
+ var all []missing
+
+ for dir, files := range dirs {
+ // parse package name from any file
+ pkg := ""
+ var funcs []FuncInfo
+ for _, file := range files {
+ f, err := parser.ParseFile(fset, file, nil, 0)
+ if err != nil { continue }
+ if pkg == "" { pkg = f.Name.Name }
+ for _, d := range f.Decls {
+ fd, ok := d.(*ast.FuncDecl)
+ if !ok || fd.Name == nil { continue }
+ // Skip init/test helpers in non-test files? keep all funcs
+ info := FuncInfo{File: file, Package: pkg, Name: fd.Name.Name, Exported: ast.IsExported(fd.Name.Name)}
+ if fd.Recv != nil && len(fd.Recv.List) > 0 {
+ info.Recv = typeString(fd.Recv.List[0].Type)
+ }
+ if fd.Body != nil {
+ info.Complexity = len(fd.Body.List)
+ ast.Inspect(fd.Body, func(n ast.Node) bool {
+ switch n.(type) {
+ case *ast.IfStmt, *ast.ForStmt, *ast.RangeStmt, *ast.SwitchStmt, *ast.SelectStmt:
+ info.HasControl = true
+ }
+ return true
+ })
+ }
+ funcs = append(funcs, info)
+ }
+ }
+ if len(funcs) == 0 { continue }
+ // Gather test identifiers and test names in this dir
+ testIdents := make(map[string]struct{})
+ testNames := make(map[string]struct{})
+ _ = filepath.WalkDir(dir, func(p string, d os.DirEntry, err error) error {
+ if err != nil { return nil }
+ if d.IsDir() { return nil }
+ if !strings.HasSuffix(p, "_test.go") { return nil }
+ tf, err := parser.ParseFile(fset, p, nil, 0)
+ if err != nil { return nil }
+ for _, d := range tf.Decls {
+ if fd, ok := d.(*ast.FuncDecl); ok && fd.Name != nil {
+ if strings.HasPrefix(fd.Name.Name, "Test") { testNames[fd.Name.Name] = struct{}{} }
+ }
+ }
+ ast.Inspect(tf, func(n ast.Node) bool {
+ switch x := n.(type) {
+ case *ast.Ident:
+ testIdents[x.Name] = struct{}{}
+ case *ast.SelectorExpr:
+ testIdents[x.Sel.Name] = struct{}{}
+ }
+ return true
+ })
+ return nil
+ })
+
+ // Filter funcs missing coverage signal
+ var miss []FuncInfo
+ for _, fn := range funcs {
+ // Heuristic: if function name appears in tests, assume covered
+ if _, ok := testIdents[fn.Name]; ok { continue }
+ // Methods: also consider receiver type name for TestType_Method style
+ if fn.Recv != "" {
+ // e.g., TestType_Method often contains Method name as Ident; already covered by above
+ }
+ // Skip generated or trivial files? we'll decide in recommendation
+ miss = append(miss, fn)
+ }
+ if len(miss) == 0 { continue }
+ sort.Slice(miss, func(i, j int) bool {
+ if miss[i].File == miss[j].File {
+ return miss[i].Name < miss[j].Name
+ }
+ return miss[i].File < miss[j].File
+ })
+ all = append(all, missing{pkg: pkg, dir: dir, items: miss})
+ }
+
+ // Write markdown report to stdout
+ fmt.Println("# Unit tests to consider adding")
+ fmt.Println()
+ fmt.Println("This report lists functions that do not appear to be referenced in test files in the same package directory. Recommendations are heuristic.")
+ fmt.Println()
+ for _, m := range all {
+ fmt.Printf("## %s (%s)\n\n", m.pkg, m.dir)
+ for _, fn := range m.items {
+ rec := recommend(fn)
+ var sig string
+ if fn.Recv != "" { sig = fmt.Sprintf("method (%s).%s", fn.Recv, fn.Name) } else { sig = "func " + fn.Name }
+ fmt.Printf("- %s — %s\n", relPath(fn.File), sig)
+ fmt.Printf(" - exported: %t complexity: %d has-control: %t\n", fn.Exported, fn.Complexity, fn.HasControl)
+ fmt.Printf(" - recommendation: %s\n", rec)
+ }
+ fmt.Println()
+ }
+}
+
+func typeString(t ast.Expr) string {
+ switch x := t.(type) {
+ case *ast.Ident:
+ return x.Name
+ case *ast.StarExpr:
+ return typeString(x.X)
+ case *ast.IndexExpr:
+ return typeString(x.X)
+ case *ast.IndexListExpr:
+ return typeString(x.X)
+ case *ast.SelectorExpr:
+ return x.Sel.Name
+ default:
+ return ""
+ }
+}
+
+func recommend(fn FuncInfo) string {
+ // Strongly recommend for exported functions and methods on exported types
+ if fn.Exported { return "add test (exported)" }
+ if fn.Recv != "" && isExportedIdent(fn.Recv) { return "add test (exported receiver)" }
+ // Recommend for functions with control flow or non-trivial body
+ if fn.HasControl || fn.Complexity >= 3 { return "add test (non-trivial logic)" }
+ // Otherwise, optional
+ return "optional (helper/trivial)"
+}
+
+func isExportedIdent(name string) bool {
+ if name == "" { return false }
+ r := []rune(name)
+ return r[0] >= 'A' && r[0] <= 'Z'
+}
+
+func relPath(p string) string {
+ if rp, err := filepath.Rel(".", p); err == nil { return rp }
+ return p
+}