diff options
Diffstat (limited to '')
-rw-r--r-- | tests/test_utils/swc.rs | 383 |
1 files changed, 132 insertions, 251 deletions
diff --git a/tests/test_utils/swc.rs b/tests/test_utils/swc.rs index f30561d..e85a239 100644 --- a/tests/test_utils/swc.rs +++ b/tests/test_utils/swc.rs @@ -1,116 +1,85 @@ //! Bridge between `markdown-rs` and SWC. -use crate::test_utils::swc_utils::{bytepos_to_point, prefix_error_with_point, RewriteContext}; -use markdown::{mdast::Stop, unist::Point, Location, MdxExpressionKind, MdxSignal}; -use swc_common::{ - source_map::Pos, sync::Lrc, BytePos, FileName, FilePathMapping, SourceFile, SourceMap, Spanned, +extern crate markdown; + +use crate::test_utils::swc_utils::{create_span, RewritePrefixContext}; +use markdown::{MdxExpressionKind, MdxSignal}; +use std::rc::Rc; +use swc_core::common::{ + comments::{Comment, SingleThreadedComments, SingleThreadedCommentsMap}, + source_map::Pos, + BytePos, FileName, SourceFile, Span, Spanned, }; -use swc_ecma_ast::{EsVersion, Expr, Module}; -use swc_ecma_codegen::{text_writer::JsWriter, Emitter}; -use swc_ecma_parser::{ +use swc_core::ecma::ast::{EsVersion, Expr, Module, PropOrSpread}; +use swc_core::ecma::parser::{ error::Error as SwcError, parse_file_as_expr, parse_file_as_module, EsConfig, Syntax, }; -use swc_ecma_visit::VisitMutWith; +use swc_core::ecma::visit::VisitMutWith; /// Lex ESM in MDX with SWC. -#[allow(dead_code)] pub fn parse_esm(value: &str) -> MdxSignal { - let (file, syntax, version) = create_config(value.into()); - let mut errors = vec![]; - let result = parse_file_as_module(&file, syntax, version, None, &mut errors); + let result = parse_esm_core(value); match result { - Err(error) => swc_error_to_signal(&error, "esm", value.len(), 0), - Ok(tree) => { - if errors.is_empty() { - check_esm_ast(&tree) - } else { - swc_error_to_signal(&errors[0], "esm", value.len(), 0) - } - } + Err((span, message)) => swc_error_to_signal(span, &message, value.len()), + Ok(_) => MdxSignal::Ok, } } -/// Parse ESM in MDX with SWC. -/// See `drop_span` in `swc_ecma_utils` for inspiration? -#[allow(dead_code)] -pub fn parse_esm_to_tree( - value: &str, - stops: &[Stop], - location: Option<&Location>, -) -> Result<swc_ecma_ast::Module, String> { +/// Core to parse ESM. +fn parse_esm_core(value: &str) -> Result<Module, (Span, String)> { let (file, syntax, version) = create_config(value.into()); let mut errors = vec![]; let result = parse_file_as_module(&file, syntax, version, None, &mut errors); - let mut rewrite_context = RewriteContext { - stops, - location, - prefix_len: 0, - }; match result { - Err(error) => Err(swc_error_to_error(&error, "esm", &rewrite_context)), - Ok(mut module) => { + Err(error) => Err(( + fix_span(error.span(), 1), + format!( + "Could not parse esm with swc: {}", + swc_error_to_string(&error) + ), + )), + Ok(module) => { if errors.is_empty() { - module.visit_mut_with(&mut rewrite_context); - Ok(module) - } else { - Err(swc_error_to_error(&errors[0], "esm", &rewrite_context)) - } - } - } -} - -/// Lex expressions in MDX with SWC. -#[allow(dead_code)] -pub fn parse_expression(value: &str, kind: &MdxExpressionKind) -> MdxSignal { - // Empty expressions are OK. - if matches!(kind, MdxExpressionKind::Expression) - && matches!(whitespace_and_comments(0, value), MdxSignal::Ok) - { - return MdxSignal::Ok; - } - - // For attribute expression, a spread is needed, for which we have to prefix - // and suffix the input. - // See `check_expression_ast` for how the AST is verified. - let (prefix, suffix) = if matches!(kind, MdxExpressionKind::AttributeExpression) { - ("({", "})") - } else { - ("", "") - }; - - let (file, syntax, version) = create_config(format!("{}{}{}", prefix, value, suffix)); - let mut errors = vec![]; - let result = parse_file_as_expr(&file, syntax, version, None, &mut errors); + let mut index = 0; + while index < module.body.len() { + let node = &module.body[index]; + + if !node.is_module_decl() { + return Err(( + fix_span(node.span(), 1), + "Unexpected statement in code: only import/exports are supported" + .into(), + )); + } - match result { - Err(error) => swc_error_to_signal(&error, "expression", value.len(), prefix.len()), - Ok(tree) => { - if errors.is_empty() { - let expression_end = fix_swc_position(tree.span().hi.to_usize(), prefix.len()); - let result = check_expression_ast(&tree, kind); - if matches!(result, MdxSignal::Ok) { - whitespace_and_comments(expression_end, value) - } else { - result + index += 1; } + + Ok(module) } else { - swc_error_to_signal(&errors[0], "expression", value.len(), prefix.len()) + Err(( + fix_span(errors[0].span(), 1), + format!( + "Could not parse esm with swc: {}", + swc_error_to_string(&errors[0]) + ), + )) } } } } -/// Parse ESM in MDX with SWC. -/// See `drop_span` in `swc_ecma_utils` for inspiration? -#[allow(dead_code)] -pub fn parse_expression_to_tree( +fn parse_expression_core( value: &str, kind: &MdxExpressionKind, - stops: &[Stop], - location: Option<&Location>, -) -> Result<Box<swc_ecma_ast::Expr>, String> { +) -> Result<Option<Box<Expr>>, (Span, String)> { + // Empty expressions are OK. + if matches!(kind, MdxExpressionKind::Expression) && whitespace_and_comments(0, value).is_ok() { + return Ok(None); + } + // For attribute expression, a spread is needed, for which we have to prefix // and suffix the input. // See `check_expression_ast` for how the AST is verified. @@ -123,185 +92,103 @@ pub fn parse_expression_to_tree( let (file, syntax, version) = create_config(format!("{}{}{}", prefix, value, suffix)); let mut errors = vec![]; let result = parse_file_as_expr(&file, syntax, version, None, &mut errors); - let mut rewrite_context = RewriteContext { - stops, - location, - prefix_len: prefix.len(), - }; match result { - Err(error) => Err(swc_error_to_error(&error, "expression", &rewrite_context)), + Err(error) => Err(( + fix_span(error.span(), prefix.len() + 1), + format!( + "Could not parse expression with swc: {}", + swc_error_to_string(&error) + ), + )), Ok(mut expr) => { if errors.is_empty() { - // Fix positions. - expr.visit_mut_with(&mut rewrite_context); + let expression_end = expr.span().hi.to_usize() - 1; + if let Err((span, reason)) = whitespace_and_comments(expression_end, value) { + return Err((span, reason)); + } - let expr_bytepos = expr.span().lo; + expr.visit_mut_with(&mut RewritePrefixContext { + prefix_len: prefix.len() as u32, + }); if matches!(kind, MdxExpressionKind::AttributeExpression) { - let mut obj = None; + let expr_span = expr.span(); - if let swc_ecma_ast::Expr::Paren(d) = *expr { - if let swc_ecma_ast::Expr::Object(d) = *d.expr { - obj = Some(d) + if let Expr::Paren(d) = *expr { + if let Expr::Object(mut obj) = *d.expr { + if obj.props.len() > 1 { + return Err((obj.span, "Unexpected extra content in spread (such as `{...x,y}`): only a single spread is supported (such as `{...x}`)".into())); + } + + if let Some(PropOrSpread::Spread(d)) = obj.props.pop() { + return Ok(Some(d.expr)); + } } }; - if let Some(mut obj) = obj { - if obj.props.len() > 1 { - Err(create_error_message( - "Unexpected extra content in spread: only a single spread is supported", - "expression", - bytepos_to_point(&obj.span.lo, location).as_ref() - )) - } else if let Some(swc_ecma_ast::PropOrSpread::Spread(d)) = obj.props.pop() - { - Ok(d.expr) - } else { - Err(create_error_message( - "Unexpected prop in spread: only a spread is supported", - "expression", - bytepos_to_point(&obj.span.lo, location).as_ref(), - )) - } - } else { - Err(create_error_message( - "Expected an object spread (`{...spread}`)", - "expression", - bytepos_to_point(&expr_bytepos, location).as_ref(), - )) - } - } else { - Ok(expr) + return Err(( + expr_span, + "Unexpected prop in spread (such as `{x}`): only a spread is supported (such as `{...x}`)".into(), + )); } + + Ok(Some(expr)) } else { - Err(swc_error_to_error( - &errors[0], - "expression", - &rewrite_context, + Err(( + fix_span(errors[0].span(), prefix.len() + 1), + format!( + "Could not parse expression with swc: {}", + swc_error_to_string(&errors[0]) + ), )) } } } } -/// Serialize an SWC module. -/// To do: support comments. -#[allow(dead_code)] -pub fn serialize(module: &Module) -> String { - let mut buf = vec![]; - let cm = Lrc::new(SourceMap::new(FilePathMapping::empty())); - // let comm = &program.comments as &dyn swc_common::comments::Comments; - { - let mut emitter = Emitter { - cfg: swc_ecma_codegen::Config { - ..Default::default() - }, - cm: cm.clone(), - // To do: figure out how to pass them. - comments: None, - wr: JsWriter::new(cm, "\n", &mut buf, None), - }; - - emitter.emit_module(module).unwrap(); - } - - String::from_utf8_lossy(&buf).into() -} - -/// Check that the resulting AST of ESM is OK. -/// -/// This checks that only module declarations (import/exports) are used, not -/// statements. -fn check_esm_ast(tree: &Module) -> MdxSignal { - let mut index = 0; - while index < tree.body.len() { - let node = &tree.body[index]; - - if !node.is_module_decl() { - let relative = fix_swc_position(node.span().lo.to_usize(), 0); - return MdxSignal::Error( - "Unexpected statement in code: only import/exports are supported".into(), - relative, - ); - } +/// Lex expressions in MDX with SWC. +pub fn parse_expression(value: &str, kind: &MdxExpressionKind) -> MdxSignal { + let result = parse_expression_core(value, kind); - index += 1; + match result { + Err((span, message)) => swc_error_to_signal(span, &message, value.len()), + Ok(_) => MdxSignal::Ok, } - - MdxSignal::Ok } -/// Check that the resulting AST of an expressions is OK. -/// -/// This checks that attribute expressions are the expected spread. -fn check_expression_ast(tree: &Expr, kind: &MdxExpressionKind) -> MdxSignal { - if matches!(kind, MdxExpressionKind::AttributeExpression) - && tree - .unwrap_parens() - .as_object() - .and_then(|object| { - if object.props.len() == 1 { - object.props[0].as_spread() - } else { - None - } - }) - .is_none() - { - MdxSignal::Error("Expected a single spread value, such as `...x`".into(), 0) - } else { - MdxSignal::Ok - } +// To do: remove this attribute, use it somewhere. +#[allow(dead_code)] +/// Turn SWC comments into a flat vec. +pub fn flat_comments(single_threaded_comments: SingleThreadedComments) -> Vec<Comment> { + let raw_comments = single_threaded_comments.take_all(); + let take = |list: SingleThreadedCommentsMap| { + Rc::try_unwrap(list) + .unwrap() + .into_inner() + .into_values() + .flatten() + .collect::<Vec<_>>() + }; + let mut list = take(raw_comments.0); + list.append(&mut take(raw_comments.1)); + list } /// Turn an SWC error into an `MdxSignal`. /// /// * If the error happens at `value_len`, yields `MdxSignal::Eof` /// * Else, yields `MdxSignal::Error`. -fn swc_error_to_signal( - error: &SwcError, - name: &str, - value_len: usize, - prefix_len: usize, -) -> MdxSignal { - let reason = create_error_reason(&swc_error_to_string(error), name); - let error_end = fix_swc_position(error.span().hi.to_usize(), prefix_len); +fn swc_error_to_signal(span: Span, reason: &str, value_len: usize) -> MdxSignal { + let error_end = span.hi.to_usize(); if error_end >= value_len { - MdxSignal::Eof(reason) + MdxSignal::Eof(reason.into()) } else { - MdxSignal::Error( - reason, - fix_swc_position(error.span().lo.to_usize(), prefix_len), - ) + MdxSignal::Error(reason.into(), span.lo.to_usize()) } } -fn swc_error_to_error(error: &SwcError, name: &str, context: &RewriteContext) -> String { - create_error_message( - &swc_error_to_string(error), - name, - context - .location - .and_then(|location| { - location.relative_to_point( - context.stops, - fix_swc_position(error.span().lo.to_usize(), context.prefix_len), - ) - }) - .as_ref(), - ) -} - -fn create_error_message(reason: &str, name: &str, point: Option<&Point>) -> String { - prefix_error_with_point(create_error_reason(name, reason), point) -} - -fn create_error_reason(reason: &str, name: &str) -> String { - format!("Could not parse {} with swc: {}", name, reason) -} - /// Turn an SWC error into a string. fn swc_error_to_string(error: &SwcError) -> String { error.kind().msg().into() @@ -313,7 +200,7 @@ fn swc_error_to_string(error: &SwcError) -> String { /// This is needed because for expressions, we use an API that parses up to /// a valid expression, but there may be more expressions after it, which we /// don’t alow. -fn whitespace_and_comments(mut index: usize, value: &str) -> MdxSignal { +fn whitespace_and_comments(mut index: usize, value: &str) -> Result<(), (Span, String)> { let bytes = value.as_bytes(); let len = bytes.len(); let mut in_multiline = false; @@ -329,10 +216,7 @@ fn whitespace_and_comments(mut index: usize, value: &str) -> MdxSignal { } // In a line comment: `// a`. else if in_line { - if index + 1 < len && bytes[index] == b'\r' && bytes[index + 1] == b'\n' { - index += 1; - in_line = false; - } else if bytes[index] == b'\r' || bytes[index] == b'\n' { + if bytes[index] == b'\r' || bytes[index] == b'\n' { in_line = false; } } @@ -352,30 +236,27 @@ fn whitespace_and_comments(mut index: usize, value: &str) -> MdxSignal { } // Outside comment, not whitespace. else { - return MdxSignal::Error( + return Err(( + create_span(index as u32, value.len() as u32), "Could not parse expression with swc: Unexpected content after expression".into(), - index, - ); + )); } index += 1; } if in_multiline { - MdxSignal::Error( - "Could not parse expression with swc: Unexpected unclosed multiline comment, expected closing: `*/`".into(), - index, - ) - } else if in_line { + return Err(( + create_span(index as u32, value.len() as u32), "Could not parse expression with swc: Unexpected unclosed multiline comment, expected closing: `*/`".into())); + } + + if in_line { // EOF instead of EOL is specifically not allowed, because that would // mean the closing brace is on the commented-out line - MdxSignal::Error( - "Could not parse expression with swc: Unexpected unclosed line comment, expected line ending: `\\n`".into(), - index, - ) - } else { - MdxSignal::Ok + return Err((create_span(index as u32, value.len() as u32), "Could not parse expression with swc: Unexpected unclosed line comment, expected line ending: `\\n`".into())); } + + Ok(()) } /// Create configuration for SWC, shared between ESM and expressions. @@ -401,8 +282,8 @@ fn create_config(source: String) -> (SourceFile, Syntax, EsVersion) { ) } -/// Turn an SWC byte position from a resulting AST to an offset in the original -/// input string. -fn fix_swc_position(index: usize, prefix_len: usize) -> usize { - index - 1 - prefix_len +fn fix_span(mut span: Span, offset: usize) -> Span { + span.lo = BytePos::from_usize(span.lo.to_usize() - offset); + span.hi = BytePos::from_usize(span.hi.to_usize() - offset); + span } |