@@ -499,7 +499,11 @@ func checkDiscardedError(root *sitter.Node, source []byte, file string) []Review
499499 }
500500
501501 // Build a map of variable names to their declared types within this function.
502+ // For closures (func_literal), also include vars from enclosing scopes.
502503 varTypes := buildVarTypeMap (body , source )
504+ if fn .Type () == "func_literal" {
505+ mergeEnclosingVarTypes (fn , source , varTypes )
506+ }
503507
504508 // Find discarded calls in this function body.
505509 exprStmts := complexity .FindNodes (body , []string {"expression_statement" })
@@ -623,6 +627,30 @@ func buildVarTypeMap(body *sitter.Node, source []byte) map[string]string {
623627 return result
624628}
625629
630+ // mergeEnclosingVarTypes walks up from a func_literal to find the enclosing
631+ // function's variable declarations. This catches cases like:
632+ //
633+ // var rawContent strings.Builder
634+ // provider.GenerateStream(ctx, prompt, func(chunk string) {
635+ // rawContent.WriteString(chunk) // closure captures outer var
636+ // })
637+ func mergeEnclosingVarTypes (closureNode * sitter.Node , source []byte , varTypes map [string ]string ) {
638+ for n := closureNode .Parent (); n != nil ; n = n .Parent () {
639+ if n .Type () == "function_declaration" || n .Type () == "method_declaration" {
640+ body := n .ChildByFieldName ("body" )
641+ if body != nil {
642+ enclosing := buildVarTypeMap (body , source )
643+ for k , v := range enclosing {
644+ if _ , exists := varTypes [k ]; ! exists {
645+ varTypes [k ] = v
646+ }
647+ }
648+ }
649+ return
650+ }
651+ }
652+ }
653+
626654// splitSelector splits "b.WriteString" into ("b", "WriteString").
627655func splitSelector (fullName string ) (receiver , method string ) {
628656 idx := strings .LastIndex (fullName , "." )
0 commit comments