use std::path::Path;
use shuck_indexer::Indexer;
use shuck_parser::parser::Parser;
use shuck_semantic::{
FileContract, FileEntryContractCollector, NoopTraversalObserver, PluginFramework,
PluginRequest, PluginRequestKind, SemanticBuildOptions, build_with_observer_with_options,
};
use std::sync::Arc;
use super::{AmbientContractCollector, AmbientContractConfig, ResolvedAmbientContracts};
use crate::ShellDialect;
fn contract_for(path: &Path, source: &str) -> Option<FileContract> {
contract_for_shell(path, source, ShellDialect::Sh)
}
fn contract_for_shell(path: &Path, source: &str, shell: ShellDialect) -> Option<FileContract> {
contract_for_optional_path(Some(path), source, shell)
}
fn contract_for_optional_path(
path: Option<&Path>,
source: &str,
shell: ShellDialect,
) -> Option<FileContract> {
contract_for_optional_path_with_contracts(
path,
source,
shell,
Arc::new(ResolvedAmbientContracts::default()),
)
}
fn contract_for_optional_path_with_contracts(
path: Option<&Path>,
source: &str,
shell: ShellDialect,
contracts: Arc<ResolvedAmbientContracts>,
) -> Option<FileContract> {
let output = Parser::new(source).parse().unwrap();
let indexer = Indexer::new(source, &output);
let mut observer = NoopTraversalObserver;
let mut collector = AmbientContractCollector::new(source, path, shell, contracts);
let _semantic = build_with_observer_with_options(
&output.file,
source,
&indexer,
&mut observer,
SemanticBuildOptions {
file_entry_contract_collector: Some(&mut collector),
..SemanticBuildOptions::default()
},
);
collector.finish()
}
fn request_contract_for(path: &Path, request: PluginRequest) -> FileContract {
ResolvedAmbientContracts::default()
.request_contracts_for_plugin(path, &request)
.requesting_file_contract
}
fn has_initialized_binding(contract: &FileContract, name: &str) -> bool {
contract.provided_bindings.iter().any(|binding| {
binding.name == name
&& binding.file_entry_initialization
== shuck_semantic::FileEntryBindingInitialization::Initialized
})
}
fn has_ambient_binding(contract: &FileContract, name: &str) -> bool {
contract.provided_bindings.iter().any(|binding| {
binding.name == name
&& binding.file_entry_initialization
== shuck_semantic::FileEntryBindingInitialization::AmbientOnly
})
}
fn has_consumed_prefix(contract: &FileContract, prefix: &str) -> bool {
contract
.externally_consumed_binding_prefixes
.iter()
.any(|consumed_prefix| consumed_prefix.as_str() == prefix)
}
fn has_consumed_name(contract: &FileContract, name: &str) -> bool {
contract
.externally_consumed_binding_names
.iter()
.any(|consumed_name| consumed_name.as_str() == name)
}
fn plugin_request(framework: PluginFramework, name: &str) -> PluginRequest {
PluginRequest {
framework,
kind: PluginRequestKind::Plugin,
name: name.to_owned(),
span: shuck_ast::Span::new(),
explicit: false,
root_hint: None,
}
}
#[test]
fn project_specific_paths_do_not_get_ambient_contracts() {
let void_path = Path::new("/tmp/void-packages/common/build-style/void-cross.sh");
let void_source = "\
helper() { cd \"${wrksrc}\"; }
printf '%s\\n' \"$pkgname\" \"$pkgver\" \"$XBPS_SRCPKGDIR\" \"$configure_args\"
";
assert!(contract_for(void_path, void_source).is_none());
let flattened_path =
Path::new("/tmp/scripts/void-linux__void-packages__common__build-style__void-cross.sh");
let flattened_source = "\
helper() { :; }
printf '%s\\n' \"$XBPS_SRCPKGDIR\" \"$configure_args\" \"$wrksrc\"
";
assert!(contract_for(flattened_path, flattened_source).is_none());
}
#[test]
fn effective_well_known_ids_include_registry_backed_bash_and_zsh_array_contracts() {
let effective = ResolvedAmbientContracts::default().effective();
assert!(
effective
.well_known_ids
.iter()
.any(|id| id.starts_with("bash/"))
);
assert!(
effective
.well_known_ids
.iter()
.any(|id| id.starts_with("bash-it/"))
);
assert!(
effective
.well_known_ids
.contains(&"zsh/caller-scoped-arrays".to_owned())
);
}
#[test]
fn bash_it_theme_paths_get_palette_ambient_contracts() {
let path = Path::new("/tmp/Bash-it/themes/example/example.theme.bash");
let source = "\
prompt_command() {
PS1=\"${green?} ${green} ${reset_color?}\"
}
PROMPT_COMMAND=prompt_command
";
let contract = contract_for(path, source).unwrap();
assert!(has_ambient_binding(&contract, "green"));
assert!(has_ambient_binding(&contract, "reset_color"));
assert!(!has_initialized_binding(&contract, "green"));
assert!(!has_initialized_binding(&contract, "reset_color"));
assert!(!contract.externally_consumed_bindings);
}
#[test]
fn disabling_bash_it_theme_selector_removes_palette_bindings() {
let path = Path::new("/tmp/Bash-it/themes/example/example.theme.bash");
let source = "\
prompt_command() {
PS1=\"${green?} ${green} ${reset_color?}\"
}
PROMPT_COMMAND=prompt_command
";
let contracts = Arc::new(
ResolvedAmbientContracts::resolve(
"/tmp",
AmbientContractConfig {
disabled: vec!["bash-it/theme".to_owned()],
..AmbientContractConfig::default()
},
)
.unwrap(),
);
let contract =
contract_for_optional_path_with_contracts(Some(path), source, ShellDialect::Sh, contracts);
assert!(contract.is_none(), "{contract:?}");
}
#[test]
fn generic_theme_paths_do_not_initialize_palette_contracts() {
let path = Path::new("/tmp/project/themes/example.theme.bash");
let source = "\
helper() {
printf '%s\\n' \"$green\" \"$reset_color\"
}
";
assert!(contract_for(path, source).is_none());
}
#[test]
fn zsh_config_namespaces_get_consumed_prefix_contracts() {
let path = Path::new("/tmp/zdot/.zshrc");
let source = "\
POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(dir vcs)
ZDOT_MODULE_NAME=prompt
";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
assert!(has_consumed_prefix(&contract, "POWERLEVEL9K_"));
assert!(has_consumed_prefix(&contract, "ZDOT_"));
assert!(!contract.externally_consumed_bindings);
}
#[test]
fn zsh_runtime_contract_initializes_special_parameters_and_prompt_colors() {
let path = Path::new("/tmp/zsh/ohmyzsh/plugins/example/example.plugin.zsh");
let source = "\
print -r -- \"$sysparams\" \"$history\" \"$words\" \"$compstate\" \"$userdirs\"
prompt=\"%{$fg_bold[blue]%}%{$reset_color%}\"
";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
for name in [
"sysparams",
"history",
"words",
"compstate",
"userdirs",
"fg_bold",
"reset_color",
] {
assert!(has_initialized_binding(&contract, name), "{contract:?}");
}
}
#[test]
fn powerlevel10k_internal_paths_get_runtime_special_parameters() {
let path = Path::new("/tmp/zsh/powerlevel10k/internal/parser.zsh");
let source = "print -r -- \"$galiases\" \"$saliases\" \"$langinfo\" \"$sysparams\"\n";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
for name in ["galiases", "saliases", "langinfo", "sysparams"] {
assert!(has_initialized_binding(&contract, name), "{contract:?}");
}
}
#[test]
fn zmodload_parameter_contract_includes_patch_character_arrays() {
let path = Path::new("/tmp/zsh/powerlevel10k/gitstatus/mbuild");
let source = "\
zmodload zsh/parameter zsh/param/private || return
print -r -- \"$patchars\" \"$dis_patchars\"
";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
for name in ["patchars", "dis_patchars"] {
assert!(has_initialized_binding(&contract, name), "{contract:?}");
}
}
#[test]
fn powerlevel10k_internal_paths_get_hook_arrays() {
let path = Path::new("/tmp/zsh/powerlevel10k/internal/p10k.zsh");
let source = "print -r -- $zsh_directory_name_functions\n";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
assert!(has_initialized_binding(
&contract,
"zsh_directory_name_functions"
));
}
#[test]
fn standalone_zsh_scripts_do_not_get_userdirs_without_runtime_context() {
let path = Path::new("/tmp/project/scripts/example.zsh");
let source = "print -r -- \"$userdirs\"\n";
assert!(contract_for_shell(path, source, ShellDialect::Zsh).is_none());
}
#[test]
fn commented_zmodload_does_not_initialize_module_parameters() {
let path = Path::new("/tmp/project/scripts/example.zsh");
let source = "\
true;# zmodload zsh/parameter
print -r -- \"$history\"
";
assert!(contract_for_shell(path, source, ShellDialect::Zsh).is_none());
}
#[test]
fn zsh_runtime_contract_initializes_hook_arrays() {
let path = Path::new("/tmp/zsh/ohmyzsh/lib/async_prompt.zsh");
let source = "\
precmd_functions=(${precmd_functions:#_async_prompt_precmd})
print -r -- $chpwd_functions
chpwd_functions+=(_async_prompt_chpwd)
";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
for name in ["precmd_functions", "chpwd_functions"] {
assert!(has_initialized_binding(&contract, name), "{contract:?}");
assert!(has_consumed_name(&contract, name), "{contract:?}");
}
}
#[test]
fn oh_my_zsh_theme_and_appearance_consumes_theme_prefixes() {
let path = Path::new("/tmp/zsh/ohmyzsh/lib/theme-and-appearance.zsh");
let source = "ZSH_THEME_GIT_PROMPT_PREFIX='git:('\n";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
assert!(has_consumed_prefix(&contract, "ZSH_THEME_"));
}
#[test]
fn oh_my_zsh_adben_theme_consumes_theme_prefixes() {
let path = Path::new("/tmp/zsh/ohmyzsh/themes/adben.zsh-theme");
let source = "ZSH_THEME_GIT_PROMPT_PREFIX='git:('\n";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
assert!(has_consumed_prefix(&contract, "ZSH_THEME_"));
}
#[test]
fn oh_my_zsh_sonicradish_theme_consumes_theme_prefixes() {
let path = Path::new("/tmp/zsh/ohmyzsh/themes/sonicradish.zsh-theme");
let source = "ZSH_THEME_GIT_PROMPT_PREFIX='git:('\n";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
assert!(has_consumed_prefix(&contract, "ZSH_THEME_"));
}
#[test]
fn oh_my_zsh_refined_theme_provides_vcs_info() {
let path = Path::new("/tmp/zsh/ohmyzsh/themes/refined.zsh-theme");
let source = "print -r -- \"$vcs_info_msg_0_\"\n";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
assert!(has_ambient_binding(&contract, "vcs_info_msg_0_"));
}
#[test]
fn oh_my_zsh_core_paths_get_plugin_configuration() {
let core_path = Path::new("/tmp/zsh/ohmyzsh/oh-my-zsh.sh");
let core_source = "print -r -- \"$plugins\"\n";
let core_contract = contract_for_shell(core_path, core_source, ShellDialect::Zsh).unwrap();
assert!(has_initialized_binding(&core_contract, "plugins"));
}
#[test]
fn oh_my_zsh_emoji_and_emotty_paths_get_emoji_runtime_bindings() {
let emoji_path = Path::new("/tmp/zsh/ohmyzsh/plugins/emoji/emoji.plugin.zsh");
let emoji_source =
"print -r -- \"$emoji_groups[people]\" \"$emoji_flags[us]\" \"$emoji[rocket]\"\n";
let emoji_contract = contract_for_shell(emoji_path, emoji_source, ShellDialect::Zsh).unwrap();
for name in ["emoji", "emoji_flags", "emoji_groups"] {
assert!(
has_initialized_binding(&emoji_contract, name),
"{emoji_contract:?}"
);
}
let emotty_path = Path::new("/tmp/zsh/ohmyzsh/plugins/emotty/emotty.plugin.zsh");
let emotty_source = "print -r -- \"$emoji[rocket]\" \"$emoji2[emoji_style]\"\n";
let emotty_contract =
contract_for_shell(emotty_path, emotty_source, ShellDialect::Zsh).unwrap();
for name in ["emoji", "emoji2"] {
assert!(
has_initialized_binding(&emotty_contract, name),
"{emotty_contract:?}"
);
}
let theme_path = Path::new("/tmp/zsh/ohmyzsh/themes/emotty.zsh-theme");
let theme_source = "print -r -- \"$emoji[skull]\" \"$emoji2[emoji_style]\"\n";
let theme_contract = contract_for_shell(theme_path, theme_source, ShellDialect::Zsh).unwrap();
for name in ["emoji", "emoji2"] {
assert!(
has_initialized_binding(&theme_contract, name),
"{theme_contract:?}"
);
}
}
#[test]
fn hidden_oh_my_zsh_paths_get_framework_contracts() {
let core_path = Path::new("/tmp/home/.oh-my-zsh/oh-my-zsh.sh");
let core_source = "print -r -- \"$plugins\"\n";
let core_contract = contract_for_shell(core_path, core_source, ShellDialect::Zsh).unwrap();
assert!(has_initialized_binding(&core_contract, "plugins"));
let emoji_path = Path::new("/tmp/home/.oh-my-zsh/plugins/emoji/emoji.plugin.zsh");
let emoji_source =
"print -r -- \"$emoji_groups[people]\" \"$emoji_flags[us]\" \"$emoji[rocket]\"\n";
let emoji_contract = contract_for_shell(emoji_path, emoji_source, ShellDialect::Zsh).unwrap();
for name in ["emoji", "emoji_flags", "emoji_groups"] {
assert!(
has_initialized_binding(&emoji_contract, name),
"{emoji_contract:?}"
);
}
let emotty_path = Path::new("/tmp/home/.oh-my-zsh/themes/emotty.zsh-theme");
let emotty_source = "print -r -- \"$emoji[skull]\" \"$emoji2[emoji_style]\"\n";
let emotty_contract =
contract_for_shell(emotty_path, emotty_source, ShellDialect::Zsh).unwrap();
for name in ["emoji", "emoji2"] {
assert!(
has_initialized_binding(&emotty_contract, name),
"{emotty_contract:?}"
);
}
}
#[test]
fn oh_my_zsh_tools_get_prompt_color_bindings() {
let path = Path::new("/tmp/zsh/ohmyzsh/tools/check_for_upgrade.sh");
let source = "print -r -- \"$fg\" \"$reset_color\"\n";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
assert!(has_initialized_binding(&contract, "fg"));
assert!(has_initialized_binding(&contract, "reset_color"));
}
#[test]
fn hyphenated_oh_my_zsh_paths_get_unknown_shell_runtime_contracts() {
let path = Path::new("/tmp/home/.oh-my-zsh/tools/check_for_upgrade.sh");
let source = "print -r -- \"$fg\" \"$reset_color\"\n";
let contract = contract_for_shell(path, source, ShellDialect::Unknown).unwrap();
assert!(has_initialized_binding(&contract, "fg"));
assert!(has_initialized_binding(&contract, "reset_color"));
}
#[test]
fn zsh_runtime_contract_initializes_braced_prompt_color_arrays() {
let path = Path::new("/tmp/zsh/ohmyzsh/plugins/example/example.plugin.zsh");
let source = "\
typeset -AHg less_termcap
less_termcap[mb]=\"${fg_bold[red]}\"
less_termcap[so]=\"${fg_bold[yellow]}${bg[blue]}\"
less_termcap[me]=\"${reset_color}\"
";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
for name in ["fg_bold", "bg", "reset_color"] {
assert!(has_initialized_binding(&contract, name), "{contract:?}");
}
}
#[test]
fn zsh_runtime_contract_initializes_prompt_colors_after_colors_autoload() {
let path = Path::new("/tmp/zsh/holman-dotfiles/zsh/prompt.zsh");
let source = "\
autoload colors && colors
echo \"on %{$fg_bold[green]%}%{$reset_color%}\"
";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
for name in ["fg_bold", "reset_color"] {
assert!(has_initialized_binding(&contract, name), "{contract:?}");
}
}
#[test]
fn zsh_runtime_contract_accepts_compact_colors_autoload_separators() {
let path = Path::new("/tmp/zsh/holman-dotfiles/zsh/prompt.zsh");
let source = "\
autoload colors&&colors
echo \"on %{$fg_bold[green]%}%{$reset_color%}\"
";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
for name in ["fg_bold", "reset_color"] {
assert!(has_initialized_binding(&contract, name), "{contract:?}");
}
}
#[test]
fn zsh_runtime_contract_requires_colors_autoload_command() {
let path = Path::new("/tmp/zsh/holman-dotfiles/zsh/prompt.zsh");
let source = "\
echo autoload colors
echo \"on %{$fg_bold[green]%}%{$reset_color%}\"
";
assert!(contract_for_shell(path, source, ShellDialect::Zsh).is_none());
}
#[test]
fn zsh_history_state_assignments_are_consumed_in_config_contexts() {
let path = Path::new("/tmp/home/zsh/config.zsh");
let source = "\
HISTFILE=~/.zsh_history
HISTSIZE=10000
SAVEHIST=10000
";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
for name in ["HISTFILE", "HISTSIZE", "SAVEHIST"] {
assert!(has_consumed_name(&contract, name), "{contract:?}");
}
}
#[test]
fn zsh_module_metadata_triplets_are_consumed_by_external_loaders() {
let path = Path::new("/tmp/project/install/01-package-manager.zsh");
let source = "\
#!/bin/zsh
module_name=\"package-manager\"
module_description=\"Install package manager\"
module_main_function=\"run_package_manager_module\"
run_package_manager_module() {
:
}
";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
for name in ["module_name", "module_description", "module_main_function"] {
assert!(has_consumed_name(&contract, name), "{contract:?}");
}
}
#[test]
fn zsh_module_metadata_accepts_next_line_function_body_brace() {
let path = Path::new("/tmp/project/install/01-package-manager.zsh");
let source = "\
#!/bin/zsh
module_name=\"package-manager\"
module_description=\"Install package manager\"
module_main_function=\"run_package_manager_module\"
function run_package_manager_module
{
:
}
";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
for name in ["module_name", "module_description", "module_main_function"] {
assert!(has_consumed_name(&contract, name), "{contract:?}");
}
}
#[test]
fn zsh_module_metadata_accepts_name_paren_next_line_function_body_brace() {
let path = Path::new("/tmp/project/install/01-package-manager.zsh");
let source = "\
#!/bin/zsh
module_name=\"package-manager\"
module_description=\"Install package manager\"
module_main_function=\"run_package_manager_module\"
run_package_manager_module()
{
:
}
";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
for name in ["module_name", "module_description", "module_main_function"] {
assert!(has_consumed_name(&contract, name), "{contract:?}");
}
}
#[test]
fn zsh_module_metadata_requires_exact_main_function_name() {
let path = Path::new("/tmp/project/install/01-package-manager.zsh");
let source = "\
#!/bin/zsh
module_name=\"package-manager\"
module_description=\"Install package manager\"
module_main_function=\"run\"
runner() {
:
}
";
assert!(contract_for_shell(path, source, ShellDialect::Zsh).is_none());
}
#[test]
fn zsh_module_metadata_rejects_command_before_brace_group() {
let path = Path::new("/tmp/project/install/01-package-manager.zsh");
let source = "\
#!/bin/zsh
module_name=\"package-manager\"
module_description=\"Install package manager\"
module_main_function=\"run\"
run
{
print -r -- not-a-function
}
";
assert!(contract_for_shell(path, source, ShellDialect::Zsh).is_none());
}
#[test]
fn unknown_generic_runtime_paths_do_not_get_zsh_runtime_contracts() {
let path = Path::new("/tmp/project/plugins/example");
let source = "print -r -- \"$history\" \"$words\"\n";
assert!(contract_for_shell(path, source, ShellDialect::Unknown).is_none());
}
#[test]
fn pathless_zsh_sources_do_not_get_ambient_runtime_contracts() {
let source = "print -r -- \"$history\" \"$words\" \"$fg_bold\"\n";
assert!(contract_for_optional_path(None, source, ShellDialect::Zsh).is_none());
}
#[test]
fn zsh_runtime_contract_marks_exact_output_parameters_consumed() {
let path = Path::new("/tmp/zsh/ohmyzsh/plugins/example/example.plugin.zsh");
let source = "\
reply=(one two)
REPLY=value
WORDCHARS=''
compstate[insert]=menu
";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
for name in ["reply", "REPLY", "WORDCHARS", "compstate"] {
assert!(has_consumed_name(&contract, name), "{contract:?}");
}
}
#[test]
fn pathless_zsh_hook_arrays_do_not_get_ambient_contracts() {
let source = "precmd_functions=(${precmd_functions:#_example_precmd})\n";
assert!(contract_for_optional_path(None, source, ShellDialect::Zsh).is_none());
}
#[test]
fn zsh_runtime_contract_does_not_consume_read_only_output_parameters() {
let path = Path::new("/tmp/zsh/ohmyzsh/plugins/example/example.plugin.zsh");
let source = "print -r -- \"$reply\" \"$REPLY\" \"$compstate\"\n";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
for name in ["reply", "REPLY", "compstate"] {
assert!(!has_consumed_name(&contract, name), "{contract:?}");
}
}
#[test]
fn powerlevel10k_internal_bootstrap_files_initialize_injected_bindings() {
let cases = [
(
"/tmp/zsh/powerlevel10k/internal/p10k.zsh",
"[[ $__p9k_sourced == 1 ]] || return\neval \"$__p9k_intro\"\nprint -r -- \"$__p9k_root_dir\"\n",
&["__p9k_sourced", "__p9k_root_dir", "__p9k_intro"][..],
),
(
"/tmp/zsh/powerlevel10k/internal/configure.zsh",
"print -r -- \"$__p9k_root_dir\"\neval \"$__p9k_intro\"\n",
&["__p9k_root_dir", "__p9k_intro"][..],
),
(
"/tmp/zsh/powerlevel10k/internal/wizard.zsh",
"print -r -- \"$__p9k_root_dir\"\neval \"$__p9k_intro\"\n",
&["__p9k_root_dir", "__p9k_intro"][..],
),
];
for (path, source, expected_bindings) in cases {
let contract = contract_for_shell(Path::new(path), source, ShellDialect::Zsh).unwrap();
for name in expected_bindings {
assert!(has_initialized_binding(&contract, name), "{contract:?}");
}
}
}
#[test]
fn powerlevel10k_internal_paths_consume_public_and_private_config_prefixes() {
let path = Path::new("/tmp/zsh/powerlevel10k/internal/p10k.zsh");
let source = "\
print -r -- \"$POWERLEVEL9K_LEFT_PROMPT_ELEMENTS\"
print -r -- \"$_POWERLEVEL9K_RBENV_SOURCES\"
";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
for prefix in ["POWERLEVEL9K_", "_POWERLEVEL9K_"] {
assert!(has_consumed_prefix(&contract, prefix), "{contract:?}");
}
}
#[test]
fn powerlevel10k_internal_paths_get_configure_state_bindings() {
let path = Path::new("/tmp/zsh/powerlevel10k/internal/wizard.zsh");
let source = "\
print -r -- \"$__p9k_cfg_path\" \"$__p9k_cfg_path_u\" \"$__p9k_zshrc\" \"$__p9k_zd\" \"$__p9k_wizard_columns\"
";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
for name in [
"__p9k_cfg_path",
"__p9k_cfg_path_u",
"__p9k_zshrc",
"__p9k_zd",
"__p9k_wizard_columns",
] {
assert!(has_initialized_binding(&contract, name), "{contract:?}");
}
}
#[test]
fn powerlevel10k_wizard_paths_get_shared_ui_state_bindings() {
let wizard_path = Path::new("/tmp/zsh/powerlevel10k/internal/wizard.zsh");
let wizard_source = "print -r -- \"$icons[VCS_GIT_ICON]\"; (( _p9k_term_has_href ))\n";
let wizard_contract =
contract_for_shell(wizard_path, wizard_source, ShellDialect::Zsh).unwrap();
for name in ["icons", "_p9k_term_has_href"] {
assert!(
has_initialized_binding(&wizard_contract, name),
"{wizard_contract:?}"
);
}
}
#[test]
fn powerlevel10k_p10k_paths_get_instant_prompt_and_vcs_info_bindings() {
let path = Path::new("/tmp/zsh/powerlevel10k/internal/p10k.zsh");
let source = "\
vcs_info
print -r -- \"$vcs_info_msg_0_\" \"$__p9k_intro_no_locale\" \"$__p9k_dump_file\" \"$__p9k_instant_prompt_time\"
";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
for name in [
"vcs_info_msg_0_",
"__p9k_intro_no_locale",
"__p9k_dump_file",
"__p9k_instant_prompt_time",
] {
assert!(has_initialized_binding(&contract, name), "{contract:?}");
}
}
#[test]
fn powerlevel10k_gitstatus_zsh_contract_initializes_intro_base_and_consumes_status_prefix() {
let path = Path::new("/tmp/zsh/powerlevel10k/gitstatus/gitstatus.plugin.zsh");
let source = "\
eval \"$__p9k_intro_base\"
typeset -g VCS_STATUS_RESULT=ok
typeset -g VCS_STATUS_HAS_STAGED=1
";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
assert!(has_initialized_binding(&contract, "__p9k_intro_base"));
assert!(has_consumed_prefix(&contract, "VCS_STATUS_"));
}
#[test]
fn powerlevel10k_gitstatus_sh_contract_consumes_status_prefix() {
let path = Path::new("/tmp/zsh/powerlevel10k/gitstatus/gitstatus.plugin.sh");
let source = "\
VCS_STATUS_RESULT=ok
VCS_STATUS_HAS_STAGED=1
";
let contract = contract_for_shell(path, source, ShellDialect::Sh).unwrap();
assert!(has_consumed_prefix(&contract, "VCS_STATUS_"));
assert!(!has_initialized_binding(&contract, "__p9k_intro_base"));
}
#[test]
fn zsh_syntax_highlighting_test_data_expected_values_are_consumed() {
let path =
Path::new("/tmp/zsh/zsh-syntax-highlighting/highlighters/main/test-data/example.zsh");
let source = "expected_region_highlight=('1 4 fg=red')\n";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
assert!(has_consumed_prefix(&contract, "expected_"));
}
#[test]
fn zsh_syntax_highlighting_test_harness_expected_values_are_consumed() {
let path = Path::new("/tmp/zsh/zsh-syntax-highlighting/tests/test-zprof.zsh");
let source = "\
run_test_internal() {
expected_region_highlight=()
true && _zsh_highlight
}
";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
assert!(has_consumed_prefix(&contract, "expected_"));
}
#[test]
fn zsh_autosuggestion_config_namespace_is_consumed_on_project_paths() {
let path = Path::new("/tmp/zsh/zsh-autosuggestions/src/config.zsh");
let source = "ZSH_AUTOSUGGEST_STRATEGY=(history)\n";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
assert!(has_consumed_prefix(&contract, "ZSH_AUTOSUGGEST_"));
}
#[test]
fn prezto_autosuggestions_module_paths_consume_config_prefix() {
let path = Path::new("/tmp/zsh/prezto/modules/autosuggestions/init.zsh");
let source = "ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8'\n";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
assert!(has_consumed_prefix(&contract, "ZSH_AUTOSUGGEST_"));
}
#[test]
fn zsh_plugin_request_contracts_consume_known_framework_config_prefixes() {
let path = Path::new("/tmp/zdot/modules/autocompletion/autocompletion.zsh");
let autosuggest = request_contract_for(
path,
plugin_request(
PluginFramework::Other("zsh-autosuggestions".to_owned()),
"zsh-autosuggestions",
),
);
assert!(has_consumed_prefix(&autosuggest, "ZSH_AUTOSUGGEST_"));
let abbr = request_contract_for(
path,
plugin_request(PluginFramework::Other("zsh-abbr".to_owned()), "zsh-abbr"),
);
assert!(has_consumed_prefix(&abbr, "ABBR_"));
let highlighting = request_contract_for(
path,
plugin_request(
PluginFramework::Other("zsh-syntax-highlighting".to_owned()),
"zsh-syntax-highlighting",
),
);
assert!(has_consumed_prefix(&highlighting, "ZSH_HIGHLIGHT_"));
}
#[test]
fn zsh_syntax_highlighting_highlighter_paths_inherit_user_options() {
let path = Path::new("/tmp/zsh/zsh-syntax-highlighting/highlighters/main/main-highlighter.zsh");
let source = "print -r -- \"$zsyh_user_options[ignorebraces]\"\n";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
assert!(has_initialized_binding(&contract, "zsyh_user_options"));
}
#[test]
fn zsh_syntax_highlighting_test_paths_consume_harness_state() {
let path = Path::new("/tmp/zsh/zsh-syntax-highlighting/tests/test-highlighting.zsh");
let source = "\
PREBUFFER=''
MARK=1
REGION_ACTIVE=2
";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
for name in ["PREBUFFER", "MARK", "REGION_ACTIVE"] {
assert!(has_consumed_name(&contract, name), "{contract:?}");
}
}
#[test]
fn zsh_config_prefix_contracts_are_zsh_only() {
let path = Path::new("/tmp/project/script.sh");
let source = "POWERLEVEL9K_DIR_FOREGROUND=31\n";
assert!(contract_for_shell(path, source, ShellDialect::Sh).is_none());
}
#[test]
fn ordinary_zsh_paths_do_not_get_config_prefix_contracts() {
let path = Path::new("/tmp/project/plugins/example.zsh");
let source = "\
POWERLEVEL9K_DIR_FOREGROUND=31
ZDOT_MODULE_NAME=prompt
";
assert!(contract_for_shell(path, source, ShellDialect::Zsh).is_none());
}
#[test]
fn shebangless_zsh_dotfiles_get_config_prefix_contracts() {
let path = Path::new("/tmp/home/.zshrc");
let source = "\
POWERLEVEL9K_DIR_FOREGROUND=31
ZDOT_MODULE_NAME=prompt
";
let contract = contract_for_shell(path, source, ShellDialect::Unknown).unwrap();
assert!(has_consumed_prefix(&contract, "POWERLEVEL9K_"));
assert!(has_consumed_prefix(&contract, "ZDOT_"));
}
#[test]
fn zshrc_named_directories_do_not_count_as_dotfiles() {
let path = Path::new("/tmp/project/zshrc-theme/prompt.zsh");
let source = "\
POWERLEVEL9K_DIR_FOREGROUND=31
ZDOT_MODULE_NAME=prompt
";
assert!(contract_for_shell(path, source, ShellDialect::Zsh).is_none());
}
#[test]
fn unknown_zshrc_backup_paths_do_not_get_runtime_contracts() {
let path = Path::new("/tmp/project/zshrc_backup/plugins/example.zsh");
let source = "print -r -- \"$history\"\n";
assert!(contract_for_shell(path, source, ShellDialect::Unknown).is_none());
}
#[test]
fn zsh_sourced_helpers_initialize_caller_scoped_array_length_targets() {
let path = Path::new("/tmp/project/core/update_core.zsh");
let source = "\
#!/bin/zsh
safe_rm() {
if [[ ${#dry_run[@]} -gt 0 ]]; then
print -r -- dry
fi
}
";
let contract = contract_for_shell(path, source, ShellDialect::Zsh).unwrap();
assert!(has_initialized_binding(&contract, "dry_run"));
assert!(!contract.externally_consumed_bindings);
}
#[test]
fn disabling_zsh_caller_scoped_array_contract_selector_keeps_helper_array_reads_reportable() {
let path = Path::new("/tmp/project/core/update_core.zsh");
let source = "\
#!/bin/zsh
safe_rm() {
if [[ ${#dry_run[@]} -gt 0 ]]; then
print -r -- dry
fi
}
";
let contracts = Arc::new(
ResolvedAmbientContracts::resolve(
"/tmp/project",
AmbientContractConfig {
disabled: vec!["zsh/caller-scoped-arrays".to_owned()],
..AmbientContractConfig::default()
},
)
.unwrap(),
);
let contract =
contract_for_optional_path_with_contracts(Some(path), source, ShellDialect::Zsh, contracts);
assert!(contract.is_none(), "{contract:?}");
}
#[test]
fn zsh_sourced_helper_array_contracts_ignore_comments() {
let path = Path::new("/tmp/project/core/update_core.zsh");
let source = "\
#!/bin/zsh
# if [[ ${#dry_run[@]} -gt 0 ]]; then
safe_rm() {
print -r -- $dry_run
}
";
let contract = contract_for_shell(path, source, ShellDialect::Zsh);
assert!(contract.is_none());
}
#[test]
fn generic_completion_paths_do_not_initialize_completion_contracts() {
let path = Path::new("/tmp/project/completions/example.sh");
let source = "\
helper() {
printf '%s\\n' \"$cur\" \"$cword\"
}
";
assert!(contract_for(path, source).is_none());
}
#[test]
fn generic_completion_paths_with_compreply_do_not_initialize_completion_contracts() {
let path = Path::new("/tmp/project/completions/example.sh");
let source = "\
helper() {
COMPREPLY=()
printf '%s\\n' \"$cur\" \"$cword\"
}
";
assert!(contract_for(path, source).is_none());
}
#[test]
fn bash_completion_paths_without_initializer_do_not_initialize_completion_contracts() {
let path = Path::new("/tmp/bash-completion/completions/example.bash");
let source = "\
_example() {
COMPREPLY=()
printf '%s\\n' \"$cur\" \"$cword\"
}
";
assert!(contract_for(path, source).is_none());
}
#[test]
fn bash_completion_paths_with_initializer_get_ambient_completion_contracts() {
let path = Path::new("/tmp/bash-completion/completions/example.bash");
let source = "\
_example() {
_init_completion || return
printf '%s\\n' \"$cur\" \"$cword\" \"$comp_args\"
}
";
let contract = contract_for(path, source).unwrap();
assert!(has_ambient_binding(&contract, "cur"));
assert!(has_ambient_binding(&contract, "cword"));
assert!(has_ambient_binding(&contract, "comp_args"));
assert!(!has_initialized_binding(&contract, "cur"));
assert!(!has_initialized_binding(&contract, "cword"));
assert!(!has_initialized_binding(&contract, "comp_args"));
assert!(!contract.externally_consumed_bindings);
}
#[test]
fn bash_completion_paths_with_chained_initializer_wrappers_get_ambient_completion_contracts() {
let path = Path::new("/tmp/bash-completion/completions/example.bash");
let source = "\
_example() {
command env LC_ALL=C _init_completion || return
printf '%s\\n' \"$cur\" \"$cword\"
}
";
let contract = contract_for(path, source).unwrap();
assert!(has_ambient_binding(&contract, "cur"));
assert!(has_ambient_binding(&contract, "cword"));
assert!(!has_initialized_binding(&contract, "cur"));
assert!(!has_initialized_binding(&contract, "cword"));
assert!(!contract.externally_consumed_bindings);
}
#[test]
fn bash_completion_paths_with_env_then_shell_wrapper_get_ambient_completion_contracts() {
let path = Path::new("/tmp/bash-completion/completions/example.bash");
let source = "\
_example() {
env LC_ALL=C command _init_completion || return
env LC_ALL=\"$locale\" _get_comp_words_by_ref cur || return
env LC_ALL=C env _comp_initialize || return
printf '%s\\n' \"$cur\" \"$cword\"
}
";
let contract = contract_for(path, source).unwrap();
assert!(has_ambient_binding(&contract, "cur"));
assert!(has_ambient_binding(&contract, "cword"));
assert!(!has_initialized_binding(&contract, "cur"));
assert!(!has_initialized_binding(&contract, "cword"));
assert!(!contract.externally_consumed_bindings);
}
#[test]
fn bash_completion_paths_with_external_initializer_wrappers_do_not_initialize_contracts() {
let path = Path::new("/tmp/bash-completion/completions/example.bash");
let source = "\
_example() {
sudo _init_completion || return
sudo env _init_completion || return
env LC_ALL=C sudo _get_comp_words_by_ref || return
printf '%s\\n' \"$cur\" \"$cword\"
}
";
assert!(contract_for(path, source).is_none());
}
#[test]
fn bash_completion_paths_with_subshell_initializer_do_not_initialize_contracts() {
let path = Path::new("/tmp/bash-completion/completions/example.bash");
let source = "\
_example() {
printf '%s\\n' \"$(_init_completion)\" \"$cur\" \"$cword\"
}
";
assert!(contract_for(path, source).is_none());
}
#[test]
fn bash_completion_paths_with_commented_initializer_do_not_initialize_contracts() {
let path = Path::new("/tmp/bash-completion/completions/example.bash");
let source = "\
# TODO: call _init_completion later
_example() {
printf '%s\\n' \"$cur\" \"$cword\"
}
";
assert!(contract_for(path, source).is_none());
}
#[test]
fn bash_completion_paths_with_wrapper_identifier_do_not_initialize_contracts() {
let path = Path::new("/tmp/bash-completion/completions/example.bash");
let source = "\
_example() {
my_init_completion_wrapper || return
printf '%s\\n' \"$cur\" \"$cword\"
}
";
assert!(contract_for(path, source).is_none());
}
#[test]
fn bash_completion_paths_with_initializer_definition_do_not_initialize_contracts() {
let path = Path::new("/tmp/bash-completion/completions/example.bash");
let source = "\
_init_completion() {
:
}
_example() {
printf '%s\\n' \"$cur\" \"$cword\"
}
";
assert!(contract_for(path, source).is_none());
}
#[test]
fn bash_completion_paths_with_separator_comment_do_not_initialize_contracts() {
let path = Path::new("/tmp/bash-completion/completions/example.bash");
let source = "\
noop;# _init_completion later
_example() {
printf '%s\\n' \"$cur\" \"$cword\"
}
";
assert!(contract_for(path, source).is_none());
}
#[test]
fn bash_completion_paths_with_initializer_in_heredoc_do_not_initialize_contracts() {
let path = Path::new("/tmp/bash-completion/completions/example.bash");
let source = "\
cat <<EOF
_init_completion
EOF
_example() {
printf '%s\\n' \"$cur\" \"$cword\"
}
";
assert!(contract_for(path, source).is_none());
}
#[test]
fn sourced_runtime_module_paths_do_not_initialize_arbitrary_reads() {
let path = Path::new("/tmp/LinuxGSM/lgsm/modules/command_backup.sh");
let source = "\
commandname=\"BACKUP\"
backup_run() {
printf '%s\\n' \"$lockdir\" \"$commandname\"
}
";
assert!(contract_for(path, source).is_none());
}