1use fervid_core::{
2 AttributeOrBinding, FervidAtom, StrOrExpr, VBindDirective, VCustomDirective, VForDirective,
3 VModelDirective, VOnDirective, VSlotDirective, VueDirectives,
4};
5use swc_core::{common::{BytePos, Span}, ecma::ast::Expr};
6use swc_ecma_parser::Syntax;
7use swc_html_ast::Attribute;
8
9use crate::{
10 error::{ParseError, ParseErrorKind},
11 SfcParser,
12};
13
14impl SfcParser<'_, '_, '_> {
15 pub fn process_element_attributes(
17 &mut self,
18 raw_attributes: Vec<Attribute>,
19 attrs_or_bindings: &mut Vec<AttributeOrBinding>,
20 vue_directives: &mut Option<Box<VueDirectives>>,
21 ) -> bool {
22 if self.is_pre {
24 attrs_or_bindings.extend(raw_attributes.into_iter().map(create_regular_attribute));
25 return false;
26 }
27
28 let has_v_pre = raw_attributes.iter().any(|attr| attr.name == "v-pre");
30 if has_v_pre {
31 attrs_or_bindings.extend(
32 raw_attributes
33 .into_iter()
34 .filter(|attr| attr.name != "v-pre")
35 .map(create_regular_attribute),
36 );
37 return true;
38 }
39
40 for mut raw_attribute in raw_attributes.into_iter() {
41 let raw_idx_start = raw_attribute.span.lo.0 as usize - 1;
44 let raw_idx_end = raw_idx_start + raw_attribute.name.len();
45 raw_attribute.name = FervidAtom::from(&self.input[raw_idx_start..raw_idx_end]);
46
47 match self.try_parse_directive(raw_attribute, attrs_or_bindings, vue_directives) {
48 Ok(()) => {
49 }
51
52 Err(raw_attribute) => {
54 attrs_or_bindings.push(create_regular_attribute(raw_attribute))
55 }
56 }
57 }
58
59 false
61 }
62
63 pub fn try_parse_directive(
65 &mut self,
66 raw_attribute: Attribute,
67 attrs_or_bindings: &mut Vec<AttributeOrBinding>,
68 vue_directives: &mut Option<Box<VueDirectives>>,
69 ) -> Result<(), Attribute> {
70 macro_rules! bail {
71 () => {
73 return Err(raw_attribute);
74 };
75 ($err_kind: expr) => {
77 self.errors.push(ParseError {
78 kind: $err_kind,
79 span: raw_attribute.span,
80 });
81 return Err(raw_attribute);
82 };
83 (js, $parse_error: expr) => {
84 self.errors.push($parse_error);
85 return Err(raw_attribute);
86 };
87 }
88
89 macro_rules! ts {
90 () => {
91 Syntax::Typescript(Default::default())
92 };
93 }
94
95 let span = raw_attribute.span;
99 let raw_name: &str = &raw_attribute.name;
100 let mut chars_iter = raw_name.chars().enumerate().peekable();
101
102 let Some((_, prefix)) = chars_iter.next() else {
104 bail!(ParseErrorKind::DirectiveSyntax);
105 };
106
107 let mut is_bind_prop = false;
109 let mut expect_argument = true;
110 let mut argument_start = 0;
111 let mut argument_end = raw_name.len();
112
113 let directive_name = match prefix {
114 '@' => "on",
115 ':' => "bind",
116 '.' => {
117 is_bind_prop = true;
118 "bind"
119 }
120 '#' => "slot",
121 'v' if matches!(chars_iter.next(), Some((_, '-'))) => {
122 let mut start = 0;
124 let mut end = raw_name.len();
125 while let Some((idx, c)) = chars_iter.next() {
126 if c == '.' {
127 expect_argument = false;
128 argument_end = idx;
129 end = idx;
130 break;
131 }
132 if c == ':' {
133 end = idx;
134 break;
135 }
136 if start == 0 {
137 start = idx;
139 }
140 }
141
142 if start == 0 {
144 bail!(ParseErrorKind::DirectiveSyntax);
145 }
146
147 &raw_name[start..end]
148 }
149 _ => {
150 bail!();
151 }
152 };
153
154 let mut argument: Option<StrOrExpr> = None;
156 if expect_argument {
157 while let Some((idx, c)) = chars_iter.next() {
158 if c == '.' {
159 argument_end = idx;
160 break;
161 }
162 if argument_start == 0 {
163 argument_start = idx;
164 }
165 }
166
167 if argument_start != 0 {
168 let mut raw_argument = &raw_name[argument_start..argument_end];
169 let mut is_dynamic_argument = false;
170
171 if raw_argument.starts_with('[') {
173 if !raw_argument.ends_with(']') {
175 bail!(ParseErrorKind::DynamicArgument);
176 }
177
178 raw_argument =
179 &raw_argument['['.len_utf8()..(raw_argument.len() - ']'.len_utf8())];
180 if raw_argument.is_empty() {
181 bail!(ParseErrorKind::DynamicArgument);
182 }
183
184 is_dynamic_argument = true;
185 }
186
187 if is_dynamic_argument {
188 let parsed_argument = match self.parse_expr(raw_argument, ts!(), span) {
190 Ok(parsed) => parsed,
191 Err(expr_err) => {
192 bail!(js, expr_err);
193 }
194 };
195
196 argument = Some(StrOrExpr::Expr(parsed_argument));
197 } else {
198 argument = Some(StrOrExpr::Str(FervidAtom::from(raw_argument)));
199 }
200 }
201 }
202
203 let mut modifiers = Vec::<FervidAtom>::new();
205 if argument_end != 0 {
206 for modifier in raw_name[argument_end..]
207 .split('.')
208 .filter(|m| !m.is_empty())
209 {
210 modifiers.push(FervidAtom::from(modifier));
211 }
212 }
213
214 macro_rules! expect_value {
216 () => {
217 if let Some(ref value) = raw_attribute.value {
218 value
219 } else {
220 bail!(ParseErrorKind::DirectiveSyntax);
221 }
222 };
223 }
224
225 macro_rules! get_directives {
226 () => {
227 vue_directives.get_or_insert_with(|| Box::new(VueDirectives::default()))
228 };
229 }
230
231 macro_rules! push_directive {
232 ($key: ident, $value: expr) => {
233 let directives = get_directives!();
234 directives.$key = Some($value);
235 };
236 }
237
238 macro_rules! push_directive_js {
239 ($key: ident, $value: expr) => {
240 match self.parse_expr($value, ts!(), span) {
241 Ok(parsed) => {
242 let directives = get_directives!();
243 directives.$key = Some(parsed);
244 }
245 Result::Err(expr_err) => self.report_error(expr_err),
246 }
247 };
248 }
249
250 match directive_name {
252 "bind" => {
254 let mut is_camel = false;
256 let mut is_prop = is_bind_prop;
257 let mut is_attr = false;
258 for modifier in modifiers.iter() {
259 match modifier.as_ref() {
260 "camel" => is_camel = true,
261 "prop" => is_prop = true,
262 "attr" => is_attr = true,
263 _ => {}
264 }
265 }
266
267 let value = expect_value!();
268
269 let parsed_expr = match self.parse_expr(&value, ts!(), span) {
270 Ok(parsed) => parsed,
271 Err(expr_err) => {
272 bail!(js, expr_err);
273 }
274 };
275
276 attrs_or_bindings.push(AttributeOrBinding::VBind(VBindDirective {
277 argument,
278 value: parsed_expr,
279 is_camel,
280 is_prop,
281 is_attr,
282 span,
283 }));
284 }
285
286 "on" => {
287 let handler = match raw_attribute.value {
288 Some(ref value) => match self.parse_expr(&value, ts!(), span) {
289 Ok(parsed) => Some(parsed),
290 Err(expr_err) => {
291 bail!(js, expr_err);
292 }
293 },
294 None => None,
295 };
296
297 attrs_or_bindings.push(AttributeOrBinding::VOn(VOnDirective {
298 event: argument,
299 handler,
300 modifiers,
301 span,
302 }));
303 }
304
305 "if" => {
306 let value = expect_value!();
307 push_directive_js!(v_if, &value);
308 }
309
310 "else-if" => {
311 let value = expect_value!();
312 push_directive_js!(v_else_if, &value);
313 }
314
315 "else" => {
316 push_directive!(v_else, ());
317 }
318
319 "for" => {
320 let value = expect_value!();
321
322 let Some(((itervar, itervar_span), (iterable, iterable_span))) =
323 split_itervar_and_iterable(&value, span)
324 else {
325 bail!(ParseErrorKind::DirectiveSyntax);
326 };
327
328 match self.parse_expr(itervar, ts!(), itervar_span) {
329 Ok(itervar) => match self.parse_expr(iterable, ts!(), iterable_span) {
330 Ok(iterable) => {
331 push_directive!(
332 v_for,
333 VForDirective {
334 iterable,
335 itervar,
336 patch_flags: Default::default(),
337 span
338 }
339 );
340 }
341 Result::Err(expr_err) => self.report_error(expr_err),
342 },
343 Result::Err(expr_err) => self.report_error(expr_err),
344 };
345 }
346
347 "model" => {
348 let value = expect_value!();
349
350 match self.parse_expr(&value, ts!(), span) {
351 Ok(model_binding) => {
352 if !matches!(*model_binding, Expr::Member(_) | Expr::Ident(_)) {
354 bail!();
356 }
357
358 let directives = get_directives!();
359 directives.v_model.push(VModelDirective {
360 argument,
361 value: model_binding,
362 update_handler: None,
363 modifiers,
364 span,
365 });
366 }
367 Result::Err(_) => {}
368 }
369 }
370
371 "slot" => {
372 let value =
373 raw_attribute
374 .value
375 .and_then(|v| match self.parse_pat(&v, ts!(), span) {
376 Ok(value) => Some(Box::new(value)),
377 Result::Err(_) => None,
378 });
379 push_directive!(
380 v_slot,
381 VSlotDirective {
382 slot_name: argument,
383 value,
384 }
385 );
386 }
387
388 "show" => {
389 let value = expect_value!();
390 push_directive_js!(v_show, &value);
391 }
392
393 "html" => {
394 let value = expect_value!();
395 push_directive_js!(v_html, &value);
396 }
397
398 "text" => {
399 let value = expect_value!();
400 push_directive_js!(v_text, &value);
401 }
402
403 "once" => {
404 push_directive!(v_once, ());
405 }
406
407 "pre" => {
408 push_directive!(v_pre, ());
409 }
410
411 "memo" => {
412 let value = expect_value!();
413 push_directive_js!(v_memo, &value);
414 }
415
416 "cloak" => {
417 push_directive!(v_cloak, ());
418 }
419
420 _ => 'custom: {
422 let Some(value) = raw_attribute.value else {
424 let directives = get_directives!();
425 directives.custom.push(VCustomDirective {
426 name: directive_name.into(),
427 argument,
428 modifiers,
429 value: None,
430 });
431 break 'custom;
432 };
433
434 match self.parse_expr(&value, ts!(), span) {
436 Ok(parsed) => {
437 let directives = get_directives!();
438 directives.custom.push(VCustomDirective {
439 name: directive_name.into(),
440 argument,
441 modifiers,
442 value: Some(parsed),
443 });
444 }
445 Result::Err(expr_err) => self.report_error(expr_err),
446 }
447 }
448 }
449
450 Ok(())
451 }
452}
453
454#[inline]
456pub fn create_regular_attribute(raw_attribute: Attribute) -> AttributeOrBinding {
457 AttributeOrBinding::RegularAttribute {
458 name: raw_attribute.name,
459 value: raw_attribute.value.unwrap_or_default(),
460 span: raw_attribute.span,
461 }
462}
463
464fn split_itervar_and_iterable<'a>(
465 raw: &'a str,
466 original_span: Span,
467) -> Option<((&'a str, Span), (&'a str, Span))> {
468 let Some(split_idx) = raw.find(" in ").or_else(|| raw.find(" of ")) else {
470 return None;
471 };
472 const SPLIT_LEN: usize = " in ".len();
473
474 let mut offset = original_span.lo.0;
476 let mut itervar = &raw[..split_idx];
477 let mut itervar_old_len = itervar.len();
478 itervar = itervar.trim_start();
479 let itervar_lo = BytePos(offset + (itervar_old_len - itervar.len()) as u32);
480 itervar_old_len = itervar.len();
481 itervar = itervar.trim_end();
482 let itervar_hi = BytePos(offset + (split_idx - (itervar_old_len - itervar.len())) as u32);
483
484 let iterable_start = split_idx + SPLIT_LEN;
485 offset += iterable_start as u32;
486
487 let mut iterable = &raw[iterable_start..];
488 let iterable_old_len = iterable.len();
489 iterable = iterable.trim_start();
490 let iterable_lo = BytePos(offset + (iterable_old_len - iterable.len()) as u32);
491 iterable = iterable.trim_end();
492 let iterable_hi = BytePos(iterable_lo.0 + iterable.len() as u32);
493
494 if itervar.is_empty() || iterable.is_empty() {
495 return None;
496 }
497
498 let new_span_itervar = Span {
499 lo: itervar_lo,
500 hi: itervar_hi,
501 ctxt: original_span.ctxt,
502 };
503
504 let new_span_iterable = Span {
505 lo: iterable_lo,
506 hi: iterable_hi,
507 ctxt: original_span.ctxt,
508 };
509
510 Some(((itervar, new_span_itervar), (iterable, new_span_iterable)))
511}
512
513#[cfg(test)]
514mod tests {
515 use super::*;
516
517 #[test]
518 fn it_correctly_splits_itervar_iterable() {
519 macro_rules! check {
520 ($input: expr, $itervar: expr, $itervar_lo: expr, $itervar_hi: expr, $iterable: expr, $iterable_lo: expr, $iterable_hi: expr) => {
521 let input = $input;
522 let span = Span {
523 lo: BytePos(1),
524 hi: BytePos((input.len() + 1) as u32),
525 ctxt: Default::default(),
526 };
527
528 let Some(((itervar, itervar_span), (iterable, iterable_span))) =
529 split_itervar_and_iterable(input, span)
530 else {
531 panic!("Did not manage to split")
532 };
533 assert_eq!($itervar, itervar);
534 assert_eq!($itervar_lo, itervar_span.lo.0);
535 assert_eq!($itervar_hi, itervar_span.hi.0);
536 assert_eq!($iterable, iterable);
537 assert_eq!($iterable_lo, iterable_span.lo.0);
538 assert_eq!($iterable_hi, iterable_span.hi.0);
539 };
540 }
541
542 check!("item in list", "item", 1, 5, "list", 9, 13);
544 check!("item of list", "item", 1, 5, "list", 9, 13);
545 check!("i in 3", "i", 1, 2, "3", 6, 7);
546
547 check!(" item in \n \t list ", "item", 4, 8, "list", 19, 23);
549 }
550}