mirror of
https://github.com/mikefarah/yq.git
synced 2026-08-24 00:04:50 +08:00
Merge master into fix/hcl-nonstring-key-panic-2795
Resolve conflict in decoder_hcl.go by keeping typed object keys via convertCtyValueToNode (preserves int/bool/float keys in YAML) rather than coercing all keys to strings. Retain PR test case for integer key with empty object value, adapted to typed key output. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -39,7 +39,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
|
||||
uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
@@ -73,6 +73,6 @@ jobs:
|
||||
# Upload the results to GitHub's code scanning dashboard (optional).
|
||||
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||
uses: github/codeql-action/upload-sarif@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
@@ -3,7 +3,7 @@ module github.com/mikefarah/yq/v4
|
||||
require (
|
||||
github.com/a8m/envsubst v1.4.3
|
||||
github.com/alecthomas/participle/v2 v2.1.4
|
||||
github.com/alecthomas/repr v0.5.2
|
||||
github.com/alecthomas/repr v0.5.4
|
||||
github.com/dimchansky/utfbom v1.1.1
|
||||
github.com/elliotchance/orderedmap v1.8.0
|
||||
github.com/fatih/color v1.19.0
|
||||
@@ -12,7 +12,7 @@ require (
|
||||
github.com/goccy/go-yaml v1.19.2
|
||||
github.com/hashicorp/hcl/v2 v2.24.0
|
||||
github.com/jinzhu/copier v0.4.0
|
||||
github.com/magiconair/properties v1.8.10
|
||||
github.com/magiconair/properties v1.18.11
|
||||
github.com/pelletier/go-toml/v2 v2.4.3
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e
|
||||
github.com/spf13/cobra v1.10.2
|
||||
|
||||
@@ -6,8 +6,8 @@ github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8v
|
||||
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/participle/v2 v2.1.4 h1:W/H79S8Sat/krZ3el6sQMvMaahJ+XcM9WSI2naI7w2U=
|
||||
github.com/alecthomas/participle/v2 v2.1.4/go.mod h1:8tqVbpTX20Ru4NfYQgZf4mP18eXPTBViyMWiArNEgGI=
|
||||
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
|
||||
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/alecthomas/repr v0.5.4 h1:OVP7JEcuzU9CCDsT6STCr3rg17oQfWILtPWd2EG0uN4=
|
||||
github.com/alecthomas/repr v0.5.4/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
|
||||
github.com/apparentlymart/go-textseg/v17 v17.0.1 h1:bpMXRgQ5cEoRNuQke1a80/Nl6w3G5eoIbWo9f3gXkAs=
|
||||
@@ -40,8 +40,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
|
||||
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/magiconair/properties v1.18.11 h1:j5ozYZl0zCjG7ahMDH0GWIobOvvUzT0BdAguG0ViKy0=
|
||||
github.com/magiconair/properties v1.18.11/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/convert"
|
||||
)
|
||||
|
||||
type hclDecoder struct {
|
||||
@@ -253,22 +252,6 @@ func addBlockToMapping(parent *CandidateNode, block *hclsyntax.Block, src []byte
|
||||
}
|
||||
}
|
||||
|
||||
// hclKeyValueAsString converts an evaluated HCL key expression to its string
|
||||
// representation. HCL object keys may be non-string literals (e.g. a number
|
||||
// like `1`); calling AsString on those panics. Mirroring OpenTofu/Terragrunt,
|
||||
// we silently coerce non-string keys to their string form ("1" -> "1",
|
||||
// true -> "true") instead of panicking.
|
||||
func hclKeyValueAsString(keyVal cty.Value) (string, error) {
|
||||
if keyVal.Type() == cty.String {
|
||||
return keyVal.AsString(), nil
|
||||
}
|
||||
strVal, err := convert.Convert(keyVal, cty.String)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strVal.AsString(), nil
|
||||
}
|
||||
|
||||
func convertHclExprToNode(expr hclsyntax.Expression, src []byte) *CandidateNode {
|
||||
// handle literal values directly
|
||||
switch e := expr.(type) {
|
||||
@@ -317,8 +300,7 @@ func convertHclExprToNode(expr hclsyntax.Expression, src []byte) *CandidateNode
|
||||
it := v.ElementIterator()
|
||||
for it.Next() {
|
||||
key, val := it.Element()
|
||||
keyStr := key.AsString()
|
||||
keyNode := createStringScalarNode(keyStr)
|
||||
keyNode := convertCtyValueToNode(key)
|
||||
valNode := convertCtyValueToNode(val)
|
||||
m.AddKeyValueChild(keyNode, valNode)
|
||||
}
|
||||
@@ -355,20 +337,7 @@ func convertHclExprToNode(expr hclsyntax.Expression, src []byte) *CandidateNode
|
||||
}
|
||||
continue
|
||||
}
|
||||
keyStr, err := hclKeyValueAsString(keyVal)
|
||||
if err != nil {
|
||||
// fallback: try to extract key from source
|
||||
r := item.KeyExpr.Range()
|
||||
start := r.Start.Byte
|
||||
end := r.End.Byte
|
||||
if start >= 0 && end >= start && end <= len(src) {
|
||||
keyNode := createStringScalarNode(strings.TrimSpace(string(src[start:end])))
|
||||
valNode := convertHclExprToNode(item.ValueExpr, src)
|
||||
m.AddKeyValueChild(keyNode, valNode)
|
||||
}
|
||||
continue
|
||||
}
|
||||
keyNode := createStringScalarNode(keyStr)
|
||||
keyNode := convertCtyValueToNode(keyVal)
|
||||
valNode := convertHclExprToNode(item.ValueExpr, src)
|
||||
m.AddKeyValueChild(keyNode, valNode)
|
||||
}
|
||||
@@ -489,7 +458,7 @@ func convertCtyValueToNode(v cty.Value) *CandidateNode {
|
||||
it := v.ElementIterator()
|
||||
for it.Next() {
|
||||
key, val := it.Element()
|
||||
keyNode := createStringScalarNode(key.AsString())
|
||||
keyNode := convertCtyValueToNode(key)
|
||||
valNode := convertCtyValueToNode(val)
|
||||
m.AddKeyValueChild(keyNode, valNode)
|
||||
}
|
||||
|
||||
+37
-9
@@ -176,6 +176,41 @@ var hclFormatScenarios = []formatScenario{
|
||||
expected: "obj: {a: 1, b: \"two\"}\n",
|
||||
scenarioType: "decode",
|
||||
},
|
||||
{
|
||||
description: "object with integer keys",
|
||||
skipDoc: true,
|
||||
input: `obj = { 1 = "one", 2 = "two" }`,
|
||||
expected: "obj: {1: \"one\", 2: \"two\"}\n",
|
||||
scenarioType: "decode",
|
||||
},
|
||||
{
|
||||
description: "object with boolean keys",
|
||||
skipDoc: true,
|
||||
input: `obj = { (true) = "yes", (false) = "no" }`,
|
||||
expected: "obj: {true: \"yes\", false: \"no\"}\n",
|
||||
scenarioType: "decode",
|
||||
},
|
||||
{
|
||||
description: "object with float keys",
|
||||
skipDoc: true,
|
||||
input: `obj = { (3.14) = "pi" }`,
|
||||
expected: "obj: {3.14: \"pi\"}\n",
|
||||
scenarioType: "decode",
|
||||
},
|
||||
{
|
||||
description: "object with mixed scalar keys",
|
||||
skipDoc: true,
|
||||
input: `obj = { a = 1, 1 = "one", (true) = "yes" }`,
|
||||
expected: "obj: {a: 1, 1: \"one\", true: \"yes\"}\n",
|
||||
scenarioType: "decode",
|
||||
},
|
||||
{
|
||||
description: "nested object with integer keys",
|
||||
skipDoc: true,
|
||||
input: `config = { levels = { 1 = "debug", 2 = "info" } }`,
|
||||
expected: "config: {levels: {1: \"debug\", 2: \"info\"}}\n",
|
||||
scenarioType: "decode",
|
||||
},
|
||||
{
|
||||
description: "nested block",
|
||||
skipDoc: true,
|
||||
@@ -473,17 +508,10 @@ var hclFormatScenarios = []formatScenario{
|
||||
scenarioType: "roundtrip",
|
||||
},
|
||||
{
|
||||
description: "Non-string object keys are coerced to strings",
|
||||
description: "object with integer key and empty value",
|
||||
skipDoc: true,
|
||||
input: `intdict = { 1 = {} }`,
|
||||
expected: "intdict: {\"1\": {}}\n",
|
||||
scenarioType: "decode",
|
||||
},
|
||||
{
|
||||
description: "Mixed non-string object keys are coerced to strings",
|
||||
skipDoc: true,
|
||||
input: `d = { 1 = "a", 2 = "b", true = "c" }`,
|
||||
expected: "d: {\"1\": \"a\", \"2\": \"b\", \"true\": \"c\"}\n",
|
||||
expected: "intdict: {1: {}}\n",
|
||||
scenarioType: "decode",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -369,7 +369,7 @@ var tomlScenarios = []formatScenario{
|
||||
skipDoc: true,
|
||||
description: "blank",
|
||||
input: `A = "hello`,
|
||||
expectedError: `bad file 'sample.yml': basic string not terminated by "`,
|
||||
expectedError: `bad file 'sample.yml': unterminated basic string`,
|
||||
scenarioType: "decode-error",
|
||||
},
|
||||
{
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ func readStream(filename string) (io.Reader, error) {
|
||||
}
|
||||
|
||||
func writeString(writer io.Writer, txt string) error {
|
||||
_, errorWriting := writer.Write([]byte(txt))
|
||||
_, errorWriting := io.WriteString(writer, txt)
|
||||
return errorWriting
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/mikefarah/yq/v4/test"
|
||||
)
|
||||
|
||||
// plainWriter only implements io.Writer, so io.WriteString must fall back to Write.
|
||||
type plainWriter struct {
|
||||
buf bytes.Buffer
|
||||
}
|
||||
|
||||
func (w *plainWriter) Write(p []byte) (int, error) {
|
||||
return w.buf.Write(p)
|
||||
}
|
||||
|
||||
func TestWriteStringToStringWriter(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
writer := bufio.NewWriter(&buf)
|
||||
test.AssertResult(t, nil, writeString(writer, "hello world"))
|
||||
test.AssertResult(t, nil, writer.Flush())
|
||||
test.AssertResult(t, "hello world", buf.String())
|
||||
}
|
||||
|
||||
func TestWriteStringToPlainWriter(t *testing.T) {
|
||||
writer := &plainWriter{}
|
||||
test.AssertResult(t, nil, writeString(writer, "hello world"))
|
||||
test.AssertResult(t, "hello world", writer.buf.String())
|
||||
}
|
||||
|
||||
func TestWriteStringDoesNotAllocate(t *testing.T) {
|
||||
writer := bufio.NewWriter(io.Discard)
|
||||
allocations := testing.AllocsPerRun(100, func() {
|
||||
_ = writeString(writer, "hello world")
|
||||
})
|
||||
test.AssertResult(t, 0.0, allocations)
|
||||
}
|
||||
Reference in New Issue
Block a user