Skip to content

fix(unload): dispatch cannot reach the unload function of a hyphenated plugin #482

Description

@ss-o

Problem

.zi-unload resolves the Zsh Plugin Standard unload hook using the plugin's
literal repository name:

(( ${+functions[${plugin}_plugin_unload]} )) && ${plugin}_plugin_unload

(( ${+functions[${plugin}_plugin_unload]} )) && ${plugin}_plugin_unload

$plugin comes from .zi-any-to-user-plugin and is used verbatim, so
zi unload z-shell/zsh-eza looks for zsh-eza_plugin_unload.

The organization's own analyzer derives the shell-visible prefix by replacing
hyphens with underscores (zsh-lint v1.2.0,
internal/projectconfig/config.go, ShellPrefix), so plugin/function-namespace
rejects that name:

zsh-eza.plugin.zsh:37:3: [plugin/function-namespace] Function name "zsh-eza_plugin_unload"
  must use public prefix "zsh_eza_" or private prefix "_zsh_eza_"

A plugin whose name contains a hyphen can therefore satisfy ADR-0020 or be
unloadable by Zi, but not both.

Reproduction

$ zsh -fc 'plugin=zsh-eza; zsh_eza_plugin_unload() { :; }
  (( ${+functions[${plugin}_plugin_unload]} )) || print "MISS: ${plugin}_plugin_unload not found"'
MISS: zsh-eza_plugin_unload not found

Impact

Renaming to satisfy the analyzer makes unload a silent no-op: no error, and
under zi light there is no per-plugin tracking to fall back on, so nothing is
undone. This affects every hyphenated plugin and annex in the organization,
including zsh-eza, zsh-fancy-completions, and all z-a-* annexes.

This is distinct from #113, which concerns ownership across repeated loads of
the same plugin, and from #108, which concerned add-zsh-hook array ownership.
Both assume the unload function is reached at all.

Two ways to resolve it

Either side can move, and the choice belongs to the maintainers rather than to
the plugin repositories.

  1. zsh-lint exempts the unload name. Accept
    <identifier>_plugin_unload in plugin/function-namespace, since the
    standard derives that one name from the project identifier itself. One rule
    change, no manager churn.

  2. Zi accepts both forms at the dispatch. Prefer the literal name for
    compatibility:

    local ___unload
    for ___unload in "${plugin}_plugin_unload" "${plugin//[^A-Za-z0-9]/_}_plugin_unload"; do
      (( ${+functions[$___unload]} )) && { $___unload; break; }
    done

Acceptance criteria

  • zi unload reaches the unload function for a plugin whose name contains a
    hyphen, whichever naming form the plugin declares.
  • Behavior is unchanged for a plugin that declares only the literal-name form.
  • A test covers a hyphenated plugin ID with each naming form.
  • The chosen resolution is recorded so plugin repositories can drop the
    suppression comments they carry today.

Current workaround

z-shell/zsh-eza and z-shell/z-a-meta-plugins keep the hyphenated name and
suppress the advisory plugin/function-namespace hint with a reason. The hint
is severity Hint and does not gate CI.

References

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:ziZi core behavior, APIs, or documentation.type:bugSomething is broken or behaving incorrectly.

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions