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}