aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Titus Wormer <tituswormer@gmail.com>2022-10-11 09:54:56 +0200
committerLibravatar Titus Wormer <tituswormer@gmail.com>2022-10-11 09:55:16 +0200
commita4b56e7b971fa81c56a59b465f90c8016f01320d (patch)
tree7002a44087e57c8158a51dd30b6eb89eb260af2b
parent1fd94f512834aa7bd70f22a60229ce01edfc754e (diff)
downloadmarkdown-rs-a4b56e7b971fa81c56a59b465f90c8016f01320d.tar.gz
markdown-rs-a4b56e7b971fa81c56a59b465f90c8016f01320d.tar.bz2
markdown-rs-a4b56e7b971fa81c56a59b465f90c8016f01320d.zip
Add support for proper positional info in swc tree
* Fix some positional info in SWC error messages * Add positional info in `to_document` on duplicate layouts * Add support for `path` on `Program` (`to_swc`, `to_document`, `jsx_rewrite`), for the path of a file on disk * Add support for `development` to `jsx-rewrite`, which when defined will embed info on where tags were written into the runtime code when they are not passed * Refactor to move some utilities to `micromark_swc_utils.rs`, `swc_utils.rs`
-rw-r--r--src/construct/mdx_esm.rs19
-rw-r--r--src/construct/partial_mdx_expression.rs24
-rw-r--r--src/lib.rs11
-rw-r--r--src/mdast.rs17
-rw-r--r--src/parser.rs16
-rw-r--r--src/to_mdast.rs153
-rw-r--r--src/util/location.rs111
-rw-r--r--src/util/mdx_collect.rs81
-rw-r--r--src/util/mod.rs1
-rw-r--r--tests/mdx_esm.rs5
-rw-r--r--tests/mdx_expression_flow.rs3
-rw-r--r--tests/mdx_expression_text.rs5
-rw-r--r--tests/mdx_jsx_text.rs10
-rw-r--r--tests/test_utils/hast.rs10
-rw-r--r--tests/test_utils/jsx_rewrite.rs261
-rw-r--r--tests/test_utils/micromark_swc_utils.rs134
-rw-r--r--tests/test_utils/mod.rs2
-rw-r--r--tests/test_utils/swc.rs145
-rw-r--r--tests/test_utils/swc_utils.rs96
-rw-r--r--tests/test_utils/to_document.rs65
-rw-r--r--tests/test_utils/to_hast.rs22
-rw-r--r--tests/test_utils/to_swc.rs78
-rw-r--r--tests/xxx_document.rs16
-rw-r--r--tests/xxx_hast.rs12
-rw-r--r--tests/xxx_jsx_rewrite.rs32
-rw-r--r--tests/xxx_swc.rs434
26 files changed, 1182 insertions, 581 deletions
diff --git a/src/construct/mdx_esm.rs b/src/construct/mdx_esm.rs
index 53f8beb..4fb6b50 100644
--- a/src/construct/mdx_esm.rs
+++ b/src/construct/mdx_esm.rs
@@ -31,10 +31,7 @@
use crate::event::Name;
use crate::state::{Name as StateName, State};
use crate::tokenizer::Tokenizer;
-use crate::util::{
- mdx_collect::{collect, place_to_point},
- slice::Slice,
-};
+use crate::util::{mdx_collect::collect, slice::Slice};
use crate::MdxSignal;
use alloc::format;
@@ -197,16 +194,24 @@ fn parse_esm(tokenizer: &mut Tokenizer) -> State {
// Collect the body of the ESM and positional info for each run of it.
let result = collect(
- tokenizer,
+ &tokenizer.events,
+ tokenizer.parse_state.bytes,
tokenizer.tokenize_state.start,
&[Name::MdxEsmData, Name::LineEnding],
+ &[],
);
// Parse and handle what was signaled back.
match parse(&result.value) {
MdxSignal::Ok => State::Ok,
- MdxSignal::Error(message, place) => {
- let point = place_to_point(&result, place);
+ MdxSignal::Error(message, relative) => {
+ let point = tokenizer
+ .parse_state
+ .location
+ .as_ref()
+ .expect("expected location index if aware mdx is on")
+ .relative_to_point(&result.stops, relative)
+ .expect("expected non-empty string");
State::Error(format!("{}:{}: {}", point.line, point.column, message))
}
MdxSignal::Eof(message) => {
diff --git a/src/construct/partial_mdx_expression.rs b/src/construct/partial_mdx_expression.rs
index 789443e..fbb13e0 100644
--- a/src/construct/partial_mdx_expression.rs
+++ b/src/construct/partial_mdx_expression.rs
@@ -60,10 +60,7 @@ use crate::construct::partial_space_or_tab::space_or_tab_min_max;
use crate::event::Name;
use crate::state::{Name as StateName, State};
use crate::tokenizer::Tokenizer;
-use crate::util::{
- constant::TAB_SIZE,
- mdx_collect::{collect, place_to_point},
-};
+use crate::util::{constant::TAB_SIZE, mdx_collect::collect};
use crate::{MdxExpressionKind, MdxExpressionParse, MdxSignal};
use alloc::{format, string::ToString};
@@ -205,9 +202,11 @@ pub fn eol_after(tokenizer: &mut Tokenizer) -> State {
fn parse_expression(tokenizer: &mut Tokenizer, parse: &MdxExpressionParse) -> State {
// Collect the body of the expression and positional info for each run of it.
let result = collect(
- tokenizer,
+ &tokenizer.events,
+ tokenizer.parse_state.bytes,
tokenizer.tokenize_state.start,
&[Name::MdxExpressionData, Name::LineEnding],
+ &[],
);
// Turn the name of the expression into a kind.
@@ -221,9 +220,18 @@ fn parse_expression(tokenizer: &mut Tokenizer, parse: &MdxExpressionParse) -> St
// Parse and handle what was signaled back.
match parse(&result.value, &kind) {
MdxSignal::Ok => State::Ok,
- MdxSignal::Error(message, place) => {
- let point = place_to_point(&result, place);
- State::Error(format!("{}:{}: {}", point.line, point.column, message))
+ MdxSignal::Error(message, relative) => {
+ let point = tokenizer
+ .parse_state
+ .location
+ .as_ref()
+ .expect("expected location index if aware mdx is on")
+ .relative_to_point(&result.stops, relative)
+ .map_or((tokenizer.point.line, tokenizer.point.column), |d| {
+ (d.line, d.column)
+ });
+
+ State::Error(format!("{}:{}: {}", point.0, point.1, message))
}
MdxSignal::Eof(message) => {
tokenizer.tokenize_state.mdx_last_parse_error = Some(message);
diff --git a/src/lib.rs b/src/lib.rs
index 02fb5f5..fd0580a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -41,6 +41,9 @@ use util::{
sanitize_uri::sanitize,
};
+#[doc(hidden)]
+pub use util::location::Location;
+
/// Type of line endings in markdown.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub enum LineEnding {
@@ -1252,8 +1255,8 @@ pub fn micromark(value: &str) -> String {
/// # }
/// ```
pub fn micromark_with_options(value: &str, options: &Options) -> Result<String, String> {
- let (events, bytes) = parse(value, &options.parse)?;
- Ok(to_html(&events, bytes, &options.compile))
+ let (events, parse_state) = parse(value, &options.parse)?;
+ Ok(to_html(&events, parse_state.bytes, &options.compile))
}
/// Turn markdown into a syntax tree.
@@ -1279,8 +1282,8 @@ pub fn micromark_with_options(value: &str, options: &Options) -> Result<String,
/// # }
/// ```
pub fn micromark_to_mdast(value: &str, options: &ParseOptions) -> Result<Node, String> {
- let (events, bytes) = parse(value, options)?;
- let node = to_mdast(&events, bytes)?;
+ let (events, parse_state) = parse(value, options)?;
+ let node = to_mdast(&events, parse_state.bytes)?;
Ok(node)
}
diff --git a/src/mdast.rs b/src/mdast.rs
index 8b5b74d..de53532 100644
--- a/src/mdast.rs
+++ b/src/mdast.rs
@@ -9,6 +9,10 @@ use alloc::{
vec::Vec,
};
+/// Relative byte index into a string, to an absolute byte index into the
+/// whole document.
+pub type Stop = (usize, usize);
+
/// Explicitness of a reference.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ReferenceKind {
@@ -429,7 +433,7 @@ pub enum AttributeContent {
/// > | <a {...b} />
/// ^^^^^^
/// ```
- Expression(String),
+ Expression(String, Vec<Stop>),
/// JSX property.
///
/// ```markdown
@@ -448,7 +452,7 @@ pub enum AttributeValue {
/// > | <a b={c} />
/// ^^^
/// ```
- Expression(String),
+ Expression(String, Vec<Stop>),
/// Static value.
///
/// ```markdown
@@ -1040,6 +1044,9 @@ pub struct MdxjsEsm {
pub value: String,
/// Positional info.
pub position: Option<Position>,
+
+ // Custom data on where each slice of `value` came from.
+ pub stops: Vec<Stop>,
}
/// MDX: expression (flow).
@@ -1055,6 +1062,9 @@ pub struct MdxFlowExpression {
pub value: String,
/// Positional info.
pub position: Option<Position>,
+
+ // Custom data on where each slice of `value` came from.
+ pub stops: Vec<Stop>,
}
/// MDX: expression (text).
@@ -1070,6 +1080,9 @@ pub struct MdxTextExpression {
pub value: String,
/// Positional info.
pub position: Option<Position>,
+
+ // Custom data on where each slice of `value` came from.
+ pub stops: Vec<Stop>,
}
/// MDX: JSX element (container).
diff --git a/src/parser.rs b/src/parser.rs
index b694bc5..a7962d0 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -4,6 +4,7 @@ use crate::event::{Event, Point};
use crate::state::{Name as StateName, State};
use crate::subtokenize::subtokenize;
use crate::tokenizer::Tokenizer;
+use crate::util::location::Location;
use crate::ParseOptions;
use alloc::{string::String, vec, vec::Vec};
@@ -14,6 +15,8 @@ use alloc::{string::String, vec, vec::Vec};
#[derive(Debug)]
pub struct ParseState<'a> {
/// Configuration.
+ pub location: Option<Location>,
+ /// Configuration.
pub options: &'a ParseOptions,
/// List of chars.
pub bytes: &'a [u8],
@@ -29,10 +32,17 @@ pub struct ParseState<'a> {
pub fn parse<'a>(
value: &'a str,
options: &'a ParseOptions,
-) -> Result<(Vec<Event>, &'a [u8]), String> {
+) -> Result<(Vec<Event>, ParseState<'a>), String> {
+ let bytes = value.as_bytes();
+
let mut parse_state = ParseState {
options,
- bytes: value.as_bytes(),
+ bytes,
+ location: if options.mdx_esm_parse.is_some() || options.mdx_expression_parse.is_some() {
+ Some(Location::new(bytes))
+ } else {
+ None
+ },
definitions: vec![],
gfm_footnote_definitions: vec![],
};
@@ -72,5 +82,5 @@ pub fn parse<'a>(
}
}
- Ok((events, parse_state.bytes))
+ Ok((events, parse_state))
}
diff --git a/src/to_mdast.rs b/src/to_mdast.rs
index 4db76e6..f2b3c30 100644
--- a/src/to_mdast.rs
+++ b/src/to_mdast.rs
@@ -1,6 +1,6 @@
//! Turn events into a syntax tree.
-use crate::event::{Event, Kind, Name};
+use crate::event::{Event, Kind, Name, Point as EventPoint};
use crate::mdast::{
AttributeContent, AttributeValue, BlockQuote, Break, Code, Definition, Delete, Emphasis,
FootnoteDefinition, FootnoteReference, Heading, Html, Image, ImageReference, InlineCode,
@@ -14,6 +14,7 @@ use crate::util::{
decode as decode_character_reference, parse as parse_character_reference,
},
infer::{gfm_table_align, list_item_loose, list_loose},
+ mdx_collect::collect,
normalize_identifier::normalize_identifier,
slice::{Position as SlicePosition, Slice},
};
@@ -255,8 +256,6 @@ fn enter(context: &mut CompileContext) -> Result<(), String> {
| Name::HtmlTextData
| Name::MathFlowChunk
| Name::MathTextData
- | Name::MdxExpressionData
- | Name::MdxEsmData
| Name::MdxJsxTagAttributeValueLiteralValue => on_enter_data(context),
Name::CodeFencedFenceInfo
| Name::CodeFencedFenceMeta
@@ -267,7 +266,6 @@ fn enter(context: &mut CompileContext) -> Result<(), String> {
| Name::LabelText
| Name::MathFlowFenceMeta
| Name::MdxJsxTagAttributeValueLiteral
- | Name::MdxJsxTagAttributeValueExpression
| Name::ReferenceString
| Name::ResourceDestinationString
| Name::ResourceTitleString => on_enter_buffer(context),
@@ -306,6 +304,9 @@ fn enter(context: &mut CompileContext) -> Result<(), String> {
Name::MdxJsxTagClosingMarker => on_enter_mdx_jsx_tag_closing_marker(context)?,
Name::MdxJsxTagAttribute => on_enter_mdx_jsx_tag_attribute(context)?,
Name::MdxJsxTagAttributeExpression => on_enter_mdx_jsx_tag_attribute_expression(context)?,
+ Name::MdxJsxTagAttributeValueExpression => {
+ on_enter_mdx_jsx_tag_attribute_value_expression(context);
+ }
Name::MdxJsxTagSelfClosingMarker => on_enter_mdx_jsx_tag_self_closing_marker(context)?,
Name::Paragraph => on_enter_paragraph(context),
Name::Reference => on_enter_reference(context),
@@ -347,11 +348,12 @@ fn exit(context: &mut CompileContext) -> Result<(), String> {
| Name::HtmlTextData
| Name::MathFlowChunk
| Name::MathTextData
- | Name::MdxExpressionData
- | Name::MdxEsmData
| Name::MdxJsxTagAttributeValueLiteralValue => {
on_exit_data(context)?;
}
+ Name::MdxJsxTagAttributeExpression | Name::MdxJsxTagAttributeValueExpression => {
+ on_exit_drop(context);
+ }
Name::AutolinkProtocol => on_exit_autolink_protocol(context)?,
Name::AutolinkEmail => on_exit_autolink_email(context)?,
Name::CharacterReferenceMarker => on_exit_character_reference_marker(context),
@@ -391,28 +393,23 @@ fn exit(context: &mut CompileContext) -> Result<(), String> {
Name::HeadingSetext => on_exit_heading_setext(context)?,
Name::HeadingSetextUnderlineSequence => on_exit_heading_setext_underline_sequence(context),
Name::HeadingSetextText => on_exit_heading_setext_text(context),
- Name::HtmlFlow
- | Name::HtmlText
- | Name::MdxEsm
- | Name::MdxFlowExpression
- | Name::MdxTextExpression => on_exit_literal(context)?,
+ Name::HtmlFlow | Name::HtmlText => on_exit_html(context)?,
Name::LabelText => on_exit_label_text(context),
Name::LineEnding => on_exit_line_ending(context)?,
Name::ListItemValue => on_exit_list_item_value(context),
+ Name::MdxEsm | Name::MdxFlowExpression | Name::MdxTextExpression => {
+ on_exit_mdx_esm_or_expression(context)?;
+ }
Name::MdxJsxFlowTag | Name::MdxJsxTextTag => on_exit_mdx_jsx_tag(context)?,
Name::MdxJsxTagClosingMarker => on_exit_mdx_jsx_tag_closing_marker(context),
Name::MdxJsxTagNamePrimary => on_exit_mdx_jsx_tag_name_primary(context),
Name::MdxJsxTagNameMember => on_exit_mdx_jsx_tag_name_member(context),
Name::MdxJsxTagNameLocal => on_exit_mdx_jsx_tag_name_local(context),
- Name::MdxJsxTagAttributeExpression => on_exit_mdx_jsx_tag_attribute_expression(context),
Name::MdxJsxTagAttributePrimaryName => on_exit_mdx_jsx_tag_attribute_primary_name(context),
Name::MdxJsxTagAttributeNameLocal => on_exit_mdx_jsx_tag_attribute_name_local(context),
Name::MdxJsxTagAttributeValueLiteral => {
on_exit_mdx_jsx_tag_attribute_value_literal(context);
}
- Name::MdxJsxTagAttributeValueExpression => {
- on_exit_mdx_jsx_tag_attribute_value_expression(context);
- }
Name::MdxJsxTagSelfClosingMarker => on_exit_mdx_jsx_tag_self_closing_marker(context),
Name::ReferenceString => on_exit_reference_string(context),
@@ -499,27 +496,51 @@ fn on_enter_math_text(context: &mut CompileContext) {
/// Handle [`Enter`][Kind::Enter]:[`MdxEsm`][Name::MdxEsm].
fn on_enter_mdx_esm(context: &mut CompileContext) {
+ let result = collect(
+ context.events,
+ context.bytes,
+ context.index,
+ &[Name::MdxEsmData, Name::LineEnding],
+ &[Name::MdxEsm],
+ );
context.tail_push(Node::MdxjsEsm(MdxjsEsm {
- value: String::new(),
+ value: result.value,
position: None,
+ stops: result.stops,
}));
context.buffer();
}
/// Handle [`Enter`][Kind::Enter]:[`MdxFlowExpression`][Name::MdxFlowExpression].
fn on_enter_mdx_flow_expression(context: &mut CompileContext) {
+ let result = collect(
+ context.events,
+ context.bytes,
+ context.index,
+ &[Name::MdxExpressionData, Name::LineEnding],
+ &[Name::MdxFlowExpression],
+ );
context.tail_push(Node::MdxFlowExpression(MdxFlowExpression {
- value: String::new(),
+ value: result.value,
position: None,
+ stops: result.stops,
}));
context.buffer();
}
/// Handle [`Enter`][Kind::Enter]:[`MdxTextExpression`][Name::MdxTextExpression].
fn on_enter_mdx_text_expression(context: &mut CompileContext) {
+ let result = collect(
+ context.events,
+ context.bytes,
+ context.index,
+ &[Name::MdxExpressionData, Name::LineEnding],
+ &[Name::MdxTextExpression],
+ );
context.tail_push(Node::MdxTextExpression(MdxTextExpression {
- value: String::new(),
+ value: result.value,
position: None,
+ stops: result.stops,
}));
context.buffer();
}
@@ -801,18 +822,50 @@ fn on_enter_mdx_jsx_tag_attribute(context: &mut CompileContext) -> Result<(), St
fn on_enter_mdx_jsx_tag_attribute_expression(context: &mut CompileContext) -> Result<(), String> {
on_enter_mdx_jsx_tag_any_attribute(context)?;
+ let result = collect(
+ context.events,
+ context.bytes,
+ context.index,
+ &[Name::MdxExpressionData, Name::LineEnding],
+ &[Name::MdxJsxTagAttributeExpression],
+ );
context
.jsx_tag
.as_mut()
.expect("expected tag")
.attributes
- .push(AttributeContent::Expression(String::new()));
+ .push(AttributeContent::Expression(result.value, result.stops));
context.buffer();
Ok(())
}
+/// Handle [`Enter`][Kind::Enter]:[`MdxJsxTagAttributeValueExpression`][Name::MdxJsxTagAttributeValueExpression].
+fn on_enter_mdx_jsx_tag_attribute_value_expression(context: &mut CompileContext) {
+ let result = collect(
+ context.events,
+ context.bytes,
+ context.index,
+ &[Name::MdxExpressionData, Name::LineEnding],
+ &[Name::MdxJsxTagAttributeValueExpression],
+ );
+
+ if let Some(AttributeContent::Property(node)) = context
+ .jsx_tag
+ .as_mut()
+ .expect("expected tag")
+ .attributes
+ .last_mut()
+ {
+ node.value = Some(AttributeValue::Expression(result.value, result.stops));
+ } else {
+ unreachable!("expected property")
+ }
+
+ context.buffer();
+}
+
/// Handle [`Enter`][Kind::Enter]:[`MdxJsxTagSelfClosingMarker`][Name::MdxJsxTagSelfClosingMarker].
fn on_enter_mdx_jsx_tag_self_closing_marker(context: &mut CompileContext) -> Result<(), String> {
let tag = context.jsx_tag.as_ref().expect("expected tag");
@@ -1086,6 +1139,11 @@ fn on_exit_definition_title_string(context: &mut CompileContext) {
}
}
+/// Handle [`Exit`][Kind::Exit]:*, by dropping the current buffer.
+fn on_exit_drop(context: &mut CompileContext) {
+ context.resume();
+}
+
/// Handle [`Exit`][Kind::Exit]:[`Frontmatter`][Name::Frontmatter].
fn on_exit_frontmatter(context: &mut CompileContext) -> Result<(), String> {
let value = trim_eol(context.resume().to_string(), true, true);
@@ -1280,20 +1338,16 @@ fn on_exit_line_ending(context: &mut CompileContext) -> Result<(), String> {
Ok(())
}
-/// Handle [`Exit`][Kind::Exit]:{[`HtmlFlow`][Name::HtmlFlow],[`MdxFlowExpression`][Name::MdxFlowExpression],etc}.
-fn on_exit_literal(context: &mut CompileContext) -> Result<(), String> {
+/// Handle [`Exit`][Kind::Exit]:{[`HtmlFlow`][Name::HtmlFlow],[`HtmlText`][Name::HtmlText]}.
+fn on_exit_html(context: &mut CompileContext) -> Result<(), String> {
let value = context.resume().to_string();
match context.tail_mut() {
Node::Html(node) => node.value = value,
- Node::MdxFlowExpression(node) => node.value = value,
- Node::MdxTextExpression(node) => node.value = value,
- Node::MdxjsEsm(node) => node.value = value,
- _ => unreachable!("expected html, mdx expression, etc on stack for value"),
+ _ => unreachable!("expected html on stack for value"),
}
on_exit(context)?;
-
Ok(())
}
@@ -1483,26 +1537,13 @@ fn on_exit_mdx_jsx_tag_name_local(context: &mut CompileContext) {
name.push_str(slice.as_str());
}
-/// Handle [`Exit`][Kind::Exit]:[`MdxJsxTagAttributeExpression`][Name::MdxJsxTagAttributeExpression].
-fn on_exit_mdx_jsx_tag_attribute_expression(context: &mut CompileContext) {
- let value = context.resume();
-
- if let Some(AttributeContent::Expression(expression)) = context
- .jsx_tag
- .as_mut()
- .expect("expected tag")
- .attributes
- .last_mut()
- {
- expression.push_str(value.to_string().as_str());
- } else {
- unreachable!("expected expression")
- }
+/// Handle [`Exit`][Kind::Exit]:{[`MdxEsm`][Name::MdxEsm],[`MdxFlowExpression`][Name::MdxFlowExpression],[`MdxTextExpression`][Name::MdxTextExpression]}.
+fn on_exit_mdx_esm_or_expression(context: &mut CompileContext) -> Result<(), String> {
+ on_exit_drop(context);
+ context.tail_pop()?;
+ Ok(())
}
-// Name:: => (context),
-// Name:: => (context),
-
/// Handle [`Exit`][Kind::Exit]:[`MdxJsxTagAttributePrimaryName`][Name::MdxJsxTagAttributePrimaryName].
fn on_exit_mdx_jsx_tag_attribute_primary_name(context: &mut CompileContext) {
let slice = Slice::from_position(
@@ -1563,23 +1604,6 @@ fn on_exit_mdx_jsx_tag_attribute_value_literal(context: &mut CompileContext) {
}
}
-/// Handle [`Exit`][Kind::Exit]:[`MdxJsxTagAttributeValueExpression`][Name::MdxJsxTagAttributeValueExpression].
-fn on_exit_mdx_jsx_tag_attribute_value_expression(context: &mut CompileContext) {
- let value = context.resume();
-
- if let Some(AttributeContent::Property(node)) = context
- .jsx_tag
- .as_mut()
- .expect("expected tag")
- .attributes
- .last_mut()
- {
- node.value = Some(AttributeValue::Expression(value.to_string()));
- } else {
- unreachable!("expected property")
- }
-}
-
/// Handle [`Exit`][Kind::Exit]:[`MdxJsxTagSelfClosingMarker`][Name::MdxJsxTagSelfClosingMarker].
fn on_exit_mdx_jsx_tag_self_closing_marker(context: &mut CompileContext) {
context.jsx_tag.as_mut().expect("expected tag").self_closing = true;
@@ -1625,8 +1649,13 @@ fn on_exit_resource_title_string(context: &mut CompileContext) {
}
// Create a point from an event.
+fn point_from_event_point(point: &EventPoint) -> Point {
+ Point::new(point.line, point.column, point.index)
+}
+
+// Create a point from an event.
fn point_from_event(event: &Event) -> Point {
- Point::new(event.point.line, event.point.column, event.point.index)
+ point_from_event_point(&event.point)
}
// Create a position from an event.
diff --git a/src/util/location.rs b/src/util/location.rs
new file mode 100644
index 0000000..0c9c426
--- /dev/null
+++ b/src/util/location.rs
@@ -0,0 +1,111 @@
+//! Deal with positions in a file.
+//!
+//! * Convert between byte indices and unist points.
+//! * Convert between byte indices into a string which is built up of several
+//! slices in a whole document, and byte indices into that whole document.
+
+use crate::unist::Point;
+use alloc::{vec, vec::Vec};
+
+/// Each stop represents a new slice, which contains the byte index into the
+/// corresponding string where the slice starts (`0`), and the byte index into
+/// the whole document where that slice starts (`1`).
+pub type Stop = (usize, usize);
+
+#[derive(Debug)]
+pub struct Location {
+ /// List, where each index is a line number (0-based), and each value is
+ /// the byte index *after* where the line ends.
+ indices: Vec<usize>,
+}
+
+impl Location {
+ /// Get an index for the given `bytes`.
+ ///
+ /// Port of <https://github.com/vfile/vfile-location/blob/main/index.js>
+ #[must_use]
+ pub fn new(bytes: &[u8]) -> Self {
+ let mut index = 0;
+ let mut location_index = Self { indices: vec![] };
+
+ while index < bytes.len() {
+ if bytes[index] == b'\r' {
+ if index + 1 < bytes.len() && bytes[index + 1] == b'\n' {
+ location_index.indices.push(index + 2);
+ } else {
+ location_index.indices.push(index + 1);
+ }
+ } else if bytes[index] == b'\n' {
+ location_index.indices.push(index + 1);
+ }
+
+ index += 1;
+ }
+
+ location_index.indices.push(index + 1);
+ location_index
+ }
+
+ /// Get the line and column-based `point` for `offset` in the bound indices.
+ ///
+ /// Returns `None` when given out of bounds input.
+ ///
+ /// Port of <https://github.com/vfile/vfile-location/blob/main/index.js>
+ #[must_use]
+ pub fn to_point(&self, offset: usize) -> Option<Point> {
+ let mut index = 0;
+
+ if let Some(end) = self.indices.last() {
+ if offset < *end {
+ while index < self.indices.len() {
+ if self.indices[index] > offset {
+ break;
+ }
+
+ index += 1;
+ }
+
+ let previous = if index > 0 {
+ self.indices[index - 1]
+ } else {
+ 0
+ };
+ return Some(Point {
+ line: index + 1,
+ column: offset + 1 - previous,
+ offset,
+ });
+ }
+ }
+
+ None
+ }
+
+ /// Like `to_point`, but takes a relative offset from a certain string
+ /// instead of an absolute offset into the whole document.
+ ///
+ /// The relative offset is made absolute based on `stops`, which represent
+ /// where that certain string is in the whole document.
+ #[must_use]
+ pub fn relative_to_point(&self, stops: &[Stop], relative: usize) -> Option<Point> {
+ Location::relative_to_absolute(stops, relative).and_then(|absolute| self.to_point(absolute))
+ }
+
+ /// Turn a relative offset into an absolute offset.
+ #[must_use]
+ pub fn relative_to_absolute(stops: &[Stop], relative: usize) -> Option<usize> {
+ let mut index = 0;
+
+ while index < stops.len() && stops[index].0 <= relative {
+ index += 1;
+ }
+
+ // There are no points: that only occurs if there was an empty string.
+ if index == 0 {
+ None
+ } else {
+ let (stop_relative, stop_absolute) = &stops[index - 1];
+ Some(stop_absolute + (relative - stop_relative))
+ }
+ }
+}
diff --git a/src/util/mdx_collect.rs b/src/util/mdx_collect.rs
index 73ead51..02921a4 100644
--- a/src/util/mdx_collect.rs
+++ b/src/util/mdx_collect.rs
@@ -1,70 +1,53 @@
//! Collect info for MDX.
-use crate::event::{Kind, Name, Point};
-use crate::tokenizer::Tokenizer;
+use crate::event::{Event, Kind, Name};
use crate::util::slice::{Position, Slice};
use alloc::{string::String, vec, vec::Vec};
-pub type Location<'a> = (usize, &'a Point);
+pub type Stop = (usize, usize);
-pub struct Result<'a> {
- pub start: &'a Point,
+#[derive(Debug)]
+pub struct Result {
pub value: String,
- pub locations: Vec<Location<'a>>,
+ pub stops: Vec<Stop>,
}
-pub fn collect<'a>(tokenizer: &'a Tokenizer, from: usize, names: &[Name]) -> Result<'a> {
+pub fn collect(
+ events: &[Event],
+ bytes: &[u8],
+ from: usize,
+ names: &[Name],
+ stop: &[Name],
+) -> Result {
let mut result = Result {
- start: &tokenizer.events[from].point,
value: String::new(),
- locations: vec![],
+ stops: vec![],
};
let mut index = from;
- let mut acc = 0;
- while index < tokenizer.events.len() {
- if tokenizer.events[index].kind == Kind::Enter
- && names.contains(&tokenizer.events[index].name)
- {
- // Include virtual spaces.
- let value = Slice::from_position(
- tokenizer.parse_state.bytes,
- &Position {
- start: &tokenizer.events[index].point,
- end: &tokenizer.events[index + 1].point,
- },
- )
- .serialize();
- acc += value.len();
- result.locations.push((acc, &tokenizer.events[index].point));
- result.value.push_str(&value);
- }
-
- index += 1;
- }
-
- result
-}
-
-// Turn an index of `result.value` into a point in the whole document.
-pub fn place_to_point(result: &Result, place: usize) -> Point {
- let mut index = 0;
- let mut point = result.start;
- let mut rest = place;
-
- while index < result.locations.len() {
- point = result.locations[index].1;
-
- if result.locations[index].0 > place {
+ while index < events.len() {
+ if events[index].kind == Kind::Enter {
+ if names.contains(&events[index].name) {
+ // Include virtual spaces, and assume void.
+ let value = Slice::from_position(
+ bytes,
+ &Position {
+ start: &events[index].point,
+ end: &events[index + 1].point,
+ },
+ )
+ .serialize();
+ result
+ .stops
+ .push((result.value.len(), events[index].point.index));
+ result.value.push_str(&value);
+ }
+ } else if stop.contains(&events[index].name) {
break;
}
- rest = place - result.locations[index].0;
index += 1;
}
- let mut point = point.clone();
- point.column += rest;
- point.index += rest;
- point
+ result
}
diff --git a/src/util/mod.rs b/src/util/mod.rs
index ac93be0..f44e183 100644
--- a/src/util/mod.rs
+++ b/src/util/mod.rs
@@ -8,6 +8,7 @@ pub mod encode;
pub mod gfm_tagfilter;
pub mod identifier;
pub mod infer;
+pub mod location;
pub mod mdx_collect;
pub mod normalize_identifier;
pub mod sanitize_uri;
diff --git a/tests/mdx_esm.rs b/tests/mdx_esm.rs
index d6021a1..31f493b 100644
--- a/tests/mdx_esm.rs
+++ b/tests/mdx_esm.rs
@@ -101,7 +101,7 @@ fn mdx_esm() -> Result<(), String> {
assert_eq!(
micromark_with_options("import 1/1", &swc).err().unwrap(),
- "1:9: Could not parse esm with swc: Expected 'from', got 'numeric literal (1, 1)'",
+ "1:8: Could not parse esm with swc: Expected 'from', got 'numeric literal (1, 1)'",
"should crash on invalid import/exports (2)"
);
@@ -250,7 +250,8 @@ fn mdx_esm() -> Result<(), String> {
Node::Root(Root {
children: vec![Node::MdxjsEsm(MdxjsEsm {
value: "import a from 'b'\nexport {a}".to_string(),
- position: Some(Position::new(1, 1, 0, 2, 11, 28))
+ position: Some(Position::new(1, 1, 0, 2, 11, 28)),
+ stops: vec![(0, 0), (17, 17), (18, 18)]
})],
position: Some(Position::new(1, 1, 0, 2, 11, 28))
}),
diff --git a/tests/mdx_expression_flow.rs b/tests/mdx_expression_flow.rs
index 0b13149..c9ff560 100644
--- a/tests/mdx_expression_flow.rs
+++ b/tests/mdx_expression_flow.rs
@@ -94,7 +94,8 @@ fn mdx_expression_flow_agnostic() -> Result<(), String> {
Node::Root(Root {
children: vec![Node::MdxFlowExpression(MdxFlowExpression {
value: "alpha +\nbravo".to_string(),
- position: Some(Position::new(1, 1, 0, 2, 7, 15))
+ position: Some(Position::new(1, 1, 0, 2, 7, 15)),
+ stops: vec![(0, 1), (7, 8), (8, 9)]
})],
position: Some(Position::new(1, 1, 0, 2, 7, 15))
}),
diff --git a/tests/mdx_expression_text.rs b/tests/mdx_expression_text.rs
index d893a70..0aee081 100644
--- a/tests/mdx_expression_text.rs
+++ b/tests/mdx_expression_text.rs
@@ -119,7 +119,7 @@ fn mdx_expression_text_gnostic_core() -> Result<(), String> {
assert_eq!(
micromark_with_options("a {var b = \"c\"} d", &swc).err().unwrap(),
- "1:7: Could not parse expression with swc: Unexpected token `var`. Expected this, import, async, function, [ for array literal, { for object literal, @ for decorator, function, class, null, true, false, number, bigint, string, regexp, ` for template literal, (, or an identifier",
+ "1:4: Could not parse expression with swc: Unexpected token `var`. Expected this, import, async, function, [ for array literal, { for object literal, @ for decorator, function, class, null, true, false, number, bigint, string, regexp, ` for template literal, (, or an identifier",
"should crash on non-expressions"
);
@@ -213,7 +213,8 @@ fn mdx_expression_text_agnostic() -> Result<(), String> {
}),
Node::MdxTextExpression(MdxTextExpression {
value: "alpha".to_string(),
- position: Some(Position::new(1, 3, 2, 1, 10, 9))
+ position: Some(Position::new(1, 3, 2, 1, 10, 9)),
+ stops: vec![(0, 3)]
}),
Node::Text(Text {
value: " b.".to_string(),
diff --git a/tests/mdx_jsx_text.rs b/tests/mdx_jsx_text.rs
index 9064e83..22a701a 100644
--- a/tests/mdx_jsx_text.rs
+++ b/tests/mdx_jsx_text.rs
@@ -169,7 +169,10 @@ fn mdx_jsx_text_core() -> Result<(), String> {
children: vec![
Node::MdxJsxTextElement(MdxJsxTextElement {
name: Some("a".to_string()),
- attributes: vec![AttributeContent::Expression("...b".to_string())],
+ attributes: vec![AttributeContent::Expression(
+ "...b".to_string(),
+ vec![(0, 4)]
+ )],
children: vec![],
position: Some(Position::new(1, 1, 0, 1, 13, 12))
}),
@@ -235,7 +238,10 @@ fn mdx_jsx_text_core() -> Result<(), String> {
}),
AttributeContent::Property(MdxJsxAttribute {
name: "f".to_string(),
- value: Some(AttributeValue::Expression("g".to_string())),
+ value: Some(AttributeValue::Expression(
+ "g".to_string(),
+ vec![(0, 18)]
+ )),
}),
],
children: vec![],
diff --git a/tests/test_utils/hast.rs b/tests/test_utils/hast.rs
index 1ad8789..48460ca 100644
--- a/tests/test_utils/hast.rs
+++ b/tests/test_utils/hast.rs
@@ -1,6 +1,6 @@
#![allow(dead_code)]
-// ^-- fix later
+// ^-- To do: fix later
extern crate alloc;
extern crate micromark;
@@ -9,7 +9,7 @@ use alloc::{
string::{String, ToString},
vec::Vec,
};
-pub use micromark::mdast::{AttributeContent, AttributeValue, MdxJsxAttribute};
+pub use micromark::mdast::{AttributeContent, AttributeValue, MdxJsxAttribute, Stop};
use micromark::unist::Position;
/// Nodes.
@@ -254,6 +254,9 @@ pub struct MdxExpression {
pub value: String,
/// Positional info.
pub position: Option<Position>,
+
+ // Custom data on where each slice of `value` came from.
+ pub stops: Vec<Stop>,
}
/// MDX: ESM.
@@ -269,4 +272,7 @@ pub struct MdxjsEsm {
pub value: String,
/// Positional info.
pub position: Option<Position>,
+
+ // Custom data on where each slice of `value` came from.
+ pub stops: Vec<Stop>,
}
diff --git a/tests/test_utils/jsx_rewrite.rs b/tests/test_utils/jsx_rewrite.rs
index b6ffad6..9dd2605 100644
--- a/tests/test_utils/jsx_rewrite.rs
+++ b/tests/test_utils/jsx_rewrite.rs
@@ -1,9 +1,13 @@
extern crate swc_common;
extern crate swc_ecma_ast;
-use crate::{
- micromark::{id_cont_ as id_cont, id_start_ as id_start},
- test_utils::to_swc::Program,
+use crate::test_utils::{
+ micromark_swc_utils::{position_to_string, span_to_position},
+ swc_utils::{
+ create_binary_expression, create_ident, create_ident_expression, create_member_expression,
+ },
+ to_swc::Program,
};
+use micromark::{id_cont_ as id_cont, id_start_ as id_start, unist::Position, Location};
use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
/// Configuration.
@@ -21,10 +25,17 @@ pub struct Options {
/// Rewrite JSX in an MDX file so that components can be passed in and provided.
#[allow(dead_code)]
-pub fn jsx_rewrite(mut program: Program, options: &Options) -> Program {
+pub fn jsx_rewrite(
+ mut program: Program,
+ options: &Options,
+ location: Option<&Location>,
+) -> Program {
let mut state = State {
scopes: vec![],
+ location,
provider: options.provider_import_source.is_some(),
+ path: program.path.clone(),
+ development: options.development,
create_provider_import: false,
create_error_helper: false,
};
@@ -44,7 +55,10 @@ pub fn jsx_rewrite(mut program: Program, options: &Options) -> Program {
// If potentially missing components are used, add the helper used for
// errors.
if state.create_error_helper {
- program.module.body.push(create_error_helper());
+ program
+ .module
+ .body
+ .push(create_error_helper(state.development, state.path));
}
program
@@ -88,8 +102,7 @@ struct Info {
/// ```
/// vec![("a".into(), false), ("a.b".into(), true)]
/// ```
- // To do: add positional info later.
- references: Vec<(String, bool)>,
+ references: Vec<(String, bool, Option<Position>)>,
}
/// Scope (block or function/global).
@@ -103,9 +116,14 @@ struct Scope {
/// Context.
#[derive(Debug, Default, Clone)]
-struct State {
+struct State<'a> {
+ location: Option<&'a Location>,
+ /// Path to file.
+ path: Option<String>,
/// List of current scopes.
scopes: Vec<Scope>,
+ /// Whether the user is in development mode.
+ development: bool,
/// Whether the user uses a provider.
provider: bool,
/// Whether a provider is referenced.
@@ -117,7 +135,7 @@ struct State {
create_error_helper: bool,
}
-impl State {
+impl<'a> State<'a> {
/// Open a new scope.
fn enter(&mut self, info: Option<Info>) {
self.scopes.push(Scope {
@@ -432,8 +450,47 @@ impl State {
// if (!a) _missingMdxReference("a", false);
// if (!a.b) _missingMdxReference("a.b", true);
// ```
- for (id, component) in info.references {
+ for (id, component, position) in info.references {
self.create_error_helper = true;
+
+ let mut args = vec![
+ swc_ecma_ast::ExprOrSpread {
+ spread: None,
+ expr: Box::new(swc_ecma_ast::Expr::Lit(swc_ecma_ast::Lit::Str(
+ swc_ecma_ast::Str {
+ value: id.clone().into(),
+ span: swc_common::DUMMY_SP,
+ raw: None,
+ },
+ ))),
+ },
+ swc_ecma_ast::ExprOrSpread {
+ spread: None,
+ expr: Box::new(swc_ecma_ast::Expr::Lit(swc_ecma_ast::Lit::Bool(
+ swc_ecma_ast::Bool {
+ value: component,
+ span: swc_common::DUMMY_SP,
+ },
+ ))),
+ },
+ ];
+
+ // Add the source location if it exists and if `development` is on.
+ if let Some(position) = position.as_ref() {
+ if self.development {
+ args.push(swc_ecma_ast::ExprOrSpread {
+ spread: None,
+ expr: Box::new(swc_ecma_ast::Expr::Lit(swc_ecma_ast::Lit::Str(
+ swc_ecma_ast::Str {
+ value: position_to_string(position).into(),
+ span: swc_common::DUMMY_SP,
+ raw: None,
+ },
+ ))),
+ })
+ }
+ }
+
statements.push(swc_ecma_ast::Stmt::If(swc_ecma_ast::IfStmt {
test: Box::new(swc_ecma_ast::Expr::Unary(swc_ecma_ast::UnaryExpr {
op: swc_ecma_ast::UnaryOp::Bang,
@@ -446,27 +503,7 @@ impl State {
callee: swc_ecma_ast::Callee::Expr(Box::new(create_ident_expression(
"_missingMdxReference",
))),
- args: vec![
- swc_ecma_ast::ExprOrSpread {
- spread: None,
- expr: Box::new(swc_ecma_ast::Expr::Lit(swc_ecma_ast::Lit::Str(
- swc_ecma_ast::Str {
- value: id.into(),
- span: swc_common::DUMMY_SP,
- raw: None,
- },
- ))),
- },
- swc_ecma_ast::ExprOrSpread {
- spread: None,
- expr: Box::new(swc_ecma_ast::Expr::Lit(swc_ecma_ast::Lit::Bool(
- swc_ecma_ast::Bool {
- value: component,
- span: swc_common::DUMMY_SP,
- },
- ))),
- },
- ],
+ args,
type_args: None,
span: swc_common::DUMMY_SP,
})),
@@ -545,6 +582,7 @@ impl State {
None
}
}
+
/// Get the top-level scope’s info, mutably.
fn current_top_level_info_mut(&mut self) -> Option<&mut Info> {
if let Some(scope) = self.scopes.get_mut(1) {
@@ -623,7 +661,7 @@ impl State {
}
}
-impl VisitMut for State {
+impl<'a> VisitMut for State<'a> {
noop_visit_mut_type!();
/// Rewrite JSX identifiers.
@@ -632,6 +670,7 @@ impl VisitMut for State {
if let Some(info) = self.current_top_level_info() {
// Rewrite only if we can rewrite.
if is_props_receiving_fn(&info.name) || self.provider {
+ let position = span_to_position(&node.span, self.location);
match &node.opening.name {
// `<x.y>`, `<Foo.Bar>`, `<x.y.z>`.
swc_ecma_ast::JSXElementName::JSXMemberExpr(d) => {
@@ -656,7 +695,6 @@ impl VisitMut for State {
if !in_scope {
let info_mut = self.current_top_level_info_mut().unwrap();
- // To do: add positional info.
let mut index = 1;
while index <= ids.len() {
let full_id = ids[0..index].join(".");
@@ -668,7 +706,9 @@ impl VisitMut for State {
reference.1 = true;
}
} else {
- info_mut.references.push((full_id, component))
+ info_mut
+ .references
+ .push((full_id, component, position.clone()))
}
index += 1;
}
@@ -749,7 +789,7 @@ impl VisitMut for State {
{
reference.1 = true;
} else {
- info_mut.references.push((id.clone(), true))
+ info_mut.references.push((id.clone(), true, position))
}
}
@@ -948,8 +988,8 @@ fn create_import_provider(source: &str) -> swc_ecma_ast::ModuleItem {
/// throw new Error("Expected " + (component ? "component" : "object") + " `" + id + "` to be defined: you likely forgot to import, pass, or provide it.");
/// }
/// ```
-fn create_error_helper() -> swc_ecma_ast::ModuleItem {
- let parameters = vec![
+fn create_error_helper(development: bool, path: Option<String>) -> swc_ecma_ast::ModuleItem {
+ let mut parameters = vec![
swc_ecma_ast::Param {
pat: swc_ecma_ast::Pat::Ident(swc_ecma_ast::BindingIdent {
id: create_ident("id"),
@@ -968,7 +1008,19 @@ fn create_error_helper() -> swc_ecma_ast::ModuleItem {
},
];
- let message = vec![
+ // Accept a source location (which might be undefiend).
+ if development {
+ parameters.push(swc_ecma_ast::Param {
+ pat: swc_ecma_ast::Pat::Ident(swc_ecma_ast::BindingIdent {
+ id: create_ident("place"),
+ type_ann: None,
+ }),
+ decorators: vec![],
+ span: swc_common::DUMMY_SP,
+ })
+ }
+
+ let mut message = vec![
swc_ecma_ast::Expr::Lit(swc_ecma_ast::Lit::Str(swc_ecma_ast::Str {
value: "Expected ".into(),
span: swc_common::DUMMY_SP,
@@ -1009,8 +1061,43 @@ fn create_error_helper() -> swc_ecma_ast::ModuleItem {
})),
];
- // To do: in development, add `place` param, and use the positional info.
- // Also, then, add file path.
+ // `place ? "\nIt’s referenced in your code at `" + place+ "`" : ""`
+ if development {
+ message.push(swc_ecma_ast::Expr::Paren(swc_ecma_ast::ParenExpr {
+ expr: Box::new(swc_ecma_ast::Expr::Cond(swc_ecma_ast::CondExpr {
+ test: Box::new(create_ident_expression("place")),
+ cons: Box::new(create_binary_expression(
+ vec![
+ swc_ecma_ast::Expr::Lit(swc_ecma_ast::Lit::Str(swc_ecma_ast::Str {
+ value: "\nIt’s referenced in your code at `".into(),
+ span: swc_common::DUMMY_SP,
+ raw: None,
+ })),
+ create_ident_expression("place"),
+ swc_ecma_ast::Expr::Lit(swc_ecma_ast::Lit::Str(swc_ecma_ast::Str {
+ value: if let Some(path) = path {
+ format!("` in `{}`", path).into()
+ } else {
+ "`".into()
+ },
+ span: swc_common::DUMMY_SP,
+ raw: None,
+ })),
+ ],
+ swc_ecma_ast::BinaryOp::Add,
+ )),
+ alt: Box::new(swc_ecma_ast::Expr::Lit(swc_ecma_ast::Lit::Str(
+ swc_ecma_ast::Str {
+ value: "".into(),
+ span: swc_common::DUMMY_SP,
+ raw: None,
+ },
+ ))),
+ span: swc_common::DUMMY_SP,
+ })),
+ span: swc_common::DUMMY_SP,
+ }))
+ }
swc_ecma_ast::ModuleItem::Stmt(swc_ecma_ast::Stmt::Decl(swc_ecma_ast::Decl::Fn(
swc_ecma_ast::FnDecl {
@@ -1047,100 +1134,6 @@ fn create_error_helper() -> swc_ecma_ast::ModuleItem {
)))
}
-/// Generate a binary expression.
-///
-/// ```js
-/// a + b + c
-/// a || b
-/// ```
-fn create_binary_expression(
- mut exprs: Vec<swc_ecma_ast::Expr>,
- op: swc_ecma_ast::BinaryOp,
-) -> swc_ecma_ast::Expr {
- exprs.reverse();
-
- let mut left = None;
-
- while let Some(right_expr) = exprs.pop() {
- left = Some(if let Some(left_expr) = left {
- swc_ecma_ast::Expr::Bin(swc_ecma_ast::BinExpr {
- left: Box::new(left_expr),
- right: Box::new(right_expr),
- op,
- span: swc_common::DUMMY_SP,
- })
- } else {
- right_expr
- });
- }
-
- left.expect("expected one or more expressions")
-}
-
-/// Generate a member expression.
-///
-/// ```js
-/// a.b
-/// a
-/// ```
-fn create_member_expression(name: &str) -> swc_ecma_ast::Expr {
- let bytes = name.as_bytes();
- let mut index = 0;
- let mut start = 0;
- let mut parts = vec![];
-
- while index < bytes.len() {
- if bytes[index] == b'.' {
- parts.push(&name[start..index]);
- start = index + 1;
- }
-
- index += 1;
- }
-
- if parts.len() > 1 {
- let mut member = swc_ecma_ast::MemberExpr {
- obj: Box::new(create_ident_expression(parts[0])),
- prop: swc_ecma_ast::MemberProp::Ident(create_ident(parts[1])),
- span: swc_common::DUMMY_SP,
- };
- let mut index = 2;
- while index < parts.len() {
- member = swc_ecma_ast::MemberExpr {
- obj: Box::new(swc_ecma_ast::Expr::Member(member)),
- prop: swc_ecma_ast::MemberProp::Ident(create_ident(parts[1])),
- span: swc_common::DUMMY_SP,
- };
- index += 1;
- }
- swc_ecma_ast::Expr::Member(member)
- } else {
- create_ident_expression(name)
- }
-}
-
-/// Generate an ident expression.
-///
-/// ```js
-/// a
-/// ```
-fn create_ident_expression(sym: &str) -> swc_ecma_ast::Expr {
- swc_ecma_ast::Expr::Ident(create_ident(sym))
-}
-
-/// Generate an ident.
-///
-/// ```js
-/// a
-/// ```
-fn create_ident(sym: &str) -> swc_ecma_ast::Ident {
- swc_ecma_ast::Ident {
- sym: sym.into(),
- optional: false,
- span: swc_common::DUMMY_SP,
- }
-}
-
/// Check if this function is a props receiving component: it’s one of ours.
fn is_props_receiving_fn(name: &Option<String>) -> bool {
if let Some(name) = name {
diff --git a/tests/test_utils/micromark_swc_utils.rs b/tests/test_utils/micromark_swc_utils.rs
new file mode 100644
index 0000000..13678d5
--- /dev/null
+++ b/tests/test_utils/micromark_swc_utils.rs
@@ -0,0 +1,134 @@
+extern crate swc_common;
+use micromark::{
+ mdast::Stop,
+ unist::{Point, Position},
+ Location,
+};
+use swc_common::{BytePos, Span, SyntaxContext, DUMMY_SP};
+use swc_ecma_visit::{noop_visit_mut_type, VisitMut};
+
+/// Turn a unist position, into an SWC span, of two byte positions.
+///
+/// > 👉 **Note**: SWC byte positions are offset by one: they are `0` when they
+/// > are missing or incremented by `1` when valid.
+pub fn position_to_span(position: Option<&Position>) -> Span {
+ position.map_or(DUMMY_SP, |d| Span {
+ lo: point_to_bytepos(&d.start),
+ hi: point_to_bytepos(&d.end),
+ ctxt: SyntaxContext::empty(),
+ })
+}
+
+/// Turn an SWC span, of two byte positions, into a unist position.
+///
+/// This assumes the span comes from a fixed tree, or is a dummy.
+///
+/// > 👉 **Note**: SWC byte positions are offset by one: they are `0` when they
+/// > are missing or incremented by `1` when valid.
+pub fn span_to_position(span: &Span, location: Option<&Location>) -> Option<Position> {
+ let lo = span.lo.0 as usize;
+ let hi = span.hi.0 as usize;
+
+ if lo > 0 && hi > 0 {
+ if let Some(location) = location {
+ if let Some(start) = location.to_point(lo - 1) {
+ if let Some(end) = location.to_point(hi - 1) {
+ return Some(Position { start, end });
+ }
+ }
+ }
+ }
+
+ None
+}
+
+/// Turn a unist point into an SWC byte position.
+///
+/// > 👉 **Note**: SWC byte positions are offset by one: they are `0` when they
+/// > are missing or incremented by `1` when valid.
+pub fn point_to_bytepos(point: &Point) -> BytePos {
+ BytePos(point.offset as u32 + 1)
+}
+
+/// Turn an SWC byte position into a unist point.
+///
+/// This assumes the byte position comes from a fixed tree, or is a dummy.
+///
+/// > 👉 **Note**: SWC byte positions are offset by one: they are `0` when they
+/// > are missing or incremented by `1` when valid.
+pub fn bytepos_to_point(bytepos: &BytePos, location: Option<&Location>) -> Option<Point> {
+ let pos = bytepos.0 as usize;
+
+ if pos > 0 {
+ if let Some(location) = location {
+ return location.to_point(pos - 1);
+ }
+ }
+
+ None
+}
+
+/// Prefix an error message with an optional point.
+pub fn prefix_error_with_point(reason: String, point: Option<&Point>) -> String {
+ if let Some(point) = point {
+ format!("{}: {}", point_to_string(point), reason)
+ } else {
+ reason
+ }
+}
+
+/// Serialize a unist position for humans.
+pub fn position_to_string(position: &Position) -> String {
+ format!(
+ "{}-{}",
+ point_to_string(&position.start),
+ point_to_string(&position.end)
+ )
+}
+
+/// Serialize a unist point for humans.
+pub fn point_to_string(point: &Point) -> String {
+ format!("{}:{}", point.line, point.column)
+}
+
+/// Visitor to fix SWC byte positions.
+///
+/// This assumes the byte position comes from an **unfixed** tree.
+///
+/// > 👉 **Note**: SWC byte positions are offset by one: they are `0` when they
+/// > are missing or incremented by `1` when valid.
+#[derive(Debug, Default, Clone)]
+pub struct RewriteContext<'a> {
+ pub prefix_len: usize,
+ pub stops: &'a [Stop],
+ pub location: Option<&'a Location>,
+}
+
+impl<'a> VisitMut for RewriteContext<'a> {
+ noop_visit_mut_type!();
+
+ // Rewrite spans.
+ fn visit_mut_span(&mut self, span: &mut Span) {
+ let mut result = DUMMY_SP;
+ let lo_rel = span.lo.0 as usize;
+ let hi_rel = span.hi.0 as usize;
+
+ if lo_rel > self.prefix_len && hi_rel > self.prefix_len {
+ if let Some(lo_abs) =
+ Location::relative_to_absolute(self.stops, lo_rel - 1 - self.prefix_len)
+ {
+ if let Some(hi_abs) =
+ Location::relative_to_absolute(self.stops, hi_rel - 1 - self.prefix_len)
+ {
+ result = Span {
+ lo: BytePos(lo_abs as u32 + 1),
+ hi: BytePos(hi_abs as u32 + 1),
+ ctxt: SyntaxContext::empty(),
+ };
+ }
+ }
+ }
+
+ *span = result;
+ }
+}
diff --git a/tests/test_utils/mod.rs b/tests/test_utils/mod.rs
index 339992c..99ded2f 100644
--- a/tests/test_utils/mod.rs
+++ b/tests/test_utils/mod.rs
@@ -1,6 +1,8 @@
pub mod hast;
pub mod jsx_rewrite;
+pub mod micromark_swc_utils;
pub mod swc;
+pub mod swc_utils;
pub mod to_document;
pub mod to_hast;
pub mod to_swc;
diff --git a/tests/test_utils/swc.rs b/tests/test_utils/swc.rs
index fb91a3b..78859b6 100644
--- a/tests/test_utils/swc.rs
+++ b/tests/test_utils/swc.rs
@@ -2,7 +2,10 @@ extern crate micromark;
extern crate swc_common;
extern crate swc_ecma_ast;
extern crate swc_ecma_parser;
-use micromark::{MdxExpressionKind, MdxSignal};
+use crate::test_utils::micromark_swc_utils::{
+ bytepos_to_point, prefix_error_with_point, RewriteContext,
+};
+use micromark::{mdast::Stop, unist::Point, Location, MdxExpressionKind, MdxSignal};
use swc_common::{
source_map::Pos, sync::Lrc, BytePos, FileName, FilePathMapping, SourceFile, SourceMap, Spanned,
};
@@ -11,10 +14,7 @@ use swc_ecma_codegen::{text_writer::JsWriter, Emitter};
use swc_ecma_parser::{
error::Error as SwcError, parse_file_as_expr, parse_file_as_module, EsConfig, Syntax,
};
-
-// To do:
-// Use lexer in the future:
-// <https://docs.rs/swc_ecma_parser/0.99.1/swc_ecma_parser/lexer/index.html>
+use swc_ecma_visit::VisitMutWith;
/// Lex ESM in MDX with SWC.
#[allow(dead_code)]
@@ -24,40 +24,42 @@ pub fn parse_esm(value: &str) -> MdxSignal {
let result = parse_file_as_module(&file, syntax, version, None, &mut errors);
match result {
- Err(error) => swc_error_to_signal(&error, value.len(), 0, "esm"),
+ Err(error) => swc_error_to_signal(&error, "esm", value.len(), 0),
Ok(tree) => {
if errors.is_empty() {
check_esm_ast(&tree)
} else {
- if errors.len() > 1 {
- println!("parse_esm: todo: multiple errors? {:?}", errors);
- }
- swc_error_to_signal(&errors[0], value.len(), 0, "esm")
+ swc_error_to_signal(&errors[0], "esm", value.len(), 0)
}
}
}
}
/// Parse ESM in MDX with SWC.
-/// To do: figure out how to fix positional info.
/// See `drop_span` in `swc_ecma_utils` for inspiration?
#[allow(dead_code)]
-pub fn parse_esm_to_tree(value: &str) -> Result<swc_ecma_ast::Module, String> {
+pub fn parse_esm_to_tree(
+ value: &str,
+ stops: &[Stop],
+ location: Option<&Location>,
+) -> Result<swc_ecma_ast::Module, String> {
let (file, syntax, version) = create_config(value.to_string());
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_string(&error)),
- Ok(module) => {
+ Err(error) => Err(swc_error_to_error(&error, "esm", &rewrite_context)),
+ Ok(mut module) => {
if errors.is_empty() {
+ module.visit_mut_with(&mut rewrite_context);
Ok(module)
} else {
- if errors.len() > 1 {
- println!("parse_esm_to_tree: todo: multiple errors? {:?}", errors);
- }
- Err(swc_error_to_string(&errors[0]))
+ Err(swc_error_to_error(&errors[0], "esm", &rewrite_context))
}
}
}
@@ -87,33 +89,31 @@ pub fn parse_expression(value: &str, kind: &MdxExpressionKind) -> MdxSignal {
let result = parse_file_as_expr(&file, syntax, version, None, &mut errors);
match result {
- Err(error) => swc_error_to_signal(&error, value.len(), prefix.len(), "expression"),
+ Err(error) => swc_error_to_signal(&error, "expression", value.len(), prefix.len()),
Ok(tree) => {
if errors.is_empty() {
- let place = fix_swc_position(tree.span().hi.to_usize(), prefix.len());
+ 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(place, value)
+ whitespace_and_comments(expression_end, value)
} else {
result
}
} else {
- if errors.len() > 1 {
- unreachable!("parse_expression: todo: multiple errors? {:?}", errors);
- }
- swc_error_to_signal(&errors[0], value.len(), prefix.len(), "expression")
+ swc_error_to_signal(&errors[0], "expression", value.len(), prefix.len())
}
}
}
}
/// Parse ESM in MDX with SWC.
-/// To do: figure out how to fix positional info.
/// See `drop_span` in `swc_ecma_utils` for inspiration?
#[allow(dead_code)]
pub fn parse_expression_to_tree(
value: &str,
kind: &MdxExpressionKind,
+ stops: &[Stop],
+ location: Option<&Location>,
) -> Result<Box<swc_ecma_ast::Expr>, String> {
// For attribute expression, a spread is needed, for which we have to prefix
// and suffix the input.
@@ -127,11 +127,21 @@ 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_string(&error)),
- Ok(expr) => {
+ Err(error) => Err(swc_error_to_error(&error, "expression", &rewrite_context)),
+ Ok(mut expr) => {
if errors.is_empty() {
+ // Fix positions.
+ expr.visit_mut_with(&mut rewrite_context);
+
+ let expr_bytepos = expr.span().lo;
+
if matches!(kind, MdxExpressionKind::AttributeExpression) {
let mut obj = None;
@@ -143,27 +153,37 @@ pub fn parse_expression_to_tree(
if let Some(mut obj) = obj {
if obj.props.len() > 1 {
- Err("Unexpected extra content in spread: only a single spread is supported".into())
+ 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("Unexpected prop in spread: only a spread is supported".into())
+ 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("Expected an object spread (`{...spread}`)".into())
+ Err(create_error_message(
+ "Expected an object spread (`{...spread}`)",
+ "expression",
+ bytepos_to_point(&expr_bytepos, location).as_ref(),
+ ))
}
} else {
Ok(expr)
}
} else {
- if errors.len() > 1 {
- println!(
- "parse_expression_to_tree: todo: multiple errors? {:?}",
- errors
- );
- }
- Err(swc_error_to_string(&errors[0]))
+ Err(swc_error_to_error(
+ &errors[0],
+ "expression",
+ &rewrite_context,
+ ))
}
}
}
@@ -203,10 +223,10 @@ fn check_esm_ast(tree: &Module) -> MdxSignal {
let node = &tree.body[index];
if !node.is_module_decl() {
- let place = fix_swc_position(node.span().hi.to_usize(), 0);
+ let relative = fix_swc_position(node.span().lo.to_usize(), 0);
return MdxSignal::Error(
"Unexpected statement in code: only import/exports are supported".to_string(),
- place,
+ relative,
);
}
@@ -248,24 +268,47 @@ fn check_expression_ast(tree: &Expr, kind: &MdxExpressionKind) -> MdxSignal {
/// * Else, yields `MdxSignal::Error`.
fn swc_error_to_signal(
error: &SwcError,
+ name: &str,
value_len: usize,
prefix_len: usize,
- name: &str,
) -> MdxSignal {
- let place = fix_swc_position(error.span().hi.to_usize(), prefix_len);
- let message = format!(
- "Could not parse {} with swc: {}",
- name,
- swc_error_to_string(error)
- );
+ let reason = create_error_reason(&swc_error_to_string(error), name);
+ let error_end = fix_swc_position(error.span().hi.to_usize(), prefix_len);
- if place >= value_len {
- MdxSignal::Eof(message)
+ if error_end >= value_len {
+ MdxSignal::Eof(reason)
} else {
- MdxSignal::Error(message, place)
+ MdxSignal::Error(
+ reason,
+ fix_swc_position(error.span().lo.to_usize(), prefix_len),
+ )
}
}
+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()
diff --git a/tests/test_utils/swc_utils.rs b/tests/test_utils/swc_utils.rs
new file mode 100644
index 0000000..1e1a526
--- /dev/null
+++ b/tests/test_utils/swc_utils.rs
@@ -0,0 +1,96 @@
+extern crate swc_common;
+extern crate swc_ecma_ast;
+
+use swc_common::DUMMY_SP;
+use swc_ecma_ast::{BinExpr, BinaryOp, Expr, Ident, MemberExpr, MemberProp};
+
+/// Generate an ident.
+///
+/// ```js
+/// a
+/// ```
+pub fn create_ident(sym: &str) -> Ident {
+ Ident {
+ sym: sym.into(),
+ optional: false,
+ span: DUMMY_SP,
+ }
+}
+
+/// Generate an ident expression.
+///
+/// ```js
+/// a
+/// ```
+pub fn create_ident_expression(sym: &str) -> Expr {
+ Expr::Ident(create_ident(sym))
+}
+
+/// Generate a binary expression.
+///
+/// ```js
+/// a + b + c
+/// a || b
+/// ```
+pub fn create_binary_expression(mut exprs: Vec<Expr>, op: BinaryOp) -> Expr {
+ exprs.reverse();
+
+ let mut left = None;
+
+ while let Some(right_expr) = exprs.pop() {
+ left = Some(if let Some(left_expr) = left {
+ Expr::Bin(BinExpr {
+ left: Box::new(left_expr),
+ right: Box::new(right_expr),
+ op,
+ span: swc_common::DUMMY_SP,
+ })
+ } else {
+ right_expr
+ });
+ }
+
+ left.expect("expected one or more expressions")
+}
+
+/// Generate a member expression.
+///
+/// ```js
+/// a.b
+/// a
+/// ```
+pub fn create_member_expression(name: &str) -> Expr {
+ let bytes = name.as_bytes();
+ let mut index = 0;
+ let mut start = 0;
+ let mut parts = vec![];
+
+ while index < bytes.len() {
+ if bytes[index] == b'.' {
+ parts.push(&name[start..index]);
+ start = index + 1;
+ }
+
+ index += 1;
+ }
+
+ if parts.len() > 1 {
+ let mut member = MemberExpr {
+ obj: Box::new(create_ident_expression(parts[0])),
+ prop: MemberProp::Ident(create_ident(parts[1])),
+ span: swc_common::DUMMY_SP,
+ };
+ let mut index = 2;
+ while index < parts.len() {
+ member = MemberExpr {
+ obj: Box::new(Expr::Member(member)),
+ prop: MemberProp::Ident(create_ident(parts[1])),
+ span: swc_common::DUMMY_SP,
+ };
+ index += 1;
+ }
+ Expr::Member(member)
+ } else {
+ create_ident_expression(name)
+ }
+}
diff --git a/tests/test_utils/to_document.rs b/tests/test_utils/to_document.rs
index ded028a..938df1b 100644
--- a/tests/test_utils/to_document.rs
+++ b/tests/test_utils/to_document.rs
@@ -1,6 +1,12 @@
-extern crate swc_common;
extern crate swc_ecma_ast;
-use crate::test_utils::to_swc::Program;
+use crate::test_utils::{
+ micromark_swc_utils::{bytepos_to_point, prefix_error_with_point, span_to_position},
+ to_swc::Program,
+};
+use micromark::{
+ unist::{Point, Position},
+ Location,
+};
/// JSX runtimes.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
@@ -60,7 +66,11 @@ impl Default for Options {
}
#[allow(dead_code)]
-pub fn to_document(mut program: Program, options: &Options) -> Result<Program, String> {
+pub fn to_document(
+ mut program: Program,
+ options: &Options,
+ location: Option<&Location>,
+) -> Result<Program, String> {
// New body children.
let mut replacements = vec![];
@@ -158,8 +168,8 @@ pub fn to_document(mut program: Program, options: &Options) -> Result<Program, S
// is.
let mut input = program.module.body.split_off(0);
input.reverse();
- // To do: place position in this.
let mut layout = false;
+ let mut layout_position = None;
let content = true;
while let Some(module_item) = input.pop() {
@@ -174,13 +184,15 @@ pub fn to_document(mut program: Program, options: &Options) -> Result<Program, S
swc_ecma_ast::ModuleItem::ModuleDecl(swc_ecma_ast::ModuleDecl::ExportDefaultDecl(
decl,
)) => {
- // To do: use positional info.
if layout {
- return Err("Cannot specify multiple layouts".into());
+ return Err(create_double_layout_message(
+ bytepos_to_point(&decl.span.lo, location).as_ref(),
+ layout_position.as_ref(),
+ ));
}
- // To do: set positional info.
layout = true;
+ layout_position = span_to_position(&decl.span, location);
match decl.decl {
swc_ecma_ast::DefaultDecl::Class(cls) => {
replacements.push(create_layout_decl(swc_ecma_ast::Expr::Class(cls)))
@@ -190,22 +202,26 @@ pub fn to_document(mut program: Program, options: &Options) -> Result<Program, S
}
swc_ecma_ast::DefaultDecl::TsInterfaceDecl(_) => {
return Err(
- "Cannot use TypeScript interface declarations as default export in MDX files. The default export is reserved for a layout, which must be a component"
- .into(),
- )
+ prefix_error_with_point(
+ "Cannot use TypeScript interface declarations as default export in MDX files. The default export is reserved for a layout, which must be a component".into(),
+ bytepos_to_point(&decl.span.lo, location).as_ref()
+ )
+ );
}
}
}
swc_ecma_ast::ModuleItem::ModuleDecl(swc_ecma_ast::ModuleDecl::ExportDefaultExpr(
expr,
)) => {
- // To do: use positional info.
if layout {
- return Err("Cannot specify multiple layouts".into());
+ return Err(create_double_layout_message(
+ bytepos_to_point(&expr.span.lo, location).as_ref(),
+ layout_position.as_ref(),
+ ));
}
- // To do: set positional info.
layout = true;
+ layout_position = span_to_position(&expr.span, location);
replacements.push(create_layout_decl(*expr.expr));
}
// ```js
@@ -239,12 +255,14 @@ pub fn to_document(mut program: Program, options: &Options) -> Result<Program, S
// Looks like some TC39 proposal. Ignore for now
// and only do things if this is an ID.
if let swc_ecma_ast::ModuleExportName::Ident(ident) = &named.orig {
- // To do: use positional info.
if layout {
- return Err("Cannot specify multiple layouts".into());
+ return Err(create_double_layout_message(
+ bytepos_to_point(&ident.span.lo, location).as_ref(),
+ layout_position.as_ref(),
+ ));
}
- // To do: set positional info.
layout = true;
+ layout_position = span_to_position(&ident.span, location);
take = true;
id = Some(ident.clone());
}
@@ -623,3 +641,18 @@ fn create_layout_decl(expr: swc_ecma_ast::Expr) -> swc_ecma_ast::ModuleItem {
},
))))
}
+
+/// Create an error message about multiple layouts.
+fn create_double_layout_message(at: Option<&Point>, previous: Option<&Position>) -> String {
+ prefix_error_with_point(
+ format!(
+ "Cannot specify multiple layouts{}",
+ if let Some(previous) = previous {
+ format!(" (previous: {:?})", previous)
+ } else {
+ "".into()
+ }
+ ),
+ at,
+ )
+}
diff --git a/tests/test_utils/to_hast.rs b/tests/test_utils/to_hast.rs
index 0716daa..4907e23 100644
--- a/tests/test_utils/to_hast.rs
+++ b/tests/test_utils/to_hast.rs
@@ -827,10 +827,23 @@ fn transform_math(_state: &mut State, _node: &mdast::Node, math: &mdast::Math) -
/// [`MdxFlowExpression`][mdast::MdxFlowExpression],[`MdxTextExpression`][mdast::MdxTextExpression].
fn transform_mdx_expression(_state: &mut State, node: &mdast::Node) -> Result {
- Result::Node(hast::Node::MdxExpression(hast::MdxExpression {
- value: node.to_string(),
- position: node.position().cloned(),
- }))
+ match node {
+ mdast::Node::MdxFlowExpression(node) => {
+ Result::Node(hast::Node::MdxExpression(hast::MdxExpression {
+ value: node.value.clone(),
+ position: node.position.clone(),
+ stops: node.stops.clone(),
+ }))
+ }
+ mdast::Node::MdxTextExpression(node) => {
+ Result::Node(hast::Node::MdxExpression(hast::MdxExpression {
+ value: node.value.clone(),
+ position: node.position.clone(),
+ stops: node.stops.clone(),
+ }))
+ }
+ _ => unreachable!("expected expression"),
+ }
}
/// [`MdxJsxFlowElement`][mdast::MdxJsxFlowElement],[`MdxJsxTextElement`][mdast::MdxJsxTextElement].
@@ -858,6 +871,7 @@ fn transform_mdxjs_esm(
Result::Node(hast::Node::MdxjsEsm(hast::MdxjsEsm {
value: mdxjs_esm.value.clone(),
position: mdxjs_esm.position.clone(),
+ stops: mdxjs_esm.stops.clone(),
}))
}
diff --git a/tests/test_utils/to_swc.rs b/tests/test_utils/to_swc.rs
index 6c7312b..02de514 100644
--- a/tests/test_utils/to_swc.rs
+++ b/tests/test_utils/to_swc.rs
@@ -1,13 +1,18 @@
extern crate swc_common;
extern crate swc_ecma_ast;
-use crate::test_utils::hast;
-use crate::test_utils::swc::{parse_esm_to_tree, parse_expression_to_tree};
+use crate::test_utils::{
+ hast,
+ micromark_swc_utils::position_to_span,
+ swc::{parse_esm_to_tree, parse_expression_to_tree},
+ swc_utils::create_ident,
+};
use core::str;
-use micromark::{unist::Position, MdxExpressionKind};
+use micromark::{Location, MdxExpressionKind};
/// Result.
#[derive(Debug, PartialEq, Eq)]
pub struct Program {
+ pub path: Option<String>,
/// JS AST.
pub module: swc_ecma_ast::Module,
/// Comments relating to AST.
@@ -22,7 +27,7 @@ enum Space {
}
#[derive(Debug)]
-struct Context {
+struct Context<'a> {
/// Whether we’re in HTML or SVG.
///
/// Not used yet, likely useful in the future.
@@ -31,22 +36,22 @@ struct Context {
comments: Vec<swc_common::comments::Comment>,
/// Declarations and stuff.
esm: Vec<swc_ecma_ast::ModuleItem>,
-}
-
-impl Context {
- /// Create a new context.
- fn new() -> Context {
- Context {
- space: Space::Html,
- comments: vec![],
- esm: vec![],
- }
- }
+ /// Optional way to turn relative positions into points.
+ location: Option<&'a Location>,
}
#[allow(dead_code)]
-pub fn to_swc(tree: &hast::Node) -> Result<Program, String> {
- let mut context = Context::new();
+pub fn to_swc(
+ tree: &hast::Node,
+ path: Option<String>,
+ location: Option<&Location>,
+) -> Result<Program, String> {
+ let mut context = Context {
+ space: Space::Html,
+ comments: vec![],
+ esm: vec![],
+ location,
+ };
let expr = match one(&mut context, tree)? {
Some(swc_ecma_ast::JSXElementChild::JSXFragment(x)) => {
Some(swc_ecma_ast::Expr::JSXFragment(x))
@@ -81,6 +86,7 @@ pub fn to_swc(tree: &hast::Node) -> Result<Program, String> {
}
Ok(Program {
+ path,
module,
comments: context.comments,
})
@@ -260,12 +266,14 @@ fn transform_mdx_jsx_element(
},
)))
}
- Some(hast::AttributeValue::Expression(value)) => {
+ Some(hast::AttributeValue::Expression(value, stops)) => {
Some(swc_ecma_ast::JSXAttrValue::JSXExprContainer(
swc_ecma_ast::JSXExprContainer {
expr: swc_ecma_ast::JSXExpr::Expr(parse_expression_to_tree(
value,
&MdxExpressionKind::AttributeValueExpression,
+ stops,
+ context.location,
)?),
span: swc_common::DUMMY_SP,
},
@@ -280,9 +288,13 @@ fn transform_mdx_jsx_element(
value,
})
}
- hast::AttributeContent::Expression(value) => {
- let expr =
- parse_expression_to_tree(value, &MdxExpressionKind::AttributeExpression)?;
+ hast::AttributeContent::Expression(value, stops) => {
+ let expr = parse_expression_to_tree(
+ value,
+ &MdxExpressionKind::AttributeExpression,
+ stops,
+ context.location,
+ )?;
swc_ecma_ast::JSXAttrOrSpread::SpreadElement(swc_ecma_ast::SpreadElement {
dot3_token: swc_common::DUMMY_SP,
expr,
@@ -303,7 +315,7 @@ fn transform_mdx_jsx_element(
/// [`MdxExpression`][hast::MdxExpression].
fn transform_mdx_expression(
- _context: &mut Context,
+ context: &mut Context,
node: &hast::Node,
expression: &hast::MdxExpression,
) -> Result<Option<swc_ecma_ast::JSXElementChild>, String> {
@@ -312,6 +324,8 @@ fn transform_mdx_expression(
expr: swc_ecma_ast::JSXExpr::Expr(parse_expression_to_tree(
&expression.value,
&MdxExpressionKind::Expression,
+ &expression.stops,
+ context.location,
)?),
span: position_to_span(node.position()),
},
@@ -324,7 +338,7 @@ fn transform_mdxjs_esm(
_node: &hast::Node,
esm: &hast::MdxjsEsm,
) -> Result<Option<swc_ecma_ast::JSXElementChild>, String> {
- let mut module = parse_esm_to_tree(&esm.value)?;
+ let mut module = parse_esm_to_tree(&esm.value, &esm.stops, context.location)?;
let mut index = 0;
// To do: check that identifiers are not duplicated across esm blocks.
@@ -455,15 +469,6 @@ fn create_fragment(
}
}
-/// Create an ident.
-fn create_ident(sym: &str) -> swc_ecma_ast::Ident {
- swc_ecma_ast::Ident {
- sym: sym.into(),
- optional: false,
- span: swc_common::DUMMY_SP,
- }
-}
-
/// Create a JSX element name.
fn create_jsx_name(name: &str) -> swc_ecma_ast::JSXElementName {
match parse_jsx_name(name) {
@@ -515,15 +520,6 @@ fn create_jsx_attr_name(name: &str) -> swc_ecma_ast::JSXAttrName {
}
}
-/// Turn a unist positions into an SWC span.
-fn position_to_span(position: Option<&Position>) -> swc_common::Span {
- position.map_or(swc_common::DUMMY_SP, |d| swc_common::Span {
- lo: swc_common::BytePos(d.start.offset as u32),
- hi: swc_common::BytePos(d.end.offset as u32),
- ctxt: swc_common::SyntaxContext::empty(),
- })
-}
-
fn inter_element_whitespace(value: &str) -> bool {
let bytes = value.as_bytes();
let mut index = 0;
diff --git a/tests/xxx_document.rs b/tests/xxx_document.rs
index d5c8eef..7e43b1c 100644
--- a/tests/xxx_document.rs
+++ b/tests/xxx_document.rs
@@ -3,7 +3,7 @@ extern crate swc_common;
extern crate swc_ecma_ast;
extern crate swc_ecma_codegen;
mod test_utils;
-use micromark::{micromark_to_mdast, Constructs, ParseOptions};
+use micromark::{micromark_to_mdast, Constructs, Location, ParseOptions};
use pretty_assertions::assert_eq;
use test_utils::{
swc::{parse_esm, parse_expression, serialize},
@@ -13,6 +13,7 @@ use test_utils::{
};
fn from_markdown(value: &str) -> Result<String, String> {
+ let location = Location::new(value.as_bytes());
let mdast = micromark_to_mdast(
value,
&ParseOptions {
@@ -23,7 +24,8 @@ fn from_markdown(value: &str) -> Result<String, String> {
},
)?;
let hast = to_hast(&mdast);
- let program = to_document(to_swc(&hast)?, &DocumentOptions::default())?;
+ let swc_tree = to_swc(&hast, None, Some(&location))?;
+ let program = to_document(swc_tree, &DocumentOptions::default(), Some(&location))?;
let value = serialize(&program.module);
Ok(value)
}
@@ -156,8 +158,6 @@ export default MDXContent;
"should support a named export w/o source, w/o a specifiers",
);
- // ...........
-
assert_eq!(
from_markdown("export {a, b as default} from 'c'")?,
"export { a } from 'c';
@@ -201,5 +201,13 @@ export default MDXContent;
"should support a named export w/ source, w/o a specifiers",
);
+ assert_eq!(
+ from_markdown("export default a = 1\n\nexport default b = 2")
+ .err()
+ .unwrap(),
+ "3:1: Cannot specify multiple layouts (previous: 1:1-1:21 (0-20))",
+ "should crash on a comment spread"
+ );
+
Ok(())
}
diff --git a/tests/xxx_hast.rs b/tests/xxx_hast.rs
index 886bcee..b0856a2 100644
--- a/tests/xxx_hast.rs
+++ b/tests/xxx_hast.rs
@@ -1137,10 +1137,12 @@ fn hast() {
to_hast(&mdast::Node::MdxFlowExpression(mdast::MdxFlowExpression {
value: "a".into(),
position: None,
+ stops: vec![]
})),
hast::Node::MdxExpression(hast::MdxExpression {
value: "a".into(),
- position: None
+ position: None,
+ stops: vec![]
}),
"should support an `MdxFlowExpression`",
);
@@ -1149,10 +1151,12 @@ fn hast() {
to_hast(&mdast::Node::MdxTextExpression(mdast::MdxTextExpression {
value: "a".into(),
position: None,
+ stops: vec![]
})),
hast::Node::MdxExpression(hast::MdxExpression {
value: "a".into(),
- position: None
+ position: None,
+ stops: vec![]
}),
"should support an `MdxTextExpression`",
);
@@ -1193,10 +1197,12 @@ fn hast() {
to_hast(&mdast::Node::MdxjsEsm(mdast::MdxjsEsm {
value: "a".into(),
position: None,
+ stops: vec![]
})),
hast::Node::MdxjsEsm(hast::MdxjsEsm {
value: "a".into(),
- position: None
+ position: None,
+ stops: vec![]
}),
"should support an `MdxjsEsm`",
);
diff --git a/tests/xxx_jsx_rewrite.rs b/tests/xxx_jsx_rewrite.rs
index 7a1c379..c383f13 100644
--- a/tests/xxx_jsx_rewrite.rs
+++ b/tests/xxx_jsx_rewrite.rs
@@ -3,7 +3,7 @@ extern crate swc_common;
extern crate swc_ecma_ast;
extern crate swc_ecma_codegen;
mod test_utils;
-use micromark::{micromark_to_mdast, Constructs, ParseOptions};
+use micromark::{micromark_to_mdast, Constructs, Location, ParseOptions};
use pretty_assertions::assert_eq;
use test_utils::{
jsx_rewrite::{jsx_rewrite, Options as RewriteOptions},
@@ -14,6 +14,7 @@ use test_utils::{
};
fn from_markdown(value: &str, options: &RewriteOptions) -> Result<String, String> {
+ let location = Location::new(value.as_bytes());
let mdast = micromark_to_mdast(
value,
&ParseOptions {
@@ -24,8 +25,9 @@ fn from_markdown(value: &str, options: &RewriteOptions) -> Result<String, String
},
)?;
let hast = to_hast(&mdast);
- let program = to_document(to_swc(&hast)?, &DocumentOptions::default())?;
- let program = jsx_rewrite(program, options);
+ let swc_tree = to_swc(&hast, Some("example.mdx".into()), Some(&location))?;
+ let program = to_document(swc_tree, &DocumentOptions::default(), Some(&location))?;
+ let program = jsx_rewrite(program, options, Some(&location));
let value = serialize(&program.module);
Ok(value)
}
@@ -351,5 +353,29 @@ function _missingMdxReference(id, component) {
"should support providing components with JSX identifiers that are not JS identifiers in locally defined components",
);
+ assert_eq!(
+ from_markdown("# <Hi />", &RewriteOptions {
+ development: true,
+ ..Default::default()
+ })?,
+ "function _createMdxContent(props) {
+ const _components = Object.assign({
+ h1: \"h1\"
+ }, props.components), { Hi } = _components;
+ if (!Hi) _missingMdxReference(\"Hi\", true, \"1:3-1:9\");
+ return <_components.h1 ><Hi /></_components.h1>;
+}
+function MDXContent(props = {}) {
+ const { wrapper: MDXLayout } = props.components || {};
+ return MDXLayout ? <MDXLayout {...props}><_createMdxContent {...props}/></MDXLayout> : _createMdxContent(props);
+}
+export default MDXContent;
+function _missingMdxReference(id, component, place) {
+ throw new Error(\"Expected \" + (component ? \"component\" : \"object\") + \" `\" + id + \"` to be defined: you likely forgot to import, pass, or provide it.\" + (place ? \"\\nIt’s referenced in your code at `\" + place + \"` in `example.mdx`\" : \"\"));
+}
+",
+ "should create missing reference helpers w/o positional info in `development` mode",
+ );
+
Ok(())
}
diff --git a/tests/xxx_swc.rs b/tests/xxx_swc.rs
index 26814cf..68a141d 100644
--- a/tests/xxx_swc.rs
+++ b/tests/xxx_swc.rs
@@ -12,14 +12,19 @@ use test_utils::{
#[test]
fn swc() -> Result<(), String> {
- let comment_ast = to_swc(&hast::Node::Comment(hast::Comment {
- value: "a".into(),
- position: None,
- }))?;
+ let comment_ast = to_swc(
+ &hast::Node::Comment(hast::Comment {
+ value: "a".into(),
+ position: None,
+ }),
+ None,
+ None,
+ )?;
assert_eq!(
comment_ast,
Program {
+ path: None,
module: swc_ecma_ast::Module {
shebang: None,
body: vec![swc_ecma_ast::ModuleItem::Stmt(swc_ecma_ast::Stmt::Expr(
@@ -66,19 +71,24 @@ fn swc() -> Result<(), String> {
"should support a `Comment` (serialize)",
);
- let element_ast = to_swc(&hast::Node::Element(hast::Element {
- tag_name: "a".into(),
- properties: vec![(
- "className".into(),
- hast::PropertyValue::SpaceSeparated(vec!["b".into()]),
- )],
- children: vec![],
- position: None,
- }))?;
+ let element_ast = to_swc(
+ &hast::Node::Element(hast::Element {
+ tag_name: "a".into(),
+ properties: vec![(
+ "className".into(),
+ hast::PropertyValue::SpaceSeparated(vec!["b".into()]),
+ )],
+ children: vec![],
+ position: None,
+ }),
+ None,
+ None,
+ )?;
assert_eq!(
element_ast,
Program {
+ path: None,
module: swc_ecma_ast::Module {
shebang: None,
body: vec![swc_ecma_ast::ModuleItem::Stmt(swc_ecma_ast::Stmt::Expr(
@@ -139,15 +149,19 @@ fn swc() -> Result<(), String> {
assert_eq!(
serialize(
- &to_swc(&hast::Node::Element(hast::Element {
- tag_name: "a".into(),
- properties: vec![],
- children: vec![hast::Node::Text(hast::Text {
- value: "a".into(),
+ &to_swc(
+ &hast::Node::Element(hast::Element {
+ tag_name: "a".into(),
+ properties: vec![],
+ children: vec![hast::Node::Text(hast::Text {
+ value: "a".into(),
+ position: None,
+ })],
position: None,
- })],
- position: None,
- }))?
+ }),
+ None,
+ None
+ )?
.module
),
"<a >{\"a\"}</a>;\n",
@@ -156,12 +170,16 @@ fn swc() -> Result<(), String> {
assert_eq!(
serialize(
- &to_swc(&hast::Node::Element(hast::Element {
- tag_name: "a".into(),
- properties: vec![("b".into(), hast::PropertyValue::String("c".into()),)],
- children: vec![],
- position: None,
- }))?
+ &to_swc(
+ &hast::Node::Element(hast::Element {
+ tag_name: "a".into(),
+ properties: vec![("b".into(), hast::PropertyValue::String("c".into()),)],
+ children: vec![],
+ position: None,
+ }),
+ None,
+ None
+ )?
.module
),
"<a b=\"c\"/>;\n",
@@ -170,12 +188,16 @@ fn swc() -> Result<(), String> {
assert_eq!(
serialize(
- &to_swc(&hast::Node::Element(hast::Element {
- tag_name: "a".into(),
- properties: vec![("b".into(), hast::PropertyValue::Boolean(true),)],
- children: vec![],
- position: None,
- }))?
+ &to_swc(
+ &hast::Node::Element(hast::Element {
+ tag_name: "a".into(),
+ properties: vec![("b".into(), hast::PropertyValue::Boolean(true),)],
+ children: vec![],
+ position: None,
+ }),
+ None,
+ None
+ )?
.module
),
"<a b/>;\n",
@@ -184,12 +206,16 @@ fn swc() -> Result<(), String> {
assert_eq!(
serialize(
- &to_swc(&hast::Node::Element(hast::Element {
- tag_name: "a".into(),
- properties: vec![("b".into(), hast::PropertyValue::Boolean(false),)],
- children: vec![],
- position: None,
- }))?
+ &to_swc(
+ &hast::Node::Element(hast::Element {
+ tag_name: "a".into(),
+ properties: vec![("b".into(), hast::PropertyValue::Boolean(false),)],
+ children: vec![],
+ position: None,
+ }),
+ None,
+ None
+ )?
.module
),
"<a />;\n",
@@ -198,15 +224,19 @@ fn swc() -> Result<(), String> {
assert_eq!(
serialize(
- &to_swc(&hast::Node::Element(hast::Element {
- tag_name: "a".into(),
- properties: vec![(
- "b".into(),
- hast::PropertyValue::CommaSeparated(vec!["c".into(), "d".into()]),
- )],
- children: vec![],
- position: None,
- }))?
+ &to_swc(
+ &hast::Node::Element(hast::Element {
+ tag_name: "a".into(),
+ properties: vec![(
+ "b".into(),
+ hast::PropertyValue::CommaSeparated(vec!["c".into(), "d".into()]),
+ )],
+ children: vec![],
+ position: None,
+ }),
+ None,
+ None
+ )?
.module
),
"<a b=\"c, d\"/>;\n",
@@ -215,16 +245,20 @@ fn swc() -> Result<(), String> {
assert_eq!(
serialize(
- &to_swc(&hast::Node::Element(hast::Element {
- tag_name: "a".into(),
- properties: vec![
- ("data123".into(), hast::PropertyValue::Boolean(true),),
- ("dataFoo".into(), hast::PropertyValue::Boolean(true),),
- ("dataBAR".into(), hast::PropertyValue::Boolean(true),)
- ],
- children: vec![],
- position: None,
- }))?
+ &to_swc(
+ &hast::Node::Element(hast::Element {
+ tag_name: "a".into(),
+ properties: vec![
+ ("data123".into(), hast::PropertyValue::Boolean(true),),
+ ("dataFoo".into(), hast::PropertyValue::Boolean(true),),
+ ("dataBAR".into(), hast::PropertyValue::Boolean(true),)
+ ],
+ children: vec![],
+ position: None,
+ }),
+ None,
+ None
+ )?
.module
),
"<a data-123 data-foo data-b-a-r/>;\n",
@@ -233,32 +267,41 @@ fn swc() -> Result<(), String> {
assert_eq!(
serialize(
- &to_swc(&hast::Node::Element(hast::Element {
- tag_name: "a".into(),
- properties: vec![
- ("role".into(), hast::PropertyValue::Boolean(true),),
- ("ariaValueNow".into(), hast::PropertyValue::Boolean(true),),
- ("ariaDescribedBy".into(), hast::PropertyValue::Boolean(true),)
- ],
- children: vec![],
- position: None,
- }))?
+ &to_swc(
+ &hast::Node::Element(hast::Element {
+ tag_name: "a".into(),
+ properties: vec![
+ ("role".into(), hast::PropertyValue::Boolean(true),),
+ ("ariaValueNow".into(), hast::PropertyValue::Boolean(true),),
+ ("ariaDescribedBy".into(), hast::PropertyValue::Boolean(true),)
+ ],
+ children: vec![],
+ position: None,
+ }),
+ None,
+ None
+ )?
.module
),
"<a role aria-valuenow aria-describedby/>;\n",
"should support an `Element` w/ aria attributes",
);
- let mdx_element_ast = to_swc(&hast::Node::MdxJsxElement(hast::MdxJsxElement {
- name: None,
- attributes: vec![],
- children: vec![],
- position: None,
- }))?;
+ let mdx_element_ast = to_swc(
+ &hast::Node::MdxJsxElement(hast::MdxJsxElement {
+ name: None,
+ attributes: vec![],
+ children: vec![],
+ position: None,
+ }),
+ None,
+ None,
+ )?;
assert_eq!(
mdx_element_ast,
Program {
+ path: None,
module: swc_ecma_ast::Module {
shebang: None,
body: vec![swc_ecma_ast::ModuleItem::Stmt(swc_ecma_ast::Stmt::Expr(
@@ -293,12 +336,16 @@ fn swc() -> Result<(), String> {
assert_eq!(
serialize(
- &to_swc(&hast::Node::MdxJsxElement(hast::MdxJsxElement {
- name: Some("a".into()),
- attributes: vec![],
- children: vec![],
- position: None,
- }))?
+ &to_swc(
+ &hast::Node::MdxJsxElement(hast::MdxJsxElement {
+ name: Some("a".into()),
+ attributes: vec![],
+ children: vec![],
+ position: None,
+ }),
+ None,
+ None
+ )?
.module
),
"<a />;\n",
@@ -307,12 +354,16 @@ fn swc() -> Result<(), String> {
assert_eq!(
serialize(
- &to_swc(&hast::Node::MdxJsxElement(hast::MdxJsxElement {
- name: Some("a:b".into()),
- attributes: vec![],
- children: vec![],
- position: None,
- }))?
+ &to_swc(
+ &hast::Node::MdxJsxElement(hast::MdxJsxElement {
+ name: Some("a:b".into()),
+ attributes: vec![],
+ children: vec![],
+ position: None,
+ }),
+ None,
+ None
+ )?
.module
),
"<a:b />;\n",
@@ -321,12 +372,16 @@ fn swc() -> Result<(), String> {
assert_eq!(
serialize(
- &to_swc(&hast::Node::MdxJsxElement(hast::MdxJsxElement {
- name: Some("a.b.c".into()),
- attributes: vec![],
- children: vec![],
- position: None,
- }))?
+ &to_swc(
+ &hast::Node::MdxJsxElement(hast::MdxJsxElement {
+ name: Some("a.b.c".into()),
+ attributes: vec![],
+ children: vec![],
+ position: None,
+ }),
+ None,
+ None
+ )?
.module
),
"<a.b.c />;\n",
@@ -335,15 +390,19 @@ fn swc() -> Result<(), String> {
assert_eq!(
serialize(
- &to_swc(&hast::Node::MdxJsxElement(hast::MdxJsxElement {
- name: Some("a".into()),
- attributes: vec![],
- children: vec![hast::Node::Text(hast::Text {
- value: "b".into(),
+ &to_swc(
+ &hast::Node::MdxJsxElement(hast::MdxJsxElement {
+ name: Some("a".into()),
+ attributes: vec![],
+ children: vec![hast::Node::Text(hast::Text {
+ value: "b".into(),
+ position: None,
+ })],
position: None,
- })],
- position: None,
- }))?
+ }),
+ None,
+ None
+ )?
.module
),
"<a >{\"b\"}</a>;\n",
@@ -352,15 +411,19 @@ fn swc() -> Result<(), String> {
assert_eq!(
serialize(
- &to_swc(&hast::Node::MdxJsxElement(hast::MdxJsxElement {
- name: Some("a".into()),
- attributes: vec![hast::AttributeContent::Property(hast::MdxJsxAttribute {
- name: "b".into(),
- value: None
- })],
- children: vec![],
- position: None,
- }))?
+ &to_swc(
+ &hast::Node::MdxJsxElement(hast::MdxJsxElement {
+ name: Some("a".into()),
+ attributes: vec![hast::AttributeContent::Property(hast::MdxJsxAttribute {
+ name: "b".into(),
+ value: None
+ })],
+ children: vec![],
+ position: None,
+ }),
+ None,
+ None
+ )?
.module
),
"<a b/>;\n",
@@ -369,15 +432,19 @@ fn swc() -> Result<(), String> {
assert_eq!(
serialize(
- &to_swc(&hast::Node::MdxJsxElement(hast::MdxJsxElement {
- name: Some("a".into()),
- attributes: vec![hast::AttributeContent::Property(hast::MdxJsxAttribute {
- name: "b".into(),
- value: Some(hast::AttributeValue::Literal("c".into()))
- })],
- children: vec![],
- position: None,
- }))?
+ &to_swc(
+ &hast::Node::MdxJsxElement(hast::MdxJsxElement {
+ name: Some("a".into()),
+ attributes: vec![hast::AttributeContent::Property(hast::MdxJsxAttribute {
+ name: "b".into(),
+ value: Some(hast::AttributeValue::Literal("c".into()))
+ })],
+ children: vec![],
+ position: None,
+ }),
+ None,
+ None
+ )?
.module
),
"<a b=\"c\"/>;\n",
@@ -386,15 +453,19 @@ fn swc() -> Result<(), String> {
assert_eq!(
serialize(
- &to_swc(&hast::Node::MdxJsxElement(hast::MdxJsxElement {
- name: Some("a".into()),
- attributes: vec![hast::AttributeContent::Property(hast::MdxJsxAttribute {
- name: "b".into(),
- value: Some(hast::AttributeValue::Expression("c".into()))
- })],
- children: vec![],
- position: None,
- }))?
+ &to_swc(
+ &hast::Node::MdxJsxElement(hast::MdxJsxElement {
+ name: Some("a".into()),
+ attributes: vec![hast::AttributeContent::Property(hast::MdxJsxAttribute {
+ name: "b".into(),
+ value: Some(hast::AttributeValue::Expression("c".into(), vec![]))
+ })],
+ children: vec![],
+ position: None,
+ }),
+ None,
+ None
+ )?
.module
),
"<a b={c}/>;\n",
@@ -403,26 +474,36 @@ fn swc() -> Result<(), String> {
assert_eq!(
serialize(
- &to_swc(&hast::Node::MdxJsxElement(hast::MdxJsxElement {
- name: Some("a".into()),
- attributes: vec![hast::AttributeContent::Expression("...c".into())],
- children: vec![],
- position: None,
- }))?
+ &to_swc(
+ &hast::Node::MdxJsxElement(hast::MdxJsxElement {
+ name: Some("a".into()),
+ attributes: vec![hast::AttributeContent::Expression("...c".into(), vec![])],
+ children: vec![],
+ position: None,
+ }),
+ None,
+ None
+ )?
.module
),
"<a {...c}/>;\n",
"should support an `MdxElement` (element, expression attribute)",
);
- let mdx_expression_ast = to_swc(&hast::Node::MdxExpression(hast::MdxExpression {
- value: "a".into(),
- position: None,
- }))?;
+ let mdx_expression_ast = to_swc(
+ &hast::Node::MdxExpression(hast::MdxExpression {
+ value: "a".into(),
+ position: None,
+ stops: vec![],
+ }),
+ None,
+ None,
+ )?;
assert_eq!(
mdx_expression_ast,
Program {
+ path: None,
module: swc_ecma_ast::Module {
shebang: None,
body: vec![swc_ecma_ast::ModuleItem::Stmt(swc_ecma_ast::Stmt::Expr(
@@ -439,13 +520,8 @@ fn swc() -> Result<(), String> {
swc_ecma_ast::JSXExprContainer {
expr: swc_ecma_ast::JSXExpr::Expr(Box::new(
swc_ecma_ast::Expr::Ident(swc_ecma_ast::Ident {
- // To do: fix positions.
- span: swc_common::Span {
- lo: swc_common::BytePos(1),
- hi: swc_common::BytePos(2),
- ctxt: swc_common::SyntaxContext::empty(),
- },
sym: "a".into(),
+ span: swc_common::DUMMY_SP,
optional: false,
})
)),
@@ -471,14 +547,20 @@ fn swc() -> Result<(), String> {
"should support an `MdxExpression` (serialize)",
);
- let mdxjs_esm_ast = to_swc(&hast::Node::MdxjsEsm(hast::MdxjsEsm {
- value: "import a from 'b'".into(),
- position: None,
- }))?;
+ let mdxjs_esm_ast = to_swc(
+ &hast::Node::MdxjsEsm(hast::MdxjsEsm {
+ value: "import a from 'b'".into(),
+ position: None,
+ stops: vec![],
+ }),
+ None,
+ None,
+ )?;
assert_eq!(
mdxjs_esm_ast,
Program {
+ path: None,
module: swc_ecma_ast::Module {
shebang: None,
body: vec![swc_ecma_ast::ModuleItem::ModuleDecl(
@@ -488,39 +570,19 @@ fn swc() -> Result<(), String> {
local: swc_ecma_ast::Ident {
sym: "a".into(),
optional: false,
- // To do: fix positions.
- span: swc_common::Span {
- lo: swc_common::BytePos(8),
- hi: swc_common::BytePos(9),
- ctxt: swc_common::SyntaxContext::empty(),
- },
- },
- // To do: fix positions.
- span: swc_common::Span {
- lo: swc_common::BytePos(8),
- hi: swc_common::BytePos(9),
- ctxt: swc_common::SyntaxContext::empty(),
+ span: swc_common::DUMMY_SP,
},
+ span: swc_common::DUMMY_SP,
}
)],
src: Box::new(swc_ecma_ast::Str {
value: "b".into(),
- // To do: fix positions.
- span: swc_common::Span {
- lo: swc_common::BytePos(15),
- hi: swc_common::BytePos(18),
- ctxt: swc_common::SyntaxContext::empty(),
- },
+ span: swc_common::DUMMY_SP,
raw: Some("\'b\'".into()),
}),
type_only: false,
asserts: None,
- // To do: fix positions.
- span: swc_common::Span {
- lo: swc_common::BytePos(1),
- hi: swc_common::BytePos(18),
- ctxt: swc_common::SyntaxContext::empty(),
- },
+ span: swc_common::DUMMY_SP,
})
)],
span: swc_common::DUMMY_SP,
@@ -536,17 +598,22 @@ fn swc() -> Result<(), String> {
"should support an `MdxjsEsm` (serialize)",
);
- let root_ast = to_swc(&hast::Node::Root(hast::Root {
- children: vec![hast::Node::Text(hast::Text {
- value: "a".into(),
+ let root_ast = to_swc(
+ &hast::Node::Root(hast::Root {
+ children: vec![hast::Node::Text(hast::Text {
+ value: "a".into(),
+ position: None,
+ })],
position: None,
- })],
- position: None,
- }))?;
+ }),
+ None,
+ None,
+ )?;
assert_eq!(
root_ast,
Program {
+ path: None,
module: swc_ecma_ast::Module {
shebang: None,
body: vec![swc_ecma_ast::ModuleItem::Stmt(swc_ecma_ast::Stmt::Expr(
@@ -592,14 +659,19 @@ fn swc() -> Result<(), String> {
"should support a `Root` (serialize)",
);
- let text_ast = to_swc(&hast::Node::Text(hast::Text {
- value: "a".into(),
- position: None,
- }))?;
+ let text_ast = to_swc(
+ &hast::Node::Text(hast::Text {
+ value: "a".into(),
+ position: None,
+ }),
+ None,
+ None,
+ )?;
assert_eq!(
text_ast,
Program {
+ path: None,
module: swc_ecma_ast::Module {
shebang: None,
body: vec![swc_ecma_ast::ModuleItem::Stmt(swc_ecma_ast::Stmt::Expr(