Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/workflows/zsh-n.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ on:
- "tests/snippet-directory-mirror.zsh"
- "tests/source-hygiene.zsh"
- "tests/subst-nesting.zsh"
- "tests/unload-hook-dispatch.zsh"
- "tests/version-reporting.zsh"
pull_request:
paths:
Expand All @@ -50,6 +51,7 @@ on:
- "tests/snippet-directory-mirror.zsh"
- "tests/source-hygiene.zsh"
- "tests/subst-nesting.zsh"
- "tests/unload-hook-dispatch.zsh"
- "tests/version-reporting.zsh"
workflow_dispatch: {}

Expand Down Expand Up @@ -98,6 +100,17 @@ jobs:
zsh -fc "zcompile ${ZSH_FILE}"; rc=$?
ls -al "${ZSH_FILE}.zwc"; exit "$rc"

unload-hook-dispatch:
name: Unload Hook Dispatch
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Zsh
run: sudo apt update && sudo apt-get install -yq zsh
- name: Test unload hook dispatch
run: zsh -f tests/unload-hook-dispatch.zsh

version-reporting:
name: Version reporting
runs-on: ubuntu-latest
Expand Down
21 changes: 20 additions & 1 deletion lib/zsh/autoload.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -897,7 +897,26 @@ ZI[EXTENDED_GLOB]=""
# Call the Zsh Plugin's Standard *_plugin_unload function
#

(( ${+functions[${plugin}_plugin_unload]} )) && ${plugin}_plugin_unload
# The Plugin Standard derives this name from the plug-in's name, and $plugin
# was used verbatim. Two kinds of plug-in could therefore never be reached:
#
# - a hyphenated repository, because ADR-0020's namespace rules require the
# shell prefix to use underscores, so `zsh-eza' can define
# `zsh_eza_plugin_unload' or be unloadable by Zi, but not both;
# - a plug-in loaded by path, whose $plugin is the absolute directory, so
# `/path/to/thing_plugin_unload' names nothing.
#
# Try the literal name first so plug-ins already defining it keep working,
# then the underscore form, then the same pair derived from the directory
# basename, which is the closest analogue of a repository name for a
# path-loaded plug-in. Call the first that exists; the Standard defines a
# single unload function per plug-in.
local ___base="${plugin:t}" ___cand
local -a ___cand_names
___cand_names=( "$plugin" "${plugin//-/_}" "$___base" "${___base//-/_}" )
for ___cand ( ${(u)___cand_names[@]} ) {
(( ${+functions[${___cand}_plugin_unload]} )) && { "${___cand}_plugin_unload"; break; }
}

#
# Call the code provided by the Zsh Plugin Standard @zsh-plugin-run-on-unload
Expand Down
90 changes: 90 additions & 0 deletions tests/unload-hook-dispatch.zsh
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/usr/bin/env zsh
# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*-
# vim: ft=zsh sw=2 ts=2 et

builtin emulate -R zsh
setopt pipe_fail

fail() {
builtin print -u2 -r -- "not ok - $1"
exit 1
}

typeset project_root="${ZI_TEST_CHECKOUT:-${0:A:h:h}}"
typeset temp_root
temp_root="$(command mktemp -d "${TMPDIR:-/tmp}/zi-unload-hook-test.XXXXXXXX")" ||
fail "create temporary directory"
trap 'command rm -rf -- "$temp_root"' EXIT INT TERM

command mkdir -p \
"${temp_root}/home" \
"${temp_root}/cache" \
"${temp_root}/config" \
"${temp_root}/data" \
"${temp_root}/zdotdir" \
"${temp_root}/plain" \
"${temp_root}/hyph-en" || fail "create isolated environment"

# A plug-in whose directory name has no hyphen. Its unload function is named
# after the directory, which is the Plugin Standard shape.
builtin print -rl -- \
'plain_plugin_unload() { builtin print -r -- plain-hook-ran >> $ZI_TEST_LOG; }' \
> "${temp_root}/plain/plain.plugin.zsh" || fail "write the plain plug-in"

# A plug-in whose directory name has a hyphen. ADR-0020 namespace rules require
# the shell prefix to use underscores, so the only name it may define is the
# underscore form. Before the fix nothing looked for that name.
builtin print -rl -- \
'hyph_en_plugin_unload() { builtin print -r -- hyphen-hook-ran >> $ZI_TEST_LOG; }' \
> "${temp_root}/hyph-en/hyph-en.plugin.zsh" || fail "write the hyphenated plug-in"

env \
HOME="${temp_root}/home" \
XDG_CACHE_HOME="${temp_root}/cache" \
XDG_CONFIG_HOME="${temp_root}/config" \
XDG_DATA_HOME="${temp_root}/data" \
ZDOTDIR="${temp_root}/zdotdir" \
ZI_TEST_CHECKOUT="$project_root" \
ZI_TEST_ROOT="$temp_root" \
ZI_TEST_LOG="${temp_root}/hooks.log" \
zsh -f <<'ZSH' || fail "the Plugin Standard unload function was not called"
builtin emulate -R zsh
setopt pipe_fail

builtin source "${ZI_TEST_CHECKOUT}/zi.zsh" || return 1
.zi-prepare-home || return 1
: > "$ZI_TEST_LOG"

zi load "${ZI_TEST_ROOT}/plain" >/dev/null 2>&1
(( ${+functions[plain_plugin_unload]} )) || {
builtin print -u2 -r -- "the plain plug-in did not define its unload function"
return 1
}
zi unload "${ZI_TEST_ROOT}/plain" >/dev/null 2>&1

zi load "${ZI_TEST_ROOT}/hyph-en" >/dev/null 2>&1
(( ${+functions[hyph_en_plugin_unload]} )) || {
builtin print -u2 -r -- "the hyphenated plug-in did not define its unload function"
return 1
}
zi unload "${ZI_TEST_ROOT}/hyph-en" >/dev/null 2>&1

typeset log="$(<$ZI_TEST_LOG)"

# A path-loaded plug-in is identified as `%<absolute path>', so the name derived
# verbatim from the id can never match. The basename is the analogue of a
# repository name.
[[ $log == *plain-hook-ran* ]] || {
builtin print -u2 -r -- "the unload function of a path-loaded plug-in was not called: [$log]"
return 1
}

# The underscore form, which is the only name ADR-0020 permits a hyphenated
# plug-in to define.
[[ $log == *hyphen-hook-ran* ]] || {
builtin print -u2 -r -- "the underscore-named unload function was not called: [$log]"
return 1
}
ZSH

builtin print -r -- "ok - the Plugin Standard unload function is reached for hyphenated and path-loaded plug-ins"
Loading