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