miller/pkg/mlrval/mlrval_output.go
John Kerl 2bec10fc54
Fix govet lint findings; fix masked unset-on-array error path (#2129)
* plans/lintfixes.md

* plans/lintfixes.md

* Fix remaining govet lint findings

- Rename MarshalJSON -> FormatAsJSON on Mlrval and Mlrmap (govet
  stdmethods): the methods shadowed json.Marshaler with an
  incompatible signature.
- Remove unreachable return after exhaustive if-else in
  pkg/mlrval/mlrval_collections.go (govet unreachable).
- Update plans/lintfixes.md with current status: 84 findings remain
  (50 errcheck, 34 staticcheck).

Part of #2109.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Return nil on successful single-index array unset

removeIndexedOnArray removed the element on the in-bounds path but
then fell through to return an "array index out of bounds for unset"
error, so the success path never returned nil. Callers currently
ignore the error, which masked this; return nil on success so that
upcoming errcheck fixes can propagate the error meaningfully. This
matches removeIndexedOnMap, which returns nil on success.

Add unit tests for RemoveIndexed on arrays.

Part of #2109.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 10:31:08 -04:00

141 lines
4.1 KiB
Go

package mlrval
import (
"fmt"
"os"
"reflect"
"strconv"
)
// Must have non-pointer receiver in order to implement the fmt.Stringer
// interface to make this printable via fmt.Println et al. However, that
// results in a needless copy of the Mlrval. So, we intentionally use pointer
// receiver, and if we need to print to stdout, we can fmt.Printf with "%s" and
// mv.String().
func (mv *Mlrval) String() string {
// TODO: comment re deferral -- important perf effect!
// if mv.IsFloat() && floatOutputFormatter != nil
// if mv.mvtype == MT_FLOAT && floatOutputFormatter != nil {
//if floatOutputFormatter != nil && (mv.mvtype == MT_FLOAT || mv.mvtype == MT_PENDING) {
if floatOutputFormatter != nil && mv.Type() == MT_FLOAT {
// Use the format string from global --ofmt, if supplied
return floatOutputFormatter.FormatFloat(mv.intf.(float64))
}
// TODO: track dirty-flag checking / somesuch.
// At present it's cumbersome to check if an array or map has been modified
// and it's safest to always recompute the string-rep.
if mv.IsArrayOrMap() {
mv.printrepValid = false
}
mv.setPrintRep()
return mv.printrep
}
// OriginalString gets the field value as a string regardless of --ofmt specification.
// E.g if the ofmt is "%.4f" and input is 3.1415926535, OriginalString() will return
// "3.1415926535" while String() will return "3.1416".
func (mv *Mlrval) OriginalString() string {
if mv.printrepValid {
return mv.printrep
}
return mv.String()
}
// StringMaybeQuoted Returns strings double-quoted; all else not.
func (mv *Mlrval) StringMaybeQuoted() string {
output := mv.String()
if mv.mvtype == MT_VOID || mv.mvtype == MT_STRING {
return `"` + output + `"`
}
return output
}
// See mlrval.go for more about JIT-formatting of string backings
func (mv *Mlrval) setPrintRep() {
if !mv.printrepValid {
switch mv.mvtype {
case MT_PENDING:
// Should not have gotten outside of the JSON decoder, so flag this
// clearly visually if it should (buggily) slip through to
// user-level visibility.
mv.printrep = "(bug-if-you-see-this:case=3)" // xxx constdef at top of file
case MT_ERROR:
mv.printrep = "(error)" // xxx constdef at top of file
case MT_ABSENT:
// Callsites should be using absence to do non-assigns, so flag
// this clearly visually if it should (buggily) slip through to
// user-level visibility.
mv.printrep = "(bug-if-you-see-this:case=4)" // xxx constdef at top of file
case MT_VOID:
mv.printrep = "" // xxx constdef at top of file
case MT_STRING:
break
case MT_INT:
mv.printrep = strconv.FormatInt(mv.intf.(int64), 10)
case MT_FLOAT:
mv.printrep = strconv.FormatFloat(mv.intf.(float64), 'f', -1, 64)
case MT_BOOL:
if mv.intf.(bool) {
mv.printrep = "true"
} else {
mv.printrep = "false"
}
case MT_ARRAY:
bytes, err := mv.FormatAsJSON(JSON_MULTILINE, false)
// maybe just InternalCodingErrorIf(err != nil)
if err != nil {
fmt.Fprintf(os.Stderr, "mlr: %v\n", err)
os.Exit(1)
}
mv.printrep = string(bytes)
case MT_MAP:
bytes, err := mv.FormatAsJSON(JSON_MULTILINE, false)
// maybe just InternalCodingErrorIf(err != nil)
if err != nil {
fmt.Fprintf(os.Stderr, "mlr: %v\n", err)
os.Exit(1)
}
mv.printrep = string(bytes)
}
mv.printrepValid = true
}
}
// StringifyValuesRecursively is nominally for the `--jvquoteall` flag.
func (mv *Mlrval) StringifyValuesRecursively() {
switch mv.mvtype {
case MT_ARRAY:
for i := range mv.intf.([]*Mlrval) {
mv.intf.([]*Mlrval)[i].StringifyValuesRecursively()
}
case MT_MAP:
for pe := mv.intf.(*Mlrmap).Head; pe != nil; pe = pe.Next {
pe.Value.StringifyValuesRecursively()
}
default:
mv.SetFromString(mv.String())
}
}
func (mv *Mlrval) ShowSizes() {
fmt.Printf("TOTAL %p %d\n", mv, reflect.TypeFor[Mlrval]().Size())
//fmt.Printf("mv.intf %p %d\n", &mv.intf, reflect.TypeOf(mv.intf).Size())
fmt.Printf("mv.printrep %p %d\n", &mv.printrep, reflect.TypeFor[string]().Size())
fmt.Printf("mv.printrepValid %p %d\n", &mv.printrepValid, reflect.TypeFor[bool]().Size())
fmt.Printf("mv.mvtype %p %d\n", &mv.mvtype, reflect.TypeFor[MVType]().Size())
}