From 424b241bc7112bde7bfb906f8726b43bed0ecd1f Mon Sep 17 00:00:00 2001 From: Tom Hale Date: Sun, 24 May 2026 15:09:43 +0700 Subject: [PATCH] feat: validate ice arguments (#778) Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- tests/ice-validation.zunit | 278 +++++++++++++++++++++++++++++++++++++ zinit-install.zsh | 2 +- zinit.zsh | 35 +++++ 3 files changed, 314 insertions(+), 1 deletion(-) create mode 100644 tests/ice-validation.zunit diff --git a/tests/ice-validation.zunit b/tests/ice-validation.zunit new file mode 100644 index 00000000..c4e88937 --- /dev/null +++ b/tests/ice-validation.zunit @@ -0,0 +1,278 @@ +#!/usr/bin/env zunit +# vim:ft=zsh:sw=2:sts=2:et:foldmarker={,}:foldmethod=marker +# +# Tests for ice value validation in .zinit-validate-ice() +# + +@test 'as-valid-null' { + run zinit ice as'null' + assert $state equals 0 +} + +@test 'as-valid-command' { + run zinit ice as'command' + assert $state equals 0 +} + +@test 'as-valid-program' { + run zinit ice as'program' + assert $state equals 0 +} + +@test 'as-valid-completion' { + run zinit ice as'completion' + assert $state equals 0 +} + +@test 'as-empty' { + run zinit ice as'' + assert $state equals 0 +} + +@test 'as-invalid-none' { + run zinit ice as'none' + assert $state equals 0 + assert $output contains 'as' + assert $output contains 'none' + assert $output contains 'null' + assert $output contains 'program' +} + +@test 'as-invalid-banana' { + run zinit ice as'banana' + assert $state equals 0 + assert $output contains 'as' + assert $output contains 'banana' +} + +@test 'wait-valid-0' { + run zinit ice wait'0' + assert $state equals 0 + +} + +@test 'wait-valid-3' { + run zinit ice wait'3' + assert $state equals 0 + +} + +@test 'wait-valid-0a' { + run zinit ice wait'0a' + assert $state equals 0 + +} + +@test 'wait-valid-3b' { + run zinit ice wait'3b' + assert $state equals 0 + +} + +@test 'wait-valid-1c' { + run zinit ice wait'1c' + assert $state equals 0 + +} + +@test 'wait-valid-bang' { + run zinit ice wait'!0' + assert $state equals 0 + +} + +@test 'wait-valid-bang-a' { + run zinit ice wait'!2a' + assert $state equals 0 + +} + +@test 'wait-valid-bang-no-suffix' { + run zinit ice wait'!3' + assert $state equals 0 + +} + +@test 'wait-valid-decimal' { + run zinit ice wait'3.5' + assert $state equals 0 + +} + +@test 'wait-empty' { + run zinit ice wait + assert $state equals 0 + +} + +@test 'wait-cond-command-exists' { + run zinit ice wait'[[ -n $commands[fzf] ]]' + assert $state equals 0 + +} + +@test 'wait-cond-arithmetic' { + run zinit ice wait'(( $+commands[kubectl] ))' + assert $state equals 0 + +} + +@test 'wait-cond-variable' { + run zinit ice wait'[[ -n $MY_VAR ]]' + assert $state equals 0 + +} + +@test 'wait-cond-always-false' { + run zinit ice wait'[[ 1 = 0 ]]' + assert $state equals 0 + +} + +@test 'wait-cond-single-command' { + run zinit ice wait'type fzf &>/dev/null' + assert $state equals 0 + +} + +@test 'wait-invalid-suffix-x' { + run zinit ice wait'0x' + assert $state equals 0 + assert $output contains 'wait' + assert $output contains 'x' + assert $output contains 'Expected one of: a, b, c' +} + +@test 'wait-invalid-suffix-d' { + run zinit ice wait'3d' + assert $state equals 0 + assert $output contains 'wait' + assert $output contains 'd' +} + +@test 'wait-invalid-suffix-z' { + run zinit ice wait'1z' + assert $state equals 0 + assert $output contains 'wait' + assert $output contains 'z' +} + +@test 'wait-invalid-bang-suffix' { + run zinit ice wait'!2x' + assert $state equals 0 + assert $output contains 'wait' + assert $output contains 'x' +} + +@test 'wait-invalid-suffix-A' { + run zinit ice wait'0A' + assert $state equals 0 + assert $output contains 'wait' + assert $output contains 'A' +} + +@test 'depth-valid-1' { + run zinit ice depth'1' + assert $state equals 0 + +} + +@test 'depth-valid-10' { + run zinit ice depth'10' + assert $state equals 0 + +} + +@test 'depth-invalid-zero' { + run zinit ice depth'0' + assert $state equals 0 + assert $output contains 'depth' + assert $output contains '0' +} + +@test 'depth-empty' { + run zinit ice depth'' + assert $state equals 0 + +} + +@test 'depth-invalid-text' { + run zinit ice depth'abc' + assert $state equals 0 + assert $output contains 'depth' + assert $output contains 'abc' +} + +@test 'depth-invalid-negative' { + run zinit ice depth'-1' + assert $state equals 0 + assert $output contains 'depth' + assert $output contains '-1' +} + +@test 'depth-invalid-decimal' { + run zinit ice depth'1.5' + assert $state equals 0 + assert $output contains 'depth' + assert $output contains '1.5' +} + +@test 'combined-all-valid' { + run zinit ice as'null' wait'0a' depth'1' + assert $state equals 0 + +} + +@test 'combined-some-invalid' { + run zinit ice as'none' wait'0a' + assert $state equals 0 + assert $output contains 'as' + assert $output contains 'none' +} + +@test 'combined-all-invalid' { + run zinit ice as'none' wait'3x' depth'abc' + assert $state equals 0 + assert $output contains 'as' + assert $output contains 'wait' + assert $output contains 'depth' +} + +@test 'dash-prefix-valid' { + run zinit ice --as'null' + assert $state equals 0 + +} + +@test 'dash-prefix-invalid' { + run zinit ice --as'none' + assert $state equals 0 + assert $output contains 'as' + assert $output contains 'none' +} + +@test 'as-invalid-NULL-case' { + run zinit ice as'NULL' + assert $state equals 0 + assert $output contains 'as' + assert $output contains 'NULL' +} + +@test 'as-colon-valid' { + run zinit ice as:null + assert $state equals 0 +} + +@test 'as-colon-invalid' { + run zinit ice as:none + assert $state equals 0 + assert $output contains 'as' + assert $output contains 'none' +} + +@test 'for-invalid-as' { + run zinit for as'none' @zdharma-continuum/null + assert $state equals 0 + assert $output contains 'as' + assert $output contains 'none' +} diff --git a/zinit-install.zsh b/zinit-install.zsh index 62072d5e..5299a2f8 100644 --- a/zinit-install.zsh +++ b/zinit-install.zsh @@ -455,7 +455,7 @@ builtin source "${ZINIT[BIN_DIR]}/zinit-side.zsh" || { } ;; (*) - builtin print -Pr "${ZINIT[col-error]}Unknown protocol:%f%b ${ICE[proto]}." + builtin print -Pr "${ZINIT[col-error]}Unknown protocol:%f%b ${ICE[proto]}. Expected one of: git, http, https, ssh, ftp, ftps, rsync." return 1 esac diff --git a/zinit.zsh b/zinit.zsh index 7462484e..ad3783d3 100644 --- a/zinit.zsh +++ b/zinit.zsh @@ -2292,8 +2292,43 @@ $match[7]}:-${ZINIT[__last-formatter-code]}}}:+}}}//←→} fi (( $+ZINIT_ICES[configure] )) && ZINIT_ICES[configure]="${ZINIT_ICES[configure]}" (( $+ZINIT_ICES[make] )) && ZINIT_ICES[make]="${ZINIT_ICES[make]:-install}" + .zinit-validate-ice return retval } # ]]] +# FUNCTION: .zinit-validate-ice [[[ +# Validates ice values at parse time. +# Warns (not errors) about invalid values — behavior is unchanged. +.zinit-validate-ice() { + builtin setopt localoptions noksharrays extendedglob typesetsilent noshortloops + if (( $+ZINIT_ICES[as] )) && [[ -n ${ZINIT_ICES[as]} ]]; then + case ${ZINIT_ICES[as]} in + (command|program|null|completion) ;; + (*) + +zi-log "{warn}Warning{b-warn}:{rst} {ice}as{rst} ice received invalid" \ + "value {apo}\`{data}${ZINIT_ICES[as]}{apo}\`{rst}." \ + "Expected one of: {data2}null{rst}, {data2}command{rst}," \ + "{data2}program{rst}, {data2}completion{rst}." + ;; + esac + fi + if (( $+ZINIT_ICES[wait] )) && [[ -n ${ZINIT_ICES[wait]} ]]; then + local w="${ZINIT_ICES[wait]#\!}" + w="${w%%.[0-9]##}" + local suffix="${w##[0-9]##}" + if [[ ${w%%[^0-9]*} = <-> && -n $suffix && $suffix != [abc] ]]; then + +zi-log "{warn}Warning{b-warn}:{rst} {ice}wait{rst} ice received invalid" \ + "suffix letter {apo}\`{data}${suffix}{apo}\`{rst}." \ + "Expected one of: {data2}a{rst}, {data2}b{rst}, {data2}c{rst}, or none." + fi + fi + if (( $+ZINIT_ICES[depth] )) && [[ -n ${ZINIT_ICES[depth]} ]]; then + if [[ ${ZINIT_ICES[depth]} != <1-> ]]; then + +zi-log "{warn}Warning{b-warn}:{rst} {ice}depth{rst} ice received invalid" \ + "value {apo}\`{data}${ZINIT_ICES[depth]}{apo}\`{rst}." \ + "Expected a positive integer." + fi + fi +} # ]]] # FUNCTION: .zinit-pack-ice [[[ # Remembers all ice-mods, assigns them to concrete plugin. Ice spec # is in general forgotten for second-next command (i.e., ice melts quickly), however they