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}