[Refactor] nvm_alias, nvm_resolve_alias: use builtins

[Tests] `nvm_alias`, `nvm_resolve_alias`: add edge-case tests

nvm_alias() used a sed/awk pipeline to strip comments and blank lines from alias files that almost always contain a single word.
A while-read loop with parameter expansion does the same filtering more directly.

nvm_resolve_alias() piped nvm_alias through head and tail to extract one line, and used printf/grep for cycle detection.
Parameter expansion and a case statement replace both without the extra plumbing.

All replacements are POSIX (read -r, case, IFS=, parameter expansion).
As a side effect, this also removes 4 external process invocations during shell init.

[Fix] `nvm_resolve_alias`: detect cycles via newline-anchored `case`

The original commit referenced above changed SEEN_ALIASES from `\n`-delimited
storage (interpreted by `printf '%b' | nvm_grep -e "^${name}$"`) to space-
delimited but left the line-anchored grep in place — without newlines in
the haystack the anchored pattern can never match, so cycles never break.

Switch to literal-newline storage and a `case` pattern anchored on those
newlines. Newline anchoring also handles alias names containing spaces,
which token-based patterns false-positive on (e.g. lookup of `bar` matches
substring " bar " inside " foo bar midway " when the chain visits the
multi-token alias `foo bar`).

New test file covers self-loop, multi-hop loop, cycle through a
space-bearing alias name, and a non-cycle through a space-bearing
alias name. Existing `test/fast/Aliases/circular/` fixtures continue
to pass.
This commit is contained in:
Jake Lodwick
2026-02-17 13:44:31 -07:00
committed by Jordan Harband
parent dd61ae4bc8
commit d22bf7f538
7 changed files with 174 additions and 10 deletions
+33 -10
View File
@@ -1370,7 +1370,22 @@ nvm_alias() {
return 0
fi
command sed 's/#.*//; s/[[:space:]]*$//' "${NVM_ALIAS_PATH}" | command awk 'NF'
local NVM_ALIAS_LINE
while IFS= read -r NVM_ALIAS_LINE || [ -n "${NVM_ALIAS_LINE}" ]; do
NVM_ALIAS_LINE="${NVM_ALIAS_LINE%%#*}"
case "${NVM_ALIAS_LINE}" in
*[!\ \ ]*) ;;
*) continue ;;
esac
while : ; do
case "${NVM_ALIAS_LINE}" in
*' ') NVM_ALIAS_LINE="${NVM_ALIAS_LINE% }" ;;
*' ') NVM_ALIAS_LINE="${NVM_ALIAS_LINE% }" ;;
*) break ;;
esac
done
nvm_echo "${NVM_ALIAS_LINE}"
done < "${NVM_ALIAS_PATH}"
}
nvm_ls_current() {
@@ -1403,24 +1418,32 @@ nvm_resolve_alias() {
local ALIAS
ALIAS="${PATTERN}"
local ALIAS_TEMP
local ALIAS_OUTPUT
local SEEN_ALIASES
SEEN_ALIASES="${ALIAS}"
local NVM_ALIAS_INDEX
NVM_ALIAS_INDEX=1
SEEN_ALIASES="
${ALIAS}
"
while true; do
ALIAS_TEMP="$( (nvm_alias "${ALIAS}" 2>/dev/null | command head -n "${NVM_ALIAS_INDEX}" | command tail -n 1) || nvm_echo)"
ALIAS_OUTPUT="$(nvm_alias "${ALIAS}" 2>/dev/null)" || ALIAS_OUTPUT=''
ALIAS_TEMP="${ALIAS_OUTPUT%%
*}"
if [ -z "${ALIAS_TEMP}" ]; then
break
fi
if command printf '%b' "${SEEN_ALIASES}" | nvm_grep -q -e "^${ALIAS_TEMP}$"; then
ALIAS="∞"
break
fi
case "${SEEN_ALIASES}" in
*"
${ALIAS_TEMP}
"*)
ALIAS="∞"
break
;;
esac
SEEN_ALIASES="${SEEN_ALIASES}\\n${ALIAS_TEMP}"
SEEN_ALIASES="${SEEN_ALIASES}${ALIAS_TEMP}
"
ALIAS="${ALIAS_TEMP}"
done