/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
libs/cli_parser/src/tests.rs
1 681 строка
42 KB
Bartek Iwańczuk
refactor: cut over CLI flag parsing to deno_cli_parser (#35982)
07 авг 2026, 12:18
Не верифицирован
07 авг 2026, 12:18
7ee9311
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. #![allow( clippy::useless_vec, reason = "svec! macro creates Vec<String> for test inputs" )] use pretty_assertions::assert_eq; use crate::convert::flags_from_vec; use crate::*; /// Helper: create Vec<String> from string literals. macro_rules! svec { ($($x:expr),* $(,)?) => { vec![$($x.to_string()),*] }; } // ---- Static command definitions for tests ---- // These mirror a simplified version of Deno's CLI structure. const GLOBAL_ARGS: &[ArgDef] = &[ ArgDef::new("help") .short('h') .long("help") .set_true() .global(), ArgDef::new("version") .short('V') .long("version") .set_true() .short_aliases(&['v']), ArgDef::new("log-level") .short('L') .long("log-level") .action(ArgAction::Set) .num_args(NumArgs::Exact(1)) .global(), ArgDef::new("quiet") .short('q') .long("quiet") .set_true() .global(), ]; const PERMISSION_ARGS: &[ArgDef] = &[ ArgDef::new("allow-all") .short('A') .long("allow-all") .set_true(), ArgDef::new("allow-read") .short('R') .long("allow-read") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("deny-read") .long("deny-read") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("allow-write") .short('W') .long("allow-write") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("deny-write") .long("deny-write") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("allow-net") .short('N') .long("allow-net") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("deny-net") .long("deny-net") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("allow-env") .short('E') .long("allow-env") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("deny-env") .long("deny-env") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("allow-run") .long("allow-run") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("deny-run") .long("deny-run") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("allow-sys") .short('S') .long("allow-sys") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("deny-sys") .long("deny-sys") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("allow-ffi") .long("allow-ffi") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("deny-ffi") .long("deny-ffi") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("allow-import") .short('I') .long("allow-import") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("deny-import") .long("deny-import") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("no-prompt").long("no-prompt").set_true(), ]; const COMPILE_ARGS: &[ArgDef] = &[ ArgDef::new("no-check") .long("no-check") .action(ArgAction::Set) .num_args(NumArgs::Optional) .require_equals(), ArgDef::new("import-map") .long("import-map") .action(ArgAction::Set) .num_args(NumArgs::Exact(1)) .value_name("FILE"), ArgDef::new("no-remote").long("no-remote").set_true(), ArgDef::new("no-npm").long("no-npm").set_true(), ArgDef::new("node-modules-dir") .long("node-modules-dir") .action(ArgAction::Set) .num_args(NumArgs::Optional) .require_equals(), ArgDef::new("config") .short('c') .long("config") .action(ArgAction::Set) .num_args(NumArgs::Exact(1)) .value_name("FILE"), ArgDef::new("no-config").long("no-config").set_true(), ArgDef::new("reload") .short('r') .long("reload") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("lock") .long("lock") .action(ArgAction::Set) .num_args(NumArgs::Optional) .require_equals(), ArgDef::new("no-lock").long("no-lock").set_true(), ArgDef::new("cert") .long("cert") .action(ArgAction::Set) .num_args(NumArgs::Exact(1)) .value_name("FILE"), ]; const RUN_ARGS: &[ArgDef] = &[ ArgDef::new("script_arg") .positional() .action(ArgAction::Set) .num_args(NumArgs::Exact(1)), ArgDef::new("check") .long("check") .action(ArgAction::Set) .num_args(NumArgs::Optional) .require_equals(), ArgDef::new("watch") .long("watch") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("hmr") .long("hmr") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("watch-exclude") .long("watch-exclude") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("no-clear-screen") .long("no-clear-screen") .set_true(), ArgDef::new("ext") .long("ext") .action(ArgAction::Set) .num_args(NumArgs::Exact(1)), ArgDef::new("env-file") .long("env-file") .action(ArgAction::Append) .num_args(NumArgs::Optional) .require_equals(), ArgDef::new("no-code-cache") .long("no-code-cache") .set_true(), // inspect args ArgDef::new("inspect") .long("inspect") .action(ArgAction::Set) .num_args(NumArgs::Optional) .require_equals(), ArgDef::new("inspect-brk") .long("inspect-brk") .action(ArgAction::Set) .num_args(NumArgs::Optional) .require_equals(), ArgDef::new("inspect-wait") .long("inspect-wait") .action(ArgAction::Set) .num_args(NumArgs::Optional) .require_equals(), // runtime misc ArgDef::new("cached-only").long("cached-only").set_true(), ArgDef::new("location") .long("location") .action(ArgAction::Set) .num_args(NumArgs::Exact(1)), ArgDef::new("v8-flags") .long("v8-flags") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("seed") .long("seed") .action(ArgAction::Set) .num_args(NumArgs::Exact(1)), ArgDef::new("enable-testing-features") .long("enable-testing-features") .set_true() .hidden(), ArgDef::new("coverage") .long("coverage") .action(ArgAction::Set) .num_args(NumArgs::Optional) .require_equals(), ]; const EVAL_ARGS: &[ArgDef] = &[ ArgDef::new("code_arg") .positional() .action(ArgAction::Set) .num_args(NumArgs::Exact(1)), ArgDef::new("print").short('p').long("print").set_true(), ArgDef::new("ext") .long("ext") .action(ArgAction::Set) .num_args(NumArgs::Exact(1)), // inspect args ArgDef::new("inspect") .long("inspect") .action(ArgAction::Set) .num_args(NumArgs::Optional) .require_equals(), ArgDef::new("inspect-brk") .long("inspect-brk") .action(ArgAction::Set) .num_args(NumArgs::Optional) .require_equals(), ArgDef::new("inspect-wait") .long("inspect-wait") .action(ArgAction::Set) .num_args(NumArgs::Optional) .require_equals(), ArgDef::new("v8-flags") .long("v8-flags") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("cached-only").long("cached-only").set_true(), ArgDef::new("location") .long("location") .action(ArgAction::Set) .num_args(NumArgs::Exact(1)), ArgDef::new("seed") .long("seed") .action(ArgAction::Set) .num_args(NumArgs::Exact(1)), ]; const FMT_ARGS: &[ArgDef] = &[ ArgDef::new("files") .positional() .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore), ArgDef::new("check").long("check").set_true(), ArgDef::new("watch") .long("watch") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("watch-exclude") .long("watch-exclude") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("no-clear-screen") .long("no-clear-screen") .set_true(), ArgDef::new("ext") .long("ext") .action(ArgAction::Set) .num_args(NumArgs::Exact(1)), ArgDef::new("ignore") .long("ignore") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("use-tabs") .long("use-tabs") .action(ArgAction::Set) .num_args(NumArgs::Optional) .require_equals(), ArgDef::new("line-width") .long("line-width") .action(ArgAction::Set) .num_args(NumArgs::Exact(1)), ArgDef::new("indent-width") .long("indent-width") .action(ArgAction::Set) .num_args(NumArgs::Exact(1)), ArgDef::new("single-quote") .long("single-quote") .action(ArgAction::Set) .num_args(NumArgs::Optional) .require_equals(), ArgDef::new("prose-wrap") .long("prose-wrap") .action(ArgAction::Set) .num_args(NumArgs::Exact(1)), ArgDef::new("no-semicolons") .long("no-semicolons") .action(ArgAction::Set) .num_args(NumArgs::Optional) .require_equals(), ArgDef::new("config") .short('c') .long("config") .action(ArgAction::Set) .num_args(NumArgs::Exact(1)), ArgDef::new("no-config").long("no-config").set_true(), ]; const TEST_ARGS: &[ArgDef] = &[ ArgDef::new("files") .positional() .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore), ArgDef::new("doc").long("doc").set_true(), ArgDef::new("no-run").long("no-run").set_true(), ArgDef::new("coverage") .long("coverage") .action(ArgAction::Set) .num_args(NumArgs::Optional) .require_equals(), ArgDef::new("clean").long("clean").set_true(), ArgDef::new("fail-fast") .long("fail-fast") .action(ArgAction::Set) .num_args(NumArgs::Optional) .require_equals(), ArgDef::new("filter") .long("filter") .action(ArgAction::Set) .num_args(NumArgs::Exact(1)), ArgDef::new("shuffle") .long("shuffle") .action(ArgAction::Set) .num_args(NumArgs::Optional) .require_equals(), ArgDef::new("parallel").long("parallel").set_true(), ArgDef::new("trace-leaks").long("trace-leaks").set_true(), ArgDef::new("watch") .long("watch") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("watch-exclude") .long("watch-exclude") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("no-clear-screen") .long("no-clear-screen") .set_true(), ArgDef::new("reporter") .long("reporter") .action(ArgAction::Set) .num_args(NumArgs::Exact(1)), ArgDef::new("junit-path") .long("junit-path") .action(ArgAction::Set) .num_args(NumArgs::Exact(1)), ArgDef::new("hide-stacktraces") .long("hide-stacktraces") .set_true(), ArgDef::new("ignore") .long("ignore") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("enable-testing-features") .long("enable-testing-features") .set_true() .hidden(), // compile/runtime args needed by test ArgDef::new("no-check") .long("no-check") .action(ArgAction::Set) .num_args(NumArgs::Optional) .require_equals(), ArgDef::new("config") .short('c') .long("config") .action(ArgAction::Set) .num_args(NumArgs::Exact(1)), ArgDef::new("no-config").long("no-config").set_true(), ArgDef::new("reload") .short('r') .long("reload") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("seed") .long("seed") .action(ArgAction::Set) .num_args(NumArgs::Exact(1)), ArgDef::new("v8-flags") .long("v8-flags") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("inspect") .long("inspect") .action(ArgAction::Set) .num_args(NumArgs::Optional) .require_equals(), ArgDef::new("inspect-brk") .long("inspect-brk") .action(ArgAction::Set) .num_args(NumArgs::Optional) .require_equals(), ]; const UPGRADE_ARGS: &[ArgDef] = &[ ArgDef::new("dry-run").long("dry-run").set_true(), ArgDef::new("force").short('f').long("force").set_true(), ArgDef::new("canary").long("canary").set_true(), ArgDef::new("release-candidate") .long("release-candidate") .long_aliases(&["rc"]) .set_true(), ArgDef::new("version") .long("version") .action(ArgAction::Set) .num_args(NumArgs::Exact(1)), ArgDef::new("output") .short('o') .long("output") .action(ArgAction::Set) .num_args(NumArgs::Exact(1)), ArgDef::new("version-or-hash-or-channel") .positional() .action(ArgAction::Set) .num_args(NumArgs::Optional), ]; const LINT_ARGS: &[ArgDef] = &[ ArgDef::new("files") .positional() .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore), ArgDef::new("rules").long("rules").set_true(), ArgDef::new("fix").long("fix").set_true(), ArgDef::new("rules-tags") .long("rules-tags") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("rules-include") .long("rules-include") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("rules-exclude") .long("rules-exclude") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("json").long("json").set_true(), ArgDef::new("compact").long("compact").set_true(), ArgDef::new("ignore") .long("ignore") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("watch") .long("watch") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("watch-exclude") .long("watch-exclude") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("no-clear-screen") .long("no-clear-screen") .set_true(), ArgDef::new("config") .short('c') .long("config") .action(ArgAction::Set) .num_args(NumArgs::Exact(1)), ArgDef::new("no-config").long("no-config").set_true(), ]; const DEPLOY_ARGS: &[ArgDef] = &[]; const SERVE_ARGS: &[ArgDef] = &[ ArgDef::new("script_arg") .positional() .action(ArgAction::Set) .num_args(NumArgs::Exact(1)), ArgDef::new("port") .long("port") .action(ArgAction::Set) .num_args(NumArgs::Exact(1)) .default_value("8000"), ArgDef::new("host") .long("host") .action(ArgAction::Set) .num_args(NumArgs::Exact(1)) .default_value("0.0.0.0"), ArgDef::new("parallel").long("parallel").set_true(), ArgDef::new("watch") .long("watch") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("hmr") .long("hmr") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("watch-exclude") .long("watch-exclude") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("no-clear-screen") .long("no-clear-screen") .set_true(), ArgDef::new("check") .long("check") .action(ArgAction::Set) .num_args(NumArgs::Optional) .require_equals(), ArgDef::new("inspect") .long("inspect") .action(ArgAction::Set) .num_args(NumArgs::Optional) .require_equals(), ArgDef::new("inspect-brk") .long("inspect-brk") .action(ArgAction::Set) .num_args(NumArgs::Optional) .require_equals(), ArgDef::new("inspect-wait") .long("inspect-wait") .action(ArgAction::Set) .num_args(NumArgs::Optional) .require_equals(), // compile args ArgDef::new("no-check") .long("no-check") .action(ArgAction::Set) .num_args(NumArgs::Optional) .require_equals(), ArgDef::new("config") .short('c') .long("config") .action(ArgAction::Set) .num_args(NumArgs::Exact(1)), ArgDef::new("no-config").long("no-config").set_true(), ArgDef::new("reload") .short('r') .long("reload") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("lock") .long("lock") .action(ArgAction::Set) .num_args(NumArgs::Optional) .require_equals(), ArgDef::new("no-lock").long("no-lock").set_true(), ArgDef::new("cert") .long("cert") .action(ArgAction::Set) .num_args(NumArgs::Exact(1)), ArgDef::new("env-file") .long("env-file") .action(ArgAction::Append) .num_args(NumArgs::Optional) .require_equals(), ArgDef::new("no-code-cache") .long("no-code-cache") .set_true(), // runtime misc ArgDef::new("cached-only").long("cached-only").set_true(), ArgDef::new("location") .long("location") .action(ArgAction::Set) .num_args(NumArgs::Exact(1)), ArgDef::new("v8-flags") .long("v8-flags") .action(ArgAction::Append) .num_args(NumArgs::ZeroOrMore) .require_equals() .value_delimiter(','), ArgDef::new("seed") .long("seed") .action(ArgAction::Set) .num_args(NumArgs::Exact(1)), ]; static TEST_ROOT: CommandDef = CommandDef { name: "deno", about: "A modern JavaScript and TypeScript runtime", aliases: &[], args: GLOBAL_ARGS, arg_groups: &[], subcommands: &[ CommandDef { name: "run", about: "Run a JavaScript or TypeScript program", aliases: &[], args: RUN_ARGS, arg_groups: &[PERMISSION_ARGS, COMPILE_ARGS], subcommands: &[], default_subcommand: None, trailing_var_arg: true, passthrough: false, keep_double_dash: true, }, CommandDef { name: "serve", about: "Run a server", aliases: &[], args: SERVE_ARGS, arg_groups: &[PERMISSION_ARGS], subcommands: &[], default_subcommand: None, trailing_var_arg: true, passthrough: false, keep_double_dash: false, }, CommandDef { name: "eval", about: "Evaluate a script", aliases: &[], args: EVAL_ARGS, arg_groups: &[PERMISSION_ARGS, COMPILE_ARGS], subcommands: &[], default_subcommand: None, trailing_var_arg: true, passthrough: false, keep_double_dash: false, }, CommandDef { name: "fmt", about: "Format source files", aliases: &[], args: FMT_ARGS, arg_groups: &[], subcommands: &[], default_subcommand: None, trailing_var_arg: false, passthrough: false, keep_double_dash: false, }, CommandDef { name: "lint", about: "Lint source files", aliases: &[], args: LINT_ARGS, arg_groups: &[], subcommands: &[], default_subcommand: None, trailing_var_arg: false, passthrough: false, keep_double_dash: false, }, CommandDef { name: "test", about: "Run tests", aliases: &[], args: TEST_ARGS, arg_groups: &[PERMISSION_ARGS], subcommands: &[], default_subcommand: None, trailing_var_arg: true, passthrough: false, keep_double_dash: false, }, CommandDef { name: "upgrade", about: "Upgrade deno executable", aliases: &[], args: UPGRADE_ARGS, arg_groups: &[], subcommands: &[], default_subcommand: None, trailing_var_arg: false, passthrough: false, keep_double_dash: false, }, CommandDef { name: "deploy", about: "Deploy to Deno Deploy", aliases: &[], args: DEPLOY_ARGS, arg_groups: &[], subcommands: &[], default_subcommand: None, trailing_var_arg: false, passthrough: true, keep_double_dash: false, }, ], default_subcommand: Some("run"), trailing_var_arg: false, passthrough: false, keep_double_dash: false, }; // ---- Tests ---- #[test] fn basic_run() { let r = parse(&TEST_ROOT, &svec!["deno", "run", "script.ts"]).unwrap(); assert_eq!(r.subcommand.as_deref(), Some("run")); assert_eq!(r.get_one("script_arg"), Some("script.ts")); assert!(!r.get_bool("allow-all")); } #[test] fn run_with_allow_all() { let r = parse(&TEST_ROOT, &svec!["deno", "run", "-A", "script.ts"]).unwrap(); assert_eq!(r.subcommand.as_deref(), Some("run")); assert!(r.get_bool("allow-all")); assert_eq!(r.get_one("script_arg"), Some("script.ts")); } #[test] fn run_with_permission_values() { let r = parse( &TEST_ROOT, &svec![ "deno", "run", "--allow-read=/etc,/var", "--allow-net=localhost:8080", "script.ts" ], ) .unwrap(); assert_eq!(r.subcommand.as_deref(), Some("run")); assert_eq!( r.get_many("allow-read"), Some(vec!["/etc".to_string(), "/var".to_string()].as_slice()) ); assert_eq!( r.get_many("allow-net"), Some(vec!["localhost:8080".to_string()].as_slice()) ); } #[test] fn run_with_bare_permission_flags() { // --allow-read without =value means "allow all reads" let r = parse( &TEST_ROOT, &svec!["deno", "run", "--allow-read", "--allow-write", "script.ts"], ) .unwrap(); assert!(r.get_bool("allow-read")); assert!(r.get_bool("allow-write")); // No values means unrestricted assert!(r.get_many("allow-read").unwrap().is_empty()); assert_eq!(r.get_one("script_arg"), Some("script.ts")); } #[test] fn run_trailing_args() { let r = parse( &TEST_ROOT, &svec!["deno", "run", "script.ts", "arg1", "--flag", "arg2"], ) .unwrap(); assert_eq!(r.get_one("script_arg"), Some("script.ts")); assert_eq!(r.trailing, vec!["arg1", "--flag", "arg2"]); } #[test] fn run_double_dash_trailing() { let r = parse( &TEST_ROOT, &svec!["deno", "run", "script.ts", "--", "arg1", "--flag"], ) .unwrap(); assert_eq!(r.get_one("script_arg"), Some("script.ts")); // After script_arg is consumed, the `--` separator is kept in the forwarded // trailing args to match clap's trailing-var-arg behavior. assert_eq!(r.trailing, vec!["--", "arg1", "--flag"]); } #[test] fn global_flags_before_subcommand() { let r = parse( &TEST_ROOT, &svec![ "deno", "--log-level", "debug", "--quiet", "run", "script.ts" ], ) .unwrap(); assert_eq!(r.subcommand.as_deref(), Some("run")); assert_eq!(r.get_one("log-level"), Some("debug")); assert!(r.get_bool("quiet")); assert_eq!(r.get_one("script_arg"), Some("script.ts")); } #[test] fn global_flags_after_subcommand() { let r = parse( &TEST_ROOT, &svec![ "deno", "run", "--log-level", "debug", "--quiet", "script.ts" ], ) .unwrap(); assert_eq!(r.subcommand.as_deref(), Some("run")); assert_eq!(r.get_one("log-level"), Some("debug")); assert!(r.get_bool("quiet")); } #[test] fn upgrade_subcommand() { let r = parse( &TEST_ROOT, &svec!["deno", "upgrade", "--dry-run", "--force"], ) .unwrap(); assert_eq!(r.subcommand.as_deref(), Some("upgrade")); assert!(r.get_bool("dry-run")); assert!(r.get_bool("force")); } #[test] fn upgrade_with_positional() { let r = parse(&TEST_ROOT, &svec!["deno", "upgrade", "canary"]).unwrap(); assert_eq!(r.subcommand.as_deref(), Some("upgrade")); assert_eq!(r.get_one("version-or-hash-or-channel"), Some("canary")); } #[test] fn run_reload() { let r = parse(&TEST_ROOT, &svec!["deno", "run", "--reload", "script.ts"]).unwrap(); assert!(r.get_bool("reload")); assert_eq!(r.get_one("script_arg"), Some("script.ts")); } #[test] fn run_reload_with_values() { let r = parse( &TEST_ROOT, &svec![ "deno", "run", "--reload=http://example.com,http://foo.com", "script.ts" ], ) .unwrap(); assert!(r.get_bool("reload")); assert_eq!( r.get_many("reload"), Some( vec![ "http://example.com".to_string(), "http://foo.com".to_string() ] .as_slice() ) ); } #[test] fn run_watch() { let r = parse(&TEST_ROOT, &svec!["deno", "run", "--watch", "script.ts"]).unwrap(); assert!(r.get_bool("watch")); assert_eq!(r.get_one("script_arg"), Some("script.ts")); } #[test] fn run_watch_with_paths() { let r = parse( &TEST_ROOT, &svec!["deno", "run", "--watch=src/,lib/", "script.ts"], ) .unwrap(); assert!(r.get_bool("watch")); assert_eq!( r.get_many("watch"), Some(vec!["src/".to_string(), "lib/".to_string()].as_slice()) ); } #[test] fn run_v8_flags() { let r = parse( &TEST_ROOT, &svec![ "deno", "run", "--v8-flags=--max-old-space-size=4096,--expose-gc", "script.ts" ], ) .unwrap(); assert_eq!( r.get_many("v8-flags"), Some( vec![ "--max-old-space-size=4096".to_string(), "--expose-gc".to_string() ] .as_slice() ) ); } #[test] fn run_seed() { let r = parse( &TEST_ROOT, &svec!["deno", "run", "--seed", "42", "script.ts"], ) .unwrap(); assert_eq!(r.get_one("seed"), Some("42")); } #[test] fn run_config() { let r = parse( &TEST_ROOT, &svec!["deno", "run", "--config", "deno.json", "script.ts"], ) .unwrap(); assert_eq!(r.get_one("config"), Some("deno.json")); } #[test] fn run_config_short() { let r = parse( &TEST_ROOT, &svec!["deno", "run", "-c", "deno.json", "script.ts"], ) .unwrap(); assert_eq!(r.get_one("config"), Some("deno.json")); } #[test] fn fmt_basic() { let r = parse(&TEST_ROOT, &svec!["deno", "fmt", "file1.ts", "file2.ts"]).unwrap(); assert_eq!(r.subcommand.as_deref(), Some("fmt")); assert_eq!( r.get_many("files"), Some(vec!["file1.ts".to_string(), "file2.ts".to_string()].as_slice()) ); } #[test] fn fmt_check() { let r = parse(&TEST_ROOT, &svec!["deno", "fmt", "--check", "file.ts"]).unwrap(); assert!(r.get_bool("check")); } #[test] fn fmt_options() { let r = parse( &TEST_ROOT, &svec![ "deno", "fmt", "--use-tabs", "--line-width", "120", "--indent-width", "4", "--single-quote", "--prose-wrap", "always", "--no-semicolons", ], ) .unwrap(); assert!(r.get_bool("use-tabs")); assert_eq!(r.get_one("line-width"), Some("120")); assert_eq!(r.get_one("indent-width"), Some("4")); assert!(r.get_bool("single-quote")); assert_eq!(r.get_one("prose-wrap"), Some("always")); assert!(r.get_bool("no-semicolons")); } #[test] fn lint_basic() { let r = parse(&TEST_ROOT, &svec!["deno", "lint", "file.ts"]).unwrap(); assert_eq!(r.subcommand.as_deref(), Some("lint")); assert_eq!( r.get_many("files"), Some(vec!["file.ts".to_string()].as_slice()) ); } #[test] fn lint_rules_include() { let r = parse( &TEST_ROOT, &svec!["deno", "lint", "--rules-include=no-var,no-eval"], ) .unwrap(); assert_eq!( r.get_many("rules-include"), Some(vec!["no-var".to_string(), "no-eval".to_string()].as_slice()) ); } #[test] fn test_basic() { let r = parse(&TEST_ROOT, &svec!["deno", "test"]).unwrap(); assert_eq!(r.subcommand.as_deref(), Some("test")); } #[test] fn test_with_filter() { let r = parse(&TEST_ROOT, &svec!["deno", "test", "--filter", "my_test"]).unwrap(); assert_eq!(r.get_one("filter"), Some("my_test")); } #[test] fn test_fail_fast_no_value() { let r = parse(&TEST_ROOT, &svec!["deno", "test", "--fail-fast"]).unwrap(); assert!(r.get_bool("fail-fast")); assert_eq!(r.get_one("fail-fast"), None); } #[test] fn test_fail_fast_with_value() { let r = parse(&TEST_ROOT, &svec!["deno", "test", "--fail-fast=3"]).unwrap(); assert!(r.get_bool("fail-fast")); assert_eq!(r.get_one("fail-fast"), Some("3")); } #[test] fn test_shuffle_no_value() { let r = parse(&TEST_ROOT, &svec!["deno", "test", "--shuffle"]).unwrap(); assert!(r.get_bool("shuffle")); } #[test] fn test_shuffle_with_value() { let r = parse(&TEST_ROOT, &svec!["deno", "test", "--shuffle=42"]).unwrap(); assert_eq!(r.get_one("shuffle"), Some("42")); } #[test] fn test_reporter() { let r = parse(&TEST_ROOT, &svec!["deno", "test", "--reporter", "dot"]).unwrap(); assert_eq!(r.get_one("reporter"), Some("dot")); } #[test] fn test_watch() { let r = parse(&TEST_ROOT, &svec!["deno", "test", "--watch"]).unwrap(); assert!(r.get_bool("watch")); } #[test] fn test_watch_with_no_clear_screen() { let r = parse( &TEST_ROOT, &svec!["deno", "test", "--watch", "--no-clear-screen"], ) .unwrap(); assert!(r.get_bool("watch")); assert!(r.get_bool("no-clear-screen")); } #[test] fn eval_basic() { let r = parse(&TEST_ROOT, &svec!["deno", "eval", "console.log('hello')"]).unwrap(); assert_eq!(r.subcommand.as_deref(), Some("eval")); assert_eq!(r.get_one("code_arg"), Some("console.log('hello')")); } #[test] fn eval_print() { let r = parse(&TEST_ROOT, &svec!["deno", "eval", "-p", "1+1"]).unwrap(); assert!(r.get_bool("print")); assert_eq!(r.get_one("code_arg"), Some("1+1")); } #[test] fn unknown_flag_error() { let err = parse( &TEST_ROOT, &svec!["deno", "run", "--unknown-flag", "script.ts"], ) .unwrap_err(); assert_eq!(err.kind, CliErrorKind::UnknownFlag); assert!(err.message.contains("--unknown-flag")); } #[test] fn unknown_flag_suggestion() { let err = parse( &TEST_ROOT, &svec!["deno", "run", "--alow-read", "script.ts"], ) .unwrap_err(); assert_eq!(err.kind, CliErrorKind::UnknownFlag); assert!(err.suggestion.is_some()); assert!(err.suggestion.unwrap().contains("--allow-read")); } #[test] fn missing_value_error() { let err = parse(&TEST_ROOT, &svec!["deno", "run", "--seed"]).unwrap_err(); assert_eq!(err.kind, CliErrorKind::MissingValue); } #[test] fn default_subcommand_run() { // `deno script.ts` parses with default subcommand args but subcommand is None // (the convert layer handles this as bare run) let r = parse(&TEST_ROOT, &svec!["deno", "script.ts"]).unwrap(); assert_eq!(r.subcommand.as_deref(), None); assert_eq!(r.get_one("script_arg"), Some("script.ts")); } #[test] fn default_subcommand_with_flags() { // `deno -A script.ts` parses with default subcommand args but subcommand is None let r = parse(&TEST_ROOT, &svec!["deno", "-A", "script.ts"]).unwrap(); assert_eq!(r.subcommand.as_deref(), None); assert!(r.get_bool("allow-all")); assert_eq!(r.get_one("script_arg"), Some("script.ts")); } #[test] fn default_subcommand_with_allow_read_values() { let r = parse(&TEST_ROOT, &svec!["deno", "--allow-read=/tmp", "script.ts"]) .unwrap(); assert_eq!(r.subcommand.as_deref(), None); assert_eq!( r.get_many("allow-read"), Some(vec!["/tmp".to_string()].as_slice()) ); } #[test] fn passthrough_subcommand() { let r = parse( &TEST_ROOT, &svec!["deno", "deploy", "--project=myapp", "--prod"], ) .unwrap(); assert_eq!(r.subcommand.as_deref(), Some("deploy")); assert_eq!(r.trailing, vec!["--project=myapp", "--prod"]); } #[test] fn combined_short_flags() { let r = parse(&TEST_ROOT, &svec!["deno", "run", "-RWNE", "script.ts"]).unwrap(); assert!(r.get_bool("allow-read")); assert!(r.get_bool("allow-write")); assert!(r.get_bool("allow-net")); assert!(r.get_bool("allow-env")); } #[test] fn upgrade_alias() { let r = parse(&TEST_ROOT, &svec!["deno", "upgrade", "--rc"]).unwrap(); assert!(r.get_bool("release-candidate")); } #[test] fn serve_basic() { let r = parse( &TEST_ROOT, &svec!["deno", "serve", "--port", "3000", "server.ts"], ) .unwrap(); assert_eq!(r.subcommand.as_deref(), Some("serve")); assert_eq!(r.get_one("port"), Some("3000")); assert_eq!(r.get_one("script_arg"), Some("server.ts")); } #[test] fn serve_with_permissions() { let r = parse( &TEST_ROOT, &svec!["deno", "serve", "-A", "--port", "8080", "server.ts"], ) .unwrap(); assert!(r.get_bool("allow-all")); assert_eq!(r.get_one("port"), Some("8080")); } #[test] fn run_inspect() { let r = parse(&TEST_ROOT, &svec!["deno", "run", "--inspect", "script.ts"]).unwrap(); assert!(r.get_bool("inspect")); } #[test] fn run_inspect_with_port() { let r = parse( &TEST_ROOT, &svec!["deno", "run", "--inspect=127.0.0.1:9229", "script.ts"], ) .unwrap(); assert_eq!(r.get_one("inspect"), Some("127.0.0.1:9229")); } #[test] fn run_inspect_brk() { let r = parse( &TEST_ROOT, &svec!["deno", "run", "--inspect-brk", "script.ts"], ) .unwrap(); assert!(r.get_bool("inspect-brk")); } #[test] fn no_check() { let r = parse(&TEST_ROOT, &svec!["deno", "run", "--no-check", "script.ts"]) .unwrap(); assert!(r.get_bool("no-check")); } #[test] fn no_check_remote() { let r = parse( &TEST_ROOT, &svec!["deno", "run", "--no-check=remote", "script.ts"], ) .unwrap(); assert_eq!(r.get_one("no-check"), Some("remote")); } #[test] fn run_import_map() { let r = parse( &TEST_ROOT, &svec![ "deno", "run", "--import-map", "import_map.json", "script.ts" ], ) .unwrap(); assert_eq!(r.get_one("import-map"), Some("import_map.json")); } #[test] fn run_no_remote() { let r = parse( &TEST_ROOT, &svec!["deno", "run", "--no-remote", "script.ts"], ) .unwrap(); assert!(r.get_bool("no-remote")); } #[test] fn run_lock() { let r = parse( &TEST_ROOT, &svec!["deno", "run", "--lock=lock.json", "script.ts"], ) .unwrap(); assert_eq!(r.get_one("lock"), Some("lock.json")); } #[test] fn run_lock_bare() { let r = parse(&TEST_ROOT, &svec!["deno", "run", "--lock", "script.ts"]).unwrap(); // --lock with require_equals and no = means present but no value assert!(r.get_bool("lock")); assert_eq!(r.get_one("lock"), None); } #[test] fn run_no_lock() { let r = parse(&TEST_ROOT, &svec!["deno", "run", "--no-lock", "script.ts"]).unwrap(); assert!(r.get_bool("no-lock")); } #[test] fn watch_exclude() { let r = parse( &TEST_ROOT, &svec![ "deno", "run", "--watch", "--watch-exclude=node_modules/,dist/", "script.ts" ], ) .unwrap(); assert!(r.get_bool("watch")); assert_eq!( r.get_many("watch-exclude"), Some(vec!["node_modules/".to_string(), "dist/".to_string()].as_slice()) ); } #[test] fn test_with_allow_flags() { let r = parse(&TEST_ROOT, &svec!["deno", "test", "-A", "test_file.ts"]).unwrap(); assert_eq!(r.subcommand.as_deref(), Some("test")); assert!(r.get_bool("allow-all")); assert_eq!( r.get_many("files"), Some(vec!["test_file.ts".to_string()].as_slice()) ); } #[test] fn run_env_file() { let r = parse(&TEST_ROOT, &svec!["deno", "run", "--env-file", "script.ts"]) .unwrap(); assert!(r.get_bool("env-file")); assert_eq!(r.get_one("script_arg"), Some("script.ts")); } #[test] fn run_env_file_with_value() { let r = parse( &TEST_ROOT, &svec!["deno", "run", "--env-file=.env.local", "script.ts"], ) .unwrap(); assert_eq!(r.get_one("env-file"), Some(".env.local")); } #[test] fn run_coverage() { let r = parse( &TEST_ROOT, &svec!["deno", "run", "--coverage=./cov", "script.ts"], ) .unwrap(); assert_eq!(r.get_one("coverage"), Some("./cov")); } #[test] fn run_no_config() { let r = parse( &TEST_ROOT, &svec!["deno", "run", "--no-config", "script.ts"], ) .unwrap(); assert!(r.get_bool("no-config")); } #[test] fn levenshtein_basics() { // Verify the suggestion engine works let err = parse( &TEST_ROOT, &svec!["deno", "run", "--allow-raed", "script.ts"], ) .unwrap_err(); assert!(err.suggestion.as_ref().unwrap().contains("--allow-read")); } /// Every non-hidden option should carry a description in `--help`. The /// cutover from clap silently dropped all of them once already; this keeps /// the arg tables and the help renderer honest. /// /// The permission args are not covered here: they are hidden from the /// options table and documented in the "Permission options" section /// instead, which `permission_help_section_is_rendered` covers. #[test] fn args_have_help_text() { const EXEMPT: &[&str] = &[ // No help text on the clap side either. "branch", "builtin", "enable-testing-features", "eszip-internal-do-not-use", "external", "format", "help", "ext", "inspect-publish-uid", "json", "no-use-env-proxy", "pr", "unstable-byonm", ]; fn walk(cmd: &CommandDef, path: &str, missing: &mut Vec<String>) { let args = cmd .args .iter() .chain(cmd.arg_groups.iter().copied().flatten()); for arg in args { if arg.hidden || arg.positional || arg.help.is_empty() { if !arg.hidden && !arg.positional && !EXEMPT.contains(&arg.name) { missing.push(format!("{path} --{}", arg.name)); } continue; } } for sub in cmd.subcommands { walk(sub, &format!("{path} {}", sub.name), missing); } } let mut missing = Vec::new(); walk(&defs::DENO_ROOT, "deno", &mut missing); missing.sort(); missing.dedup(); assert!( missing.is_empty(), "these options render with no description in --help:\n {}", missing.join("\n ") ); } /// The permission flags are hidden from the options table, so the /// "Permission options" section is the only place they are documented. #[test] fn permission_help_section_is_rendered() { let run = help::render_subcommand_help(&defs::DENO_ROOT, "run").unwrap(); assert!(run.contains("Permission options:")); assert!(run.contains("Allow file system read access")); assert!(run.contains("DENO_TRACE_PERMISSIONS")); // Hidden from the table above, documented in the section below. assert!(!run.contains(" -R, --allow-read[=VALUE...] \n")); // Commands without permission args don't get the section. let fmt = help::render_subcommand_help(&defs::DENO_ROOT, "fmt").unwrap(); assert!(!fmt.contains("Permission options:")); } /// Commands that resolve a module graph but never run it take only the /// import permissions — not the full permission set. These wrongly accepted /// every permission flag during the clap cutover. #[test] fn graph_only_commands_take_only_import_permissions() { for cmd in ["bundle", "cache", "check", "doc", "info"] { let sub = defs::DENO_ROOT.find_subcommand(cmd).unwrap(); let names: Vec<&str> = sub .args .iter() .chain(sub.arg_groups.iter().copied().flatten()) .map(|a| a.name) .collect(); assert!( names.contains(&"allow-import") && names.contains(&"deny-import"), "`deno {cmd}` should accept the import permissions" ); for denied in [ "allow-all", "allow-read", "allow-write", "allow-net", "allow-env", "allow-run", "allow-sys", "allow-ffi", "no-prompt", "permission-set", ] { assert!( !names.contains(&denied), "`deno {cmd}` should not accept --{denied}" ); } } } /// Flags that landed on `main` after this branch forked and were only wired /// into the clap tables. The `--long` diff that caught the missing *flags* /// can't see missing value choices, conflicts, or conversion wiring, so pin /// them here. #[test] fn post_fork_flag_wiring() { // #36149: `deno fmt --ext` gained xml/svg. let fmt = defs::DENO_ROOT.find_subcommand("fmt").unwrap(); let ext = fmt.all_args().find(|a| a.name == "ext").unwrap(); let ValueParser::Choices(choices) = ext.value_parser.as_ref().unwrap() else { panic!("fmt --ext should validate against a fixed list"); }; assert!(choices.contains(&"xml")); assert!(choices.contains(&"svg")); // #35748: `deno task --members` conflicts with --recursive and --filter. let task = defs::DENO_ROOT.find_subcommand("task").unwrap(); let members = task.all_args().find(|a| a.name == "members").unwrap(); assert!(members.conflicts.contains(&"recursive")); assert!(members.conflicts.contains(&"filter")); let err = flags_from_vec(svec!["deno", "task", "--members", "--recursive", "dev"]) .unwrap_err(); assert!( err .to_string() .contains("the argument '--members' cannot be used with '--recursive'"), "got: {err}" ); // #36099: --minimum-dependency-age reaches add/remove/info, not just the // runtime subcommands. for args in [ svec!["deno", "add", "--min-dep-age=0", "jsr:@std/path"], svec!["deno", "remove", "--min-dep-age=0", "@std/path"], svec!["deno", "info", "--min-dep-age=0", "main.ts"], ] { let cmd = args[1].clone(); let flags = flags_from_vec(args).unwrap(); assert!( flags.minimum_dependency_age.is_some(), "`deno {cmd}` dropped --min-dep-age" ); } } /// clap attached the unstable flags to every subcommand via its shared /// `command()` builder, so a command that omits them rejects flags the old /// parser accepted. Eight commands were missing them after the cutover. #[test] fn every_subcommand_accepts_unstable_flags() { fn accepts_unstable(cmd: &CommandDef) -> bool { cmd.all_args().any(|a| a.name == "unstable-kv") && cmd.all_args().any(|a| a.name == "unstable") } let mut missing = Vec::new(); for sub in defs::DENO_ROOT.subcommands { // `help` is the one command clap builds without them. if sub.name == "help" || sub.name.starts_with("json_reference") { continue; } if !accepts_unstable(sub) { missing.push(sub.name); } } assert!( missing.is_empty(), "these subcommands reject --unstable-* flags: {missing:?}" ); // They stay hidden from `--help`, as they were under clap. let init = defs::DENO_ROOT.find_subcommand("init").unwrap(); assert!( init .all_args() .find(|a| a.name == "unstable") .unwrap() .hidden ); // ...except on `vendor`, which documents the deprecation notice. let vendor = defs::DENO_ROOT.find_subcommand("vendor").unwrap(); assert!( !vendor .all_args() .find(|a| a.name == "unstable") .unwrap() .hidden ); }