diff --git a/Cargo.lock b/Cargo.lock index 6d1f5a1a..6f06da44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -316,9 +316,11 @@ dependencies = [ "cached", "criterion", "indenter", + "insta", "peg", "pprof", "pretty_assertions", + "serde", "thiserror", "tracing", "utf8-chars", @@ -592,6 +594,18 @@ dependencies = [ "windows 0.44.0", ] +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "const_format" version = "0.2.33" @@ -850,6 +864,12 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "env_logger" version = "0.10.2" @@ -1282,6 +1302,19 @@ dependencies = [ "str_stack", ] +[[package]] +name = "insta" +version = "1.41.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e9ffc4d4892617c50a928c52b2961cb5174b6fc6ebf252b2fac9d21955c48b8" +dependencies = [ + "console", + "lazy_static", + "linked-hash-map", + "serde", + "similar", +] + [[package]] name = "is-terminal" version = "0.4.13" @@ -1385,6 +1418,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -2089,6 +2128,12 @@ dependencies = [ "libc", ] +[[package]] +name = "similar" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" + [[package]] name = "slab" version = "0.4.9" diff --git a/Cargo.toml b/Cargo.toml index 837f9006..15054396 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,3 +45,7 @@ strip = "debuginfo" opt-level = "z" lto = true codegen-units = 1 + +[profile.dev.package] +insta.opt-level = 3 +similar.opt-level = 3 diff --git a/brush-parser/Cargo.toml b/brush-parser/Cargo.toml index 5fd040dd..72d62a0d 100644 --- a/brush-parser/Cargo.toml +++ b/brush-parser/Cargo.toml @@ -30,7 +30,9 @@ utf8-chars = "3.0.5" anyhow = "1.0.92" assert_matches = "1.5.0" criterion = { version = "0.5.1", features = ["html_reports"] } +insta = {version = "1.41.1", features = ["yaml"] } pretty_assertions = { version = "1.4.1", features = ["unstable"] } +serde = { version = "1.0.214", features = ["derive"] } [target.'cfg(unix)'.dev-dependencies] pprof = { version = "0.13.0", features = ["criterion", "flamegraph"] } diff --git a/brush-parser/src/ast.rs b/brush-parser/src/ast.rs index cdc40c02..22889c8e 100644 --- a/brush-parser/src/ast.rs +++ b/brush-parser/src/ast.rs @@ -10,7 +10,7 @@ const DISPLAY_INDENT: &str = " "; /// Represents a complete shell program. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub struct Program { /// A sequence of complete shell commands. pub complete_commands: Vec, @@ -34,7 +34,7 @@ pub type CompleteCommandItem = CompoundListItem; /// Indicates whether the preceding command is executed synchronously or asynchronously. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub enum SeparatorOperator { /// The preceding command is executed asynchronously. Async, @@ -54,7 +54,7 @@ impl Display for SeparatorOperator { /// Represents a sequence of command pipelines connected by boolean operators. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub struct AndOrList { /// The first command pipeline. pub first: Pipeline, @@ -77,7 +77,7 @@ impl Display for AndOrList { /// succeeding pipeline. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub enum AndOr { /// Boolean AND operator; the embedded pipeline is only to be executed if the /// preceding command has succeeded. @@ -100,7 +100,7 @@ impl Display for AndOr { /// to the command that follows it. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub struct Pipeline { /// Indicates whether the result of the overall pipeline should be the logical /// negation of the result of the pipeline. @@ -128,7 +128,7 @@ impl Display for Pipeline { /// Represents a shell command. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub enum Command { /// A simple command, directly invoking an external command, a built-in command, /// a shell function, or similar. @@ -163,7 +163,7 @@ impl Display for Command { /// Represents a compound command, potentially made up of multiple nested commands. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub enum CompoundCommand { /// An arithmetic command, evaluating an arithmetic expression. Arithmetic(ArithmeticCommand), @@ -215,7 +215,7 @@ impl Display for CompoundCommand { /// An arithmetic command, evaluating an arithmetic expression. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub struct ArithmeticCommand { /// The raw, unparsed and unexpanded arithmetic expression. pub expr: UnexpandedArithmeticExpr, @@ -230,7 +230,7 @@ impl Display for ArithmeticCommand { /// A subshell, which executes commands in a subshell. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub struct SubshellCommand(pub CompoundList); impl Display for SubshellCommand { @@ -244,7 +244,7 @@ impl Display for SubshellCommand { /// A for clause, which loops over a set of values. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub struct ForClauseCommand { /// The name of the iterator variable. pub variable_name: String, @@ -277,7 +277,7 @@ impl Display for ForClauseCommand { /// An arithmetic for clause, which loops until an arithmetic condition is reached. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub struct ArithmeticForClauseCommand { /// Optionally, the initializer expression evaluated before the first iteration of the loop. pub initializer: Option, @@ -319,7 +319,7 @@ impl Display for ArithmeticForClauseCommand { /// pattern-based filters. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub struct CaseClauseCommand { /// The value being matched on. pub value: Word, @@ -341,7 +341,7 @@ impl Display for CaseClauseCommand { /// A sequence of commands. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub struct CompoundList(pub Vec); impl Display for CompoundList { @@ -369,7 +369,7 @@ impl Display for CompoundList { /// An element of a compound command list. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub struct CompoundListItem(pub AndOrList, pub SeparatorOperator); impl Display for CompoundListItem { @@ -383,7 +383,7 @@ impl Display for CompoundListItem { /// An if clause, which conditionally executes a command. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub struct IfClauseCommand { /// The command whose execution result is inspected. pub condition: CompoundList, @@ -417,7 +417,7 @@ impl Display for IfClauseCommand { /// Represents the `else` clause of a conditional command. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub struct ElseClause { /// If present, the condition that must be met for this `else` clause to be executed. pub condition: Option, @@ -445,7 +445,7 @@ impl Display for ElseClause { /// An individual matching case item in a case clause. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub struct CaseItem { /// The patterns that select this case branch. pub patterns: Vec, @@ -477,7 +477,7 @@ impl Display for CaseItem { /// Describes the action to take after executing the body command of a case clause. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub enum CaseItemPostAction { /// The containing case should be exited. ExitCase, @@ -502,7 +502,7 @@ impl Display for CaseItemPostAction { /// A while or until clause, whose looping is controlled by a condition. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub struct WhileOrUntilClauseCommand(pub CompoundList, pub DoGroupCommand); impl Display for WhileOrUntilClauseCommand { @@ -514,7 +514,7 @@ impl Display for WhileOrUntilClauseCommand { /// Encapsulates the definition of a shell function. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub struct FunctionDefinition { /// The name of the function. pub fname: String, @@ -535,7 +535,7 @@ impl Display for FunctionDefinition { /// Encapsulates the body of a function definition. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub struct FunctionBody(pub CompoundCommand, pub Option); impl Display for FunctionBody { @@ -552,7 +552,7 @@ impl Display for FunctionBody { /// A brace group, which groups commands together. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub struct BraceGroupCommand(pub CompoundList); impl Display for BraceGroupCommand { @@ -569,7 +569,7 @@ impl Display for BraceGroupCommand { /// A do group, which groups commands together. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub struct DoGroupCommand(pub CompoundList); impl Display for DoGroupCommand { @@ -584,7 +584,7 @@ impl Display for DoGroupCommand { /// Represents the invocation of a simple command. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub struct SimpleCommand { /// Optionally, a prefix to the command. pub prefix: Option, @@ -631,7 +631,7 @@ impl Display for SimpleCommand { /// Represents a prefix to a simple command. #[derive(Clone, Debug, Default)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub struct CommandPrefix(pub Vec); impl Display for CommandPrefix { @@ -650,7 +650,7 @@ impl Display for CommandPrefix { /// Represents a suffix to a simple command; a word argument, declaration, or I/O redirection. #[derive(Clone, Default, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub struct CommandSuffix(pub Vec); impl Display for CommandSuffix { @@ -669,7 +669,7 @@ impl Display for CommandSuffix { /// Represents the I/O direction of a process substitution. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub enum ProcessSubstitutionKind { /// The process is read from. Read, @@ -689,7 +689,7 @@ impl Display for ProcessSubstitutionKind { /// A prefix or suffix for a simple command. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub enum CommandPrefixOrSuffixItem { /// An I/O redirection. IoRedirect(IoRedirect), @@ -717,7 +717,7 @@ impl Display for CommandPrefixOrSuffixItem { /// Encapsulates an assignment declaration. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub struct Assignment { /// Name being assigned to. pub name: AssignmentName, @@ -740,7 +740,7 @@ impl Display for Assignment { /// The target of an assignment. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub enum AssignmentName { /// A named variable. VariableName(String), @@ -762,7 +762,7 @@ impl Display for AssignmentName { /// A value being assigned to a variable. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub enum AssignmentValue { /// A scalar (word) value. Scalar(Word), @@ -794,7 +794,7 @@ impl Display for AssignmentValue { /// A list of I/O redirections to be applied to a command. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub struct RedirectList(pub Vec); impl Display for RedirectList { @@ -809,7 +809,7 @@ impl Display for RedirectList { /// An I/O redirection. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub enum IoRedirect { /// Redirection to a file. File(Option, IoFileRedirectKind, IoFileRedirectTarget), @@ -877,7 +877,7 @@ impl Display for IoRedirect { /// Kind of file I/O redirection. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub enum IoFileRedirectKind { /// Read (`<`). Read, @@ -912,7 +912,7 @@ impl Display for IoFileRedirectKind { /// Target for an I/O file redirection. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub enum IoFileRedirectTarget { /// Path to a file. Filename(Word), @@ -938,7 +938,7 @@ impl Display for IoFileRedirectTarget { /// Represents an I/O here document. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub struct IoHereDocument { /// Whether to remove leading tabs from the here document. pub remove_tabs: bool, @@ -989,7 +989,7 @@ impl Display for TestExpr { /// An extended test expression. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub enum ExtendedTestExpr { /// Logical AND operation on two nested expressions. And(Box, Box), @@ -1033,7 +1033,7 @@ impl Display for ExtendedTestExpr { /// A unary predicate usable in an extended test expression. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub enum UnaryPredicate { /// Computes if the operand is a path to an existing file. FileExists, @@ -1122,7 +1122,7 @@ impl Display for UnaryPredicate { /// A binary predicate usable in an extended test expression. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub enum BinaryPredicate { /// Computes if two files refer to the same device and inode numbers. FilesReferToSameDeviceAndInodeNumbers, @@ -1181,7 +1181,7 @@ impl Display for BinaryPredicate { /// Represents a shell word. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub struct Word { /// Raw text of the word. pub value: String, @@ -1229,7 +1229,7 @@ impl Word { /// Encapsulates an unparsed arithmetic expression. #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))] pub struct UnexpandedArithmeticExpr { /// The raw text of the expression. pub value: String, diff --git a/brush-parser/src/parser.rs b/brush-parser/src/parser.rs index 74f79023..cf3c6a04 100644 --- a/brush-parser/src/parser.rs +++ b/brush-parser/src/parser.rs @@ -1081,4 +1081,42 @@ for f in A B C; do Ok(()) } + + macro_rules! case { + ($case:ident($parser:ident($input:literal))) => { + #[test] + fn $case() -> Result<()> { + let tokens = tokenize_str($input)?; + let ast = $parser( + &Tokens { + tokens: tokens.as_slice(), + }, + &ParserOptions::default(), + &SourceInfo::default(), + )?; + insta::with_settings!({ + description => $input, + omit_expression => true, + }, { + insta::assert_yaml_snapshot!(&ast); + }); + Ok(()) + } + }; + } + + use super::token_parser::program; + + case! {test_ambiguous_for_loop(program(r#"for for in for; do for=for; done; echo $for"#))} + case! {sample_program(program( r#" +#!/usr/bin/env bash + +for f in A B C; do + + # sdfsdf + echo "${f@L}" >&2 + + done + +"#))} } diff --git a/brush-parser/src/snapshots/brush_parser__parser__tests__sample_program.snap b/brush-parser/src/snapshots/brush_parser__parser__tests__sample_program.snap new file mode 100644 index 00000000..3dfc89db --- /dev/null +++ b/brush-parser/src/snapshots/brush_parser__parser__tests__sample_program.snap @@ -0,0 +1,36 @@ +--- +source: brush-parser/src/parser.rs +description: "\n#!/usr/bin/env bash\n\nfor f in A B C; do\n\n # sdfsdf\n echo \"${f@L}\" >&2\n\n done\n\n" +--- +complete_commands: + - - - first: + bang: false + seq: + - Compound: + - ForClause: + variable_name: f + values: + - value: A + - value: B + - value: C + body: + - - first: + bang: false + seq: + - Simple: + prefix: ~ + word_or_name: + value: echo + suffix: + - Word: + value: "\"${f@L}\"" + - IoRedirect: + File: + - ~ + - DuplicateOutput + - Fd: 2 + additional: [] + - Sequence + - ~ + additional: [] + - Sequence