Skip to main content

nu_parser/
parse_alias.rs

1use crate::{
2    is_math_expression_like,
3    lite_parser::LiteCommand,
4    parse_helpers::{garbage, garbage_pipeline},
5    parse_keywords::reject_parser_keyword_name,
6    parse_pipelines::redirecting_builtin_error,
7    parser::{
8        ArgumentParsingLevel, CallKind, ParsedInternalCall, parse_call, parse_expression,
9        parse_internal_call,
10    },
11};
12
13use nu_protocol::{
14    Alias, ParseError, Span,
15    ast::{Argument, Expr, Expression, Pipeline},
16    engine::StateWorkingSet,
17};
18
19use crate::ALIASABLE_PARSER_KEYWORDS;
20
21fn check_alias_name<'a>(working_set: &mut StateWorkingSet, spans: &'a [Span]) -> Option<&'a Span> {
22    let command_len = if !spans.is_empty() {
23        if working_set.get_span_contents(spans[0]) == b"export" {
24            2
25        } else {
26            1
27        }
28    } else {
29        return None;
30    };
31
32    if spans.len() == command_len {
33        None
34    } else if spans.len() < command_len + 3 {
35        if working_set.get_span_contents(spans[command_len]) == b"=" {
36            let name = String::from_utf8_lossy(
37                working_set.get_span_contents(Span::concat(&spans[..command_len])),
38            );
39            working_set.error(ParseError::AssignmentMismatch(
40                format!("{name} missing name"),
41                "missing name".into(),
42                spans[command_len],
43            ));
44            Some(&spans[command_len])
45        } else {
46            None
47        }
48    } else if working_set.get_span_contents(spans[command_len + 1]) != b"=" {
49        let name = String::from_utf8_lossy(
50            working_set.get_span_contents(Span::concat(&spans[..command_len])),
51        );
52        working_set.error(ParseError::AssignmentMismatch(
53            format!("{name} missing sign"),
54            "missing equal sign".into(),
55            spans[command_len + 1],
56        ));
57        Some(&spans[command_len + 1])
58    } else {
59        None
60    }
61}
62
63pub fn parse_alias(
64    working_set: &mut StateWorkingSet,
65    lite_command: &LiteCommand,
66    module_name: Option<&[u8]>,
67) -> Pipeline {
68    let spans = &lite_command.parts;
69
70    let (name_span, split_id) =
71        if spans.len() > 1 && working_set.get_span_contents(spans[0]) == b"export" {
72            (spans[1], 2)
73        } else {
74            (spans[0], 1)
75        };
76
77    let name = working_set.get_span_contents(name_span);
78
79    if name != b"alias" {
80        working_set.error(ParseError::InternalError(
81            "Alias statement unparsable".into(),
82            Span::concat(spans),
83        ));
84        return garbage_pipeline(working_set, spans);
85    }
86    if let Some(redirection) = lite_command.redirection.as_ref() {
87        working_set.error(redirecting_builtin_error("alias", redirection));
88        return garbage_pipeline(working_set, spans);
89    }
90
91    if let Some(span) = check_alias_name(working_set, spans) {
92        return Pipeline::from_vec(vec![garbage(working_set, *span)]);
93    }
94
95    if let Some(decl_id) = working_set.find_decl(b"alias") {
96        let (command_spans, rest_spans) = spans.split_at(split_id);
97
98        let original_starting_error_count = working_set.parse_errors.len();
99
100        let ParsedInternalCall {
101            call: alias_call,
102            output,
103            call_kind,
104        } = parse_internal_call(
105            working_set,
106            Span::concat(command_spans),
107            rest_spans,
108            decl_id,
109            ArgumentParsingLevel::Full,
110            None,
111        );
112
113        working_set
114            .parse_errors
115            .truncate(original_starting_error_count);
116
117        let alias_pipeline = Pipeline::from_vec(vec![Expression::new(
118            working_set,
119            Expr::Call(alias_call.clone()),
120            Span::concat(spans),
121            output,
122        )]);
123
124        if call_kind == CallKind::Help {
125            return alias_pipeline;
126        }
127
128        let Some(alias_name_expr) = alias_call.positional_iter().next() else {
129            working_set.error(ParseError::UnknownState(
130                "Missing positional after call check".to_string(),
131                Span::concat(spans),
132            ));
133            return garbage_pipeline(working_set, spans);
134        };
135
136        let alias_name = if let Some(name) = alias_name_expr.as_string() {
137            if name.contains('#')
138                || name.contains('^')
139                || name.contains('%')
140                || name.parse::<bytesize::ByteSize>().is_ok()
141                || name.parse::<f64>().is_ok()
142            {
143                working_set.error(ParseError::AliasNotValid(alias_name_expr.span));
144                return garbage_pipeline(working_set, spans);
145            } else if reject_parser_keyword_name(working_set, &name, "alias", alias_name_expr.span)
146            {
147                return garbage_pipeline(working_set, spans);
148            } else {
149                name
150            }
151        } else {
152            working_set.error(ParseError::AliasNotValid(alias_name_expr.span));
153            return garbage_pipeline(working_set, spans);
154        };
155
156        if spans.len() >= split_id + 3 {
157            if let Some(mod_name) = module_name {
158                if alias_name.as_bytes() == mod_name {
159                    working_set.error(ParseError::NamedAsModule(
160                        "alias".to_string(),
161                        alias_name,
162                        "main".to_string(),
163                        spans[split_id],
164                    ));
165
166                    return alias_pipeline;
167                }
168
169                if alias_name == "main" {
170                    working_set.error(ParseError::ExportMainAliasNotAllowed(spans[split_id]));
171                    return alias_pipeline;
172                }
173            }
174
175            let _equals = working_set.get_span_contents(spans[split_id + 1]);
176
177            let replacement_spans = &spans[(split_id + 2)..];
178            let first_bytes = working_set.get_span_contents(replacement_spans[0]);
179
180            if first_bytes != b"if"
181                && first_bytes != b"match"
182                && is_math_expression_like(working_set, replacement_spans[0])
183            {
184                let starting_error_count = working_set.parse_errors.len();
185                let expr = parse_expression(working_set, replacement_spans, None);
186                working_set.parse_errors.truncate(starting_error_count);
187
188                working_set.error(ParseError::CantAliasExpression(
189                    expr.expr.description().to_string(),
190                    replacement_spans[0],
191                ));
192                return alias_pipeline;
193            }
194
195            let starting_error_count = working_set.parse_errors.len();
196            working_set.search_predecls = false;
197
198            let expr = parse_call(working_set, replacement_spans, replacement_spans[0], None);
199
200            working_set.search_predecls = true;
201
202            if starting_error_count != working_set.parse_errors.len()
203                && let Some(e) = working_set.parse_errors.get(starting_error_count)
204            {
205                if let ParseError::MissingPositional(..)
206                | ParseError::MissingRequiredFlag(..)
207                | ParseError::MissingFlagParam(..) = e
208                {
209                    working_set
210                        .parse_errors
211                        .truncate(original_starting_error_count);
212                } else {
213                    return garbage_pipeline(working_set, replacement_spans);
214                }
215            }
216
217            let (command, wrapped_call) = match expr {
218                Expression {
219                    expr: Expr::Call(ref rhs_call),
220                    ..
221                } => {
222                    let cmd = working_set.get_decl(rhs_call.decl_id);
223
224                    if cmd.is_keyword()
225                        && !ALIASABLE_PARSER_KEYWORDS.contains(&cmd.name().as_bytes())
226                    {
227                        working_set.error(ParseError::CantAliasKeyword(
228                            ALIASABLE_PARSER_KEYWORDS
229                                .iter()
230                                .map(|bytes| String::from_utf8_lossy(bytes).to_string())
231                                .collect::<Vec<String>>()
232                                .join(", "),
233                            rhs_call.head,
234                        ));
235                        return alias_pipeline;
236                    }
237
238                    (Some(cmd.clone_box()), expr)
239                }
240                Expression {
241                    expr: Expr::ExternalCall(..),
242                    ..
243                } => (None, expr),
244                _ => {
245                    working_set.error(ParseError::InternalError(
246                        "Parsed call not a call".into(),
247                        expr.span,
248                    ));
249                    return alias_pipeline;
250                }
251            };
252
253            let (description, extra_description) = match lite_command.comments.is_empty() {
254                false => working_set.build_desc(&lite_command.comments),
255                true => match alias_call.arguments.get(1) {
256                    Some(Argument::Positional(Expression {
257                        expr: Expr::Keyword(kw),
258                        ..
259                    })) => {
260                        let aliased = working_set.get_span_contents(kw.expr.span);
261                        (
262                            format!("Alias for `{}`", String::from_utf8_lossy(aliased)),
263                            String::new(),
264                        )
265                    }
266                    _ => ("User declared alias".into(), String::new()),
267                },
268            };
269
270            let decl = Alias {
271                name: alias_name,
272                command,
273                wrapped_call,
274                description,
275                extra_description,
276            };
277
278            working_set.add_decl(Box::new(decl));
279        }
280
281        if spans.len() == 2 && working_set.get_span_contents(spans[1]).contains(&b'=') {
282            let arg = String::from_utf8_lossy(working_set.get_span_contents(spans[1]));
283
284            let (name, initial_value) = arg.split_once('=').unwrap_or((&arg, ""));
285
286            let name = if name.is_empty() { "{name}" } else { name };
287            let initial_value = if initial_value.is_empty() {
288                "{initial_value}"
289            } else {
290                initial_value
291            };
292
293            working_set.error(ParseError::IncorrectValue(
294                "alias argument".into(),
295                spans[1],
296                format!("Make sure to put spaces around '=': alias {name} = {initial_value}"),
297            ))
298        } else if spans.len() < 4 {
299            working_set.error(ParseError::IncorrectValue(
300                "Incomplete alias".into(),
301                Span::concat(&spans[..split_id]),
302                "incomplete alias".into(),
303            ));
304        }
305
306        return alias_pipeline;
307    }
308
309    working_set.error(ParseError::InternalError(
310        "Alias statement unparsable".into(),
311        Span::concat(spans),
312    ));
313
314    garbage_pipeline(working_set, spans)
315}