{"record":{"id":"aa135900e5984f33","repo":"jdx/mise","slug":"expected-identifier-after-dot","errorCode":null,"errorMessage":"expected identifier after dot","messagePattern":"expected identifier after dot","errorType":"validation","errorClass":"eyre::Report","httpStatus":null,"severity":"error","filePath":"crates/aqua-registry/src/template.rs","lineNumber":312,"sourceCode":"            tokens.next();\n            Ok(Expr::Literal(s.to_string()))\n        }\n        _ => Err(eyre!(\"expected argument\")),\n    }\n}\n\nfn parse_property_chain(\n    tokens: &mut std::iter::Peekable<std::slice::Iter<Token>>,\n    mut expr: Expr,\n) -> Result<Expr> {\n    while matches!(tokens.peek(), Some(Token::Dot)) {\n        tokens.next(); // consume dot\n        skip_whitespace(tokens);\n\n        if let Some(Token::Ident(prop)) = tokens.next() {\n            expr = Expr::PropertyAccess(Box::new(expr), prop.to_string());\n        } else {\n            bail!(\"expected identifier after dot\");\n        }\n    }\n\n    Ok(expr)\n}\n\nfn skip_whitespace(tokens: &mut std::iter::Peekable<std::slice::Iter<Token>>) {\n    while matches!(tokens.peek(), Some(Token::Whitespace(_))) {\n        tokens.next();\n    }\n}\n\n/// Function signature for template functions that return Value trait objects\ntype TemplateFn = fn(&[Box<dyn Value>]) -> Result<Box<dyn Value>>;\n\n/// Static registry of available template functions\nstatic FUNCTION_REGISTRY: LazyLock<HashMap<&'static str, TemplateFn>> = LazyLock::new(|| {\n    let mut registry: HashMap<&'static str, TemplateFn> = HashMap::new();","sourceCodeStart":294,"sourceCodeEnd":330,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/crates/aqua-registry/src/template.rs#L294-L330","documentation":"A command_wrapper artifact's first array element must be a bare command name: Path::file_name() of the name must equal the whole string and it must not be '.' or '..'. Any '/', trailing separator, or dot-only name is rejected because the name becomes both the staged script name and the symlinked command in bin.","triggerScenarios":"command_wrapper names like 'bin/tool', './tool', 'tool/', 'sub/dir/tool', '.', or '..' in the cask/mise metadata.","commonSituations":"Authors pasting a relative path into the wrapper name; metadata generators filling the field with a file path instead of a command name.","solutions":["Use a plain command name with no path separators ('tool', not 'bin/tool')","If the executable lives in a subdirectory, put the path in the 'executable' option and keep 'name' bare"],"exampleFix":"# command_wrapper artifact\n# before\n{\"command_wrapper\": [\"bin/my-tool\", {\"executable\": \"pkg/bin/tool\"}]}\n# after\n{\"command_wrapper\": [\"my-tool\", {\"executable\": \"pkg/bin/tool\"}]}","handlingStrategy":"validation","validationCode":"let p = Path::new(name);\nif p.file_name().and_then(|n| n.to_str()) != Some(name) || matches!(name, \".\" | \"..\") {\n    // reject before parse/install\n}","typeGuard":"fn is_bare_command_name(name: &str) -> bool {\n    Path::new(name).file_name().and_then(|n| n.to_str()) == Some(name)\n        && !matches!(name, \".\" | \"..\")\n}","tryCatchPattern":null,"preventionTips":["Use plain command names for wrappers; put paths in the executable option","Lint wrapper names for separators in metadata pipelines"],"tags":["rust","mise","brew-cask","command-wrapper","name-validation"],"backgroundTag":"invalid-command-name","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}