use markdown::{
id_cont, id_start,
mdast::Stop,
unist::{Point, Position},
Location,
};
use swc_core::ecma::ast::{
BinExpr, BinaryOp, Bool, CallExpr, Callee, ComputedPropName, Expr, ExprOrSpread, Ident,
JSXAttrName, JSXElementName, JSXMemberExpr, JSXNamespacedName, JSXObject, Lit, MemberExpr,
MemberProp, Null, Number, ObjectLit, PropName, PropOrSpread, Str,
};
use swc_core::ecma::visit::{noop_visit_mut_type, VisitMut};
use swc_core::{
common::{BytePos, Span, SyntaxContext, DUMMY_SP},
ecma::ast::IdentName,
};
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),
})
}
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
}
pub fn point_to_bytepos(point: &Point) -> BytePos {
BytePos(point.offset as u32 + 1)
}
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
}
pub fn position_opt_to_string(position: Option<&Position>) -> String {
if let Some(position) = position {
position_to_string(position)
} else {
"0:0".into()
}
}
pub fn position_to_string(position: &Position) -> String {
format!(
"{}-{}",
point_to_string(&position.start),
point_to_string(&position.end)
)
}
pub fn point_to_string(point: &Point) -> String {
format!("{}:{}", point.line, point.column)
}
#[derive(Debug, Default, Clone)]
pub struct RewriteStopsContext<'a> {
pub stops: &'a [Stop],
pub location: Option<&'a Location>,
}
impl VisitMut for RewriteStopsContext<'_> {
noop_visit_mut_type!();
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;
let lo_clean = Location::relative_to_absolute(self.stops, lo_rel - 1);
let hi_clean = Location::relative_to_absolute(self.stops, hi_rel - 1);
if let Some(lo_abs) = lo_clean {
if let Some(hi_abs) = hi_clean {
result = create_span(lo_abs as u32 + 1, hi_abs as u32 + 1);
}
}
*span = result;
}
}
#[derive(Debug, Default, Clone)]
pub struct RewritePrefixContext {
pub prefix_len: u32,
}
impl VisitMut for RewritePrefixContext {
noop_visit_mut_type!();
fn visit_mut_span(&mut self, span: &mut Span) {
let mut result = DUMMY_SP;
if span.lo.0 > self.prefix_len && span.hi.0 > self.prefix_len {
result = create_span(span.lo.0 - self.prefix_len, span.hi.0 - self.prefix_len);
}
*span = result;
}
}
#[derive(Debug, Default, Clone)]
pub struct DropContext {}
impl VisitMut for DropContext {
noop_visit_mut_type!();
fn visit_mut_span(&mut self, span: &mut Span) {
*span = DUMMY_SP;
}
}
pub fn create_span(lo: u32, hi: u32) -> Span {
Span {
lo: BytePos(lo),
hi: BytePos(hi),
}
}
pub fn create_ident(sym: &str) -> IdentName {
IdentName {
sym: sym.into(),
span: DUMMY_SP,
}
}
pub fn create_ident_expression(sym: &str) -> Expr {
Expr::Ident(create_ident(sym).into())
}
pub fn create_null() -> Null {
Null {
span: swc_core::common::DUMMY_SP,
}
}
pub fn create_null_lit() -> Lit {
Lit::Null(create_null())
}
pub fn create_null_expression() -> Expr {
Expr::Lit(create_null_lit())
}
pub fn create_str(value: &str) -> Str {
value.into()
}
pub fn create_str_lit(value: &str) -> Lit {
Lit::Str(create_str(value))
}
pub fn create_str_expression(value: &str) -> Expr {
Expr::Lit(create_str_lit(value))
}
pub fn create_bool(value: bool) -> Bool {
value.into()
}
pub fn create_bool_lit(value: bool) -> Lit {
Lit::Bool(create_bool(value))
}
pub fn create_bool_expression(value: bool) -> Expr {
Expr::Lit(create_bool_lit(value))
}
pub fn create_num(value: f64) -> Number {
value.into()
}
pub fn create_num_lit(value: f64) -> Lit {
Lit::Num(create_num(value))
}
pub fn create_num_expression(value: f64) -> Expr {
Expr::Lit(create_num_lit(value))
}
pub fn create_object_lit(value: Vec<PropOrSpread>) -> ObjectLit {
ObjectLit {
props: value,
span: DUMMY_SP,
}
}
pub fn create_object_expression(value: Vec<PropOrSpread>) -> Expr {
Expr::Object(create_object_lit(value))
}
pub fn create_call(callee: Callee, args: Vec<ExprOrSpread>) -> CallExpr {
CallExpr {
callee,
args,
span: DUMMY_SP,
type_args: None,
ctxt: SyntaxContext::empty(),
}
}
pub fn create_call_expression(callee: Callee, args: Vec<ExprOrSpread>) -> Expr {
Expr::Call(create_call(callee, args))
}
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: DUMMY_SP,
})
} else {
right_expr
});
}
left.expect("expected one or more expressions")
}
pub fn create_member_expression_from_str(name: &str) -> Expr {
match parse_js_name(name) {
JsName::Normal(name) => create_ident_expression(name),
JsName::Member(parts) => {
let mut member = create_member(
create_ident_expression(parts[0]),
create_member_prop_from_str(parts[1]),
);
let mut index = 2;
while index < parts.len() {
member = create_member(
Expr::Member(member),
create_member_prop_from_str(parts[index]),
);
index += 1;
}
Expr::Member(member)
}
}
}
pub fn create_member(obj: Expr, prop: MemberProp) -> MemberExpr {
MemberExpr {
obj: Box::new(obj),
prop,
span: DUMMY_SP,
}
}
pub fn create_member_prop_from_str(name: &str) -> MemberProp {
if is_identifier_name(name) {
MemberProp::Ident(create_ident(name))
} else {
MemberProp::Computed(ComputedPropName {
expr: Box::new(create_str_expression(name)),
span: DUMMY_SP,
})
}
}
pub fn create_jsx_name_from_str(name: &str) -> JSXElementName {
match parse_jsx_name(name) {
JsxName::Normal(name) => JSXElementName::Ident(create_ident(name).into()),
JsxName::Namespace(ns, name) => JSXElementName::JSXNamespacedName(JSXNamespacedName {
span: DUMMY_SP,
ns: create_ident(ns),
name: create_ident(name),
}),
JsxName::Member(parts) => {
let mut member = create_jsx_member(
JSXObject::Ident(create_ident(parts[0]).into()),
create_ident(parts[1]),
);
let mut index = 2;
while index < parts.len() {
member = create_jsx_member(
JSXObject::JSXMemberExpr(Box::new(member)),
create_ident(parts[index]),
);
index += 1;
}
JSXElementName::JSXMemberExpr(member)
}
}
}
pub fn create_jsx_member(obj: JSXObject, prop: IdentName) -> JSXMemberExpr {
JSXMemberExpr {
span: DUMMY_SP,
obj,
prop,
}
}
pub fn jsx_element_name_to_expression(node: JSXElementName) -> Expr {
match node {
JSXElementName::JSXMemberExpr(member_expr) => {
jsx_member_expression_to_expression(member_expr)
}
JSXElementName::JSXNamespacedName(namespace_name) => create_str_expression(&format!(
"{}:{}",
namespace_name.ns.sym, namespace_name.name.sym
)),
JSXElementName::Ident(ident) => create_ident_or_literal(&ident),
}
}
pub fn create_jsx_attr_name_from_str(name: &str) -> JSXAttrName {
match parse_jsx_name(name) {
JsxName::Member(_) => {
unreachable!("member expressions in attribute names are not supported")
}
JsxName::Namespace(ns, name) => JSXAttrName::JSXNamespacedName(JSXNamespacedName {
span: DUMMY_SP,
ns: create_ident(ns),
name: create_ident(name),
}),
JsxName::Normal(name) => JSXAttrName::Ident(create_ident(name)),
}
}
pub fn jsx_member_expression_to_expression(node: JSXMemberExpr) -> Expr {
Expr::Member(create_member(
jsx_object_to_expression(node.obj),
ident_to_member_prop(&node.prop),
))
}
pub fn ident_to_member_prop(node: &IdentName) -> MemberProp {
if is_identifier_name(node.as_ref()) {
MemberProp::Ident(IdentName {
sym: node.sym.clone(),
span: node.span,
})
} else {
MemberProp::Computed(ComputedPropName {
expr: Box::new(create_str_expression(&node.sym)),
span: node.span,
})
}
}
pub fn jsx_attribute_name_to_prop_name(node: JSXAttrName) -> PropName {
match node {
JSXAttrName::JSXNamespacedName(namespace_name) => create_prop_name(&format!(
"{}:{}",
namespace_name.ns.sym, namespace_name.name.sym
)),
JSXAttrName::Ident(ident) => create_prop_name(&ident.sym),
}
}
pub fn jsx_object_to_expression(node: JSXObject) -> Expr {
match node {
JSXObject::Ident(ident) => create_ident_or_literal(&ident),
JSXObject::JSXMemberExpr(member_expr) => jsx_member_expression_to_expression(*member_expr),
}
}
pub fn create_ident_or_literal(node: &Ident) -> Expr {
if is_identifier_name(node.as_ref()) {
create_ident_expression(node.sym.as_ref())
} else {
create_str_expression(&node.sym)
}
}
pub fn create_prop_name(name: &str) -> PropName {
if is_identifier_name(name) {
PropName::Ident(create_ident(name))
} else {
PropName::Str(create_str(name))
}
}
pub fn is_literal_name(name: &str) -> bool {
matches!(name.as_bytes().first(), Some(b'a'..=b'z')) || !is_identifier_name(name)
}
pub fn is_identifier_name(name: &str) -> bool {
for (index, char) in name.chars().enumerate() {
if if index == 0 {
!id_start(char)
} else {
!id_cont(char, false)
} {
return false;
}
}
true
}
pub enum JsName<'a> {
Member(Vec<&'a str>),
Normal(&'a str),
}
pub enum JsxName<'a> {
Member(Vec<&'a str>),
Namespace(&'a str, &'a str),
Normal(&'a str),
}
pub fn parse_js_name(name: &str) -> JsName {
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.is_empty() {
JsName::Normal(name)
}
else {
parts.push(&name[start..]);
JsName::Member(parts)
}
}
pub fn parse_jsx_name(name: &str) -> JsxName {
match parse_js_name(name) {
JsName::Member(parts) => JsxName::Member(parts),
JsName::Normal(name) => {
if let Some(colon) = name.as_bytes().iter().position(|d| matches!(d, b':')) {
JsxName::Namespace(&name[0..colon], &name[(colon + 1)..])
}
else {
JsxName::Normal(name)
}
}
}
}
pub fn jsx_member_to_parts(node: &JSXMemberExpr) -> Vec<&str> {
let mut parts = vec![];
let mut member_opt = Some(node);
while let Some(member) = member_opt {
parts.push(member.prop.sym.as_ref());
match &member.obj {
JSXObject::Ident(d) => {
parts.push(d.sym.as_ref());
member_opt = None;
}
JSXObject::JSXMemberExpr(node) => {
member_opt = Some(node);
}
}
}
parts.reverse();
parts
}
pub fn inter_element_whitespace(value: &str) -> bool {
let bytes = value.as_bytes();
let mut index = 0;
while index < bytes.len() {
match bytes[index] {
b'\t' | 0x0C | b'\r' | b'\n' | b' ' => {}
_ => return false,
}
index += 1;
}
true
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn bytepos_to_point_test() {
assert_eq!(
bytepos_to_point(BytePos(123), None),
None,
"should support no location"
);
}
#[test]
fn position_opt_to_string_test() {
assert_eq!(
position_opt_to_string(None),
"0:0",
"should support no position"
);
}
#[test]
fn jsx_member_to_parts_test() {
assert_eq!(
jsx_member_to_parts(&JSXMemberExpr {
span: DUMMY_SP,
prop: create_ident("a"),
obj: JSXObject::Ident(create_ident("b").into())
}),
vec!["b", "a"],
"should support a member with 2 items"
);
assert_eq!(
jsx_member_to_parts(&JSXMemberExpr {
span: DUMMY_SP,
prop: create_ident("a"),
obj: JSXObject::JSXMemberExpr(Box::new(JSXMemberExpr {
span: DUMMY_SP,
prop: create_ident("b"),
obj: JSXObject::JSXMemberExpr(Box::new(JSXMemberExpr {
span: DUMMY_SP,
prop: create_ident("c"),
obj: JSXObject::Ident(create_ident("d").into())
}))
}))
}),
vec!["d", "c", "b", "a"],
"should support a member with 4 items"
);
}
}