1use markdown_it::parser::inline::{InlineRule, InlineState};
2use markdown_it::Node;
3use oxc_allocator::Allocator;
4use oxc_diagnostics::OxcDiagnostic;
5use oxc_parser::Parser;
6use oxc_span::{GetSpan, SourceType};
7
8use crate::plugin::{ErrorBlock, RawBlock};
9
10/// Inline rule for ${...} TypeScript expressions.
11pub(crate) struct Rule;
12
13impl InlineRule for Rule {
14 const MARKER: char = '$';
15
16 fn run(state: &mut InlineState) -> Option<(Node, usize)> {
17 let input = &state.src[state.pos..state.pos_max];
18
19 if !input.starts_with("${") {
20 return None;
21 }
22
23 let expr_source = &input["${".len()..];
24
25 let allocator = Allocator::default();
26 let source_type = SourceType::default()
27 .with_module(true)
28 .with_typescript(true)
29 .with_jsx(false);
30
31 let expr = match Parser::new(&allocator, expr_source, source_type).parse_expression() {
32 Ok(expr) => expr,
33 Err(errors) => {
34 return Some((Node::new(ErrorBlock { errors }), "${".len()));
35 }
36 };
37 let mut length = expr.span().end as usize;
38
39 // Verify the next character after whitespace is a }
40 while let Some(char) = expr_source.as_bytes().get(length) {
41 match char {
42 b' ' | b'\t' | b'\n' | b'\r' => length += 1,
43 _ => break,
44 }
45 }
46 if expr_source.as_bytes().get(length) != Some(&b'}') {
47 let error = OxcDiagnostic::error("Invalid template expression");
48 return Some((
49 Node::new(ErrorBlock {
50 errors: vec![error],
51 }),
52 input.len(),
53 ));
54 }
55
56 let total_len = "${".len() + length + "}".len();
57 let content = &input[..total_len];
58 let node = Node::new(RawBlock {
59 content: content.to_string(),
60 });
61
62 Some((node, total_len))
63 }
64}