mirror of
https://github.com/go-task/task.git
synced 2026-08-29 01:58:56 +02:00
fix: accept []string and []int in matrix refs (#2956)
Co-authored-by: no-hup <shauryaj.finance@gmail.com>
This commit is contained in:
@@ -929,6 +929,7 @@ func TestForCmds(t *testing.T) {
|
||||
{name: "loop-explicit"},
|
||||
{name: "loop-matrix"},
|
||||
{name: "loop-matrix-ref"},
|
||||
{name: "loop-matrix-ref-computed"},
|
||||
{
|
||||
name: "loop-matrix-ref-error",
|
||||
wantErr: true,
|
||||
|
||||
11
testdata/for/cmds/Taskfile.yml
vendored
11
testdata/for/cmds/Taskfile.yml
vendored
@@ -4,6 +4,7 @@ vars:
|
||||
OS_VAR: ["windows", "linux", "darwin"]
|
||||
ARCH_VAR: ["amd64", "arm64"]
|
||||
NOT_A_LIST: "not a list"
|
||||
OS_CSV: "windows,linux,darwin"
|
||||
|
||||
tasks:
|
||||
# Loop over a list of values
|
||||
@@ -30,6 +31,16 @@ tasks:
|
||||
ref: .ARCH_VAR
|
||||
cmd: echo "{{.ITEM.OS}}/{{.ITEM.ARCH}}"
|
||||
|
||||
loop-matrix-ref-computed:
|
||||
cmds:
|
||||
- for:
|
||||
matrix:
|
||||
OS:
|
||||
ref: 'splitList "," .OS_CSV'
|
||||
ARCH:
|
||||
ref: .ARCH_VAR
|
||||
cmd: echo "{{.ITEM.OS}}/{{.ITEM.ARCH}}"
|
||||
|
||||
loop-matrix-ref-error:
|
||||
cmds:
|
||||
- for:
|
||||
|
||||
6
testdata/for/cmds/testdata/TestForCmds-loop-matrix-ref-computed.golden
vendored
Normal file
6
testdata/for/cmds/testdata/TestForCmds-loop-matrix-ref-computed.golden
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
windows/amd64
|
||||
windows/arm64
|
||||
linux/amd64
|
||||
linux/arm64
|
||||
darwin/amd64
|
||||
darwin/arm64
|
||||
25
variables.go
25
variables.go
@@ -355,6 +355,22 @@ func asAnySlice[T any](slice []T) []any {
|
||||
return ret
|
||||
}
|
||||
|
||||
// resolvedAsAnySlice converts a value resolved from a reference into a []any.
|
||||
// A reference does not always resolve to a []any: lists declared in a Taskfile
|
||||
// do, but template functions such as `keys` and `splitList` return a []string.
|
||||
// The accepted types mirror the list types itemsFromFor already supports.
|
||||
func resolvedAsAnySlice(v any) ([]any, bool) {
|
||||
switch value := v.(type) {
|
||||
case []any:
|
||||
return value, true
|
||||
case []string:
|
||||
return asAnySlice(value), true
|
||||
case []int:
|
||||
return asAnySlice(value), true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func itemsFromFor(
|
||||
f *ast.For,
|
||||
dir string,
|
||||
@@ -476,12 +492,11 @@ func resolveMatrixRefs(matrix *ast.Matrix, cache *templater.Cache) (*ast.Matrix,
|
||||
if cache.Err() != nil {
|
||||
return nil, cache.Err()
|
||||
}
|
||||
switch value := v.(type) {
|
||||
case []any:
|
||||
row.Value = value
|
||||
default:
|
||||
value, ok := resolvedAsAnySlice(v)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("matrix reference %q must resolve to a list", row.Ref)
|
||||
}
|
||||
row.Value = value
|
||||
}
|
||||
}
|
||||
return resolved, nil
|
||||
@@ -499,7 +514,7 @@ func resolveEnumRefs(requires *ast.Requires, cache *templater.Cache) error {
|
||||
if cache.Err() != nil {
|
||||
return cache.Err()
|
||||
}
|
||||
arr, ok := resolved.([]any)
|
||||
arr, ok := resolvedAsAnySlice(resolved)
|
||||
if !ok {
|
||||
return fmt.Errorf("enum reference %q must resolve to a list", v.Enum.Ref)
|
||||
}
|
||||
|
||||
@@ -43,3 +43,52 @@ func TestResolveMatrixRefsDoesNotMutateInput(t *testing.T) {
|
||||
require.Nil(t, orig.Value, "input matrix was mutated: Ref rows must be resolved into a copy")
|
||||
require.Equal(t, ".ARCH_VAR", orig.Ref, "input matrix Ref was altered")
|
||||
}
|
||||
|
||||
// TestResolveMatrixRefsListTypes is a regression test for #2544. A `ref:` is
|
||||
// evaluated as a template expression, so it does not always resolve to a
|
||||
// []any: a list declared in a Taskfile does, but template functions such as
|
||||
// `keys` and `splitList` return a []string. Resolving a ref used to type
|
||||
// assert []any, so those references failed with "must resolve to a list" even
|
||||
// though the value was a list. The accepted types mirror the ones
|
||||
// itemsFromFor supports for `for: var:`.
|
||||
func TestResolveMatrixRefsListTypes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
value any
|
||||
want []any
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "any slice", value: []any{"amd64", "arm64"}, want: []any{"amd64", "arm64"}},
|
||||
{name: "string slice", value: []string{"amd64", "arm64"}, want: []any{"amd64", "arm64"}},
|
||||
{name: "int slice", value: []int{1, 2}, want: []any{1, 2}},
|
||||
{name: "string", value: "not a list", wantErr: true},
|
||||
{name: "map", value: map[string]any{"key": "value"}, wantErr: true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
matrix := ast.NewMatrix(
|
||||
&ast.MatrixElement{Key: "ARCH", Value: &ast.MatrixRow{Ref: ".ARCH_VAR"}},
|
||||
)
|
||||
|
||||
vars := ast.NewVars()
|
||||
vars.Set("ARCH_VAR", ast.Var{Value: test.value})
|
||||
cache := &templater.Cache{Vars: vars}
|
||||
|
||||
resolved, err := resolveMatrixRefs(matrix, cache)
|
||||
if test.wantErr {
|
||||
require.ErrorContains(t, err, `matrix reference ".ARCH_VAR" must resolve to a list`)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
row, ok := resolved.Get("ARCH")
|
||||
require.True(t, ok, "ARCH row missing from resolved matrix")
|
||||
require.Equal(t, test.want, row.Value)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user