mirror of
https://github.com/nvm-sh/nvm.git
synced 2026-07-18 12:58:21 +08:00
[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:
@@ -1370,7 +1370,22 @@ nvm_alias() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
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() {
|
nvm_ls_current() {
|
||||||
@@ -1403,24 +1418,32 @@ nvm_resolve_alias() {
|
|||||||
local ALIAS
|
local ALIAS
|
||||||
ALIAS="${PATTERN}"
|
ALIAS="${PATTERN}"
|
||||||
local ALIAS_TEMP
|
local ALIAS_TEMP
|
||||||
|
local ALIAS_OUTPUT
|
||||||
|
|
||||||
local SEEN_ALIASES
|
local SEEN_ALIASES
|
||||||
SEEN_ALIASES="${ALIAS}"
|
SEEN_ALIASES="
|
||||||
local NVM_ALIAS_INDEX
|
${ALIAS}
|
||||||
NVM_ALIAS_INDEX=1
|
"
|
||||||
while true; do
|
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
|
if [ -z "${ALIAS_TEMP}" ]; then
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if command printf '%b' "${SEEN_ALIASES}" | nvm_grep -q -e "^${ALIAS_TEMP}$"; then
|
case "${SEEN_ALIASES}" in
|
||||||
|
*"
|
||||||
|
${ALIAS_TEMP}
|
||||||
|
"*)
|
||||||
ALIAS="∞"
|
ALIAS="∞"
|
||||||
break
|
break
|
||||||
fi
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
SEEN_ALIASES="${SEEN_ALIASES}\\n${ALIAS_TEMP}"
|
SEEN_ALIASES="${SEEN_ALIASES}${ALIAS_TEMP}
|
||||||
|
"
|
||||||
ALIAS="${ALIAS_TEMP}"
|
ALIAS="${ALIAS_TEMP}"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
die () { echo "$@" ; exit 1; }
|
||||||
|
|
||||||
|
export NVM_DIR="$(cd ../../.. && pwd)"
|
||||||
|
|
||||||
|
: nvm.sh
|
||||||
|
\. "${NVM_DIR}/nvm.sh"
|
||||||
|
|
||||||
|
# Create an alias file containing only comments
|
||||||
|
printf '# this is a comment\n# another comment\n' > ../../../alias/test-edge-comments
|
||||||
|
ACTUAL="$(nvm_alias test-edge-comments 2>/dev/null)"
|
||||||
|
EXIT_CODE="$(nvm_alias test-edge-comments 2>/dev/null; echo $?)"
|
||||||
|
[ -z "${ACTUAL}" ] || die "expected empty output for comment-only alias file, got >${ACTUAL}<"
|
||||||
|
[ "${EXIT_CODE}" = '0' ] || die "expected exit code 0, got ${EXIT_CODE}"
|
||||||
|
|
||||||
|
rm -f ../../../alias/test-edge-comments
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
die () { echo "$@" ; exit 1; }
|
||||||
|
|
||||||
|
export NVM_DIR="$(cd ../../.. && pwd)"
|
||||||
|
|
||||||
|
: nvm.sh
|
||||||
|
\. "${NVM_DIR}/nvm.sh"
|
||||||
|
|
||||||
|
# Create an empty alias file
|
||||||
|
printf '' > ../../../alias/test-edge-empty
|
||||||
|
ACTUAL="$(nvm_alias test-edge-empty 2>/dev/null)"
|
||||||
|
EXIT_CODE="$(nvm_alias test-edge-empty 2>/dev/null; echo $?)"
|
||||||
|
[ -z "${ACTUAL}" ] || die "expected empty output for empty alias file, got >${ACTUAL}<"
|
||||||
|
[ "${EXIT_CODE}" = '0' ] || die "expected exit code 0, got ${EXIT_CODE}"
|
||||||
|
|
||||||
|
rm -f ../../../alias/test-edge-empty
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
die () { echo "$@" ; exit 1; }
|
||||||
|
|
||||||
|
export NVM_DIR="$(cd ../../.. && pwd)"
|
||||||
|
|
||||||
|
: nvm.sh
|
||||||
|
\. "${NVM_DIR}/nvm.sh"
|
||||||
|
|
||||||
|
# Create an alias file with trailing spaces and tabs
|
||||||
|
printf '22.1.0 \t \n' > ../../../alias/test-edge-trailing-ws
|
||||||
|
ACTUAL="$(nvm_alias test-edge-trailing-ws)"
|
||||||
|
EXPECTED='22.1.0'
|
||||||
|
[ "${ACTUAL}" = "${EXPECTED}" ] || die "expected >${EXPECTED}<, got >${ACTUAL}<"
|
||||||
|
|
||||||
|
rm -f ../../../alias/test-edge-trailing-ws
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
die () { echo "$@" ; exit 1; }
|
||||||
|
|
||||||
|
export NVM_DIR="$(cd ../../.. && pwd)"
|
||||||
|
|
||||||
|
: nvm.sh
|
||||||
|
\. "${NVM_DIR}/nvm.sh"
|
||||||
|
|
||||||
|
# 1-hop self-reference: alias points to itself
|
||||||
|
echo 'self' > '../../../alias/self'
|
||||||
|
|
||||||
|
ACTUAL="$(nvm_resolve_alias self)"
|
||||||
|
EXPECTED='∞'
|
||||||
|
[ "${ACTUAL}" = "${EXPECTED}" ] || die "expected >${EXPECTED}< for self-reference, got >${ACTUAL}<"
|
||||||
|
|
||||||
|
rm -f '../../../alias/self'
|
||||||
|
|
||||||
|
# Multi-hop loop: link1 -> link2 -> link3 -> link1
|
||||||
|
echo 'link2' > '../../../alias/link1'
|
||||||
|
echo 'link3' > '../../../alias/link2'
|
||||||
|
echo 'link1' > '../../../alias/link3'
|
||||||
|
|
||||||
|
ACTUAL="$(nvm_resolve_alias link1)"
|
||||||
|
EXPECTED='∞'
|
||||||
|
[ "${ACTUAL}" = "${EXPECTED}" ] || die "expected >${EXPECTED}< for 3-hop cycle, got >${ACTUAL}<"
|
||||||
|
|
||||||
|
rm -f '../../../alias/link1' '../../../alias/link2' '../../../alias/link3'
|
||||||
|
|
||||||
|
# Cycle through an alias name containing a space
|
||||||
|
echo 'midway' > '../../../alias/foo bar'
|
||||||
|
echo 'foo bar' > '../../../alias/midway'
|
||||||
|
|
||||||
|
ACTUAL="$(nvm_resolve_alias 'foo bar')"
|
||||||
|
EXPECTED='∞'
|
||||||
|
[ "${ACTUAL}" = "${EXPECTED}" ] || die "expected >${EXPECTED}< for space-name cycle, got >${ACTUAL}<"
|
||||||
|
|
||||||
|
rm -f '../../../alias/foo bar' '../../../alias/midway'
|
||||||
|
|
||||||
|
# Non-cycle through an alias name containing a space.
|
||||||
|
# Token-based cycle detection would false-positive on this chain because
|
||||||
|
# 'bar' appears as a substring of the multi-token alias name 'foo bar'.
|
||||||
|
# Resolves: 'foo bar' -> 'midway' -> 'bar' -> 0.0.99
|
||||||
|
echo 'midway' > '../../../alias/foo bar'
|
||||||
|
echo 'bar' > '../../../alias/midway'
|
||||||
|
echo '0.0.99' > '../../../alias/bar'
|
||||||
|
|
||||||
|
ACTUAL="$(nvm_resolve_alias 'foo bar')"
|
||||||
|
EXPECTED='v0.0.99'
|
||||||
|
[ "${ACTUAL}" = "${EXPECTED}" ] || die "expected >${EXPECTED}< for space-name chain, got >${ACTUAL}<"
|
||||||
|
|
||||||
|
rm -f '../../../alias/foo bar' '../../../alias/midway' '../../../alias/bar'
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
die () { echo "$@" ; exit 1; }
|
||||||
|
|
||||||
|
export NVM_DIR="$(cd ../../.. && pwd)"
|
||||||
|
|
||||||
|
: nvm.sh
|
||||||
|
\. "${NVM_DIR}/nvm.sh"
|
||||||
|
|
||||||
|
# Create a 4-deep alias chain: hop1 -> hop2 -> hop3 -> hop4 -> 0.0.99
|
||||||
|
echo 'hop2' > ../../../alias/hop1
|
||||||
|
echo 'hop3' > ../../../alias/hop2
|
||||||
|
echo 'hop4' > ../../../alias/hop3
|
||||||
|
echo '0.0.99' > ../../../alias/hop4
|
||||||
|
|
||||||
|
ACTUAL="$(nvm_resolve_alias hop1)"
|
||||||
|
EXPECTED='v0.0.99'
|
||||||
|
[ "${ACTUAL}" = "${EXPECTED}" ] || die "expected >${EXPECTED}< for 4-deep chain, got >${ACTUAL}<"
|
||||||
|
|
||||||
|
rm -f ../../../alias/hop1 ../../../alias/hop2 ../../../alias/hop3 ../../../alias/hop4
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
die () { echo "$@" ; exit 1; }
|
||||||
|
|
||||||
|
export NVM_DIR="$(cd ../../.. && pwd)"
|
||||||
|
|
||||||
|
: nvm.sh
|
||||||
|
\. "${NVM_DIR}/nvm.sh"
|
||||||
|
|
||||||
|
# Create an alias pointing to a version that does not exist as an alias
|
||||||
|
echo '99.99.99' > ../../../alias/test-edge-noexist
|
||||||
|
|
||||||
|
ACTUAL="$(nvm_resolve_alias test-edge-noexist)"
|
||||||
|
EXPECTED='v99.99.99'
|
||||||
|
EXIT_CODE="$(nvm_resolve_alias test-edge-noexist >/dev/null 2>&1; echo $?)"
|
||||||
|
[ "${ACTUAL}" = "${EXPECTED}" ] || die "expected >${EXPECTED}<, got >${ACTUAL}<"
|
||||||
|
[ "${EXIT_CODE}" = '0' ] || die "expected exit code 0, got ${EXIT_CODE}"
|
||||||
|
|
||||||
|
rm -f ../../../alias/test-edge-noexist
|
||||||
Reference in New Issue
Block a user