use std::borrow::Cow;
use memchr::memchr;
use oxc_ast::Comment;
use crate::{JsxOptions, JsxRuntime, TransformCtx, TypeScriptOptions};
pub fn update_options_with_comments(
comments: &[Comment],
typescript: &mut TypeScriptOptions,
jsx: &mut JsxOptions,
ctx: &TransformCtx,
) {
let source_text = ctx.source_text;
for comment in comments {
update_options_with_comment(typescript, jsx, comment, source_text);
}
}
fn update_options_with_comment(
typescript: &mut TypeScriptOptions,
jsx: &mut JsxOptions,
comment: &Comment,
source_text: &str,
) {
let mut comment_str = comment.content_span().source_text(source_text);
while let Some((keyword, value, remainder)) = find_jsx_pragma(comment_str) {
match keyword {
PragmaType::Jsx => {
if jsx.jsx_plugin || jsx.development {
jsx.pragma = Some(value.to_string());
}
typescript.jsx_pragma = Cow::Owned(value.to_string());
}
PragmaType::JsxRuntime => match value {
"classic" => jsx.runtime = JsxRuntime::Classic,
"automatic" => jsx.runtime = JsxRuntime::Automatic,
_ => {}
},
PragmaType::JsxImportSource => {
jsx.import_source = Some(value.to_string());
}
PragmaType::JsxFrag => {
if jsx.jsx_plugin || jsx.development {
jsx.pragma_frag = Some(value.to_string());
}
typescript.jsx_pragma_frag = Cow::Owned(value.to_string());
}
}
comment_str = remainder;
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum PragmaType {
Jsx,
JsxRuntime,
JsxImportSource,
JsxFrag,
}
fn find_jsx_pragma(mut comment_str: &str) -> Option<(PragmaType, &str, &str)> {
let pragma_type;
loop {
let at_sign_index = memchr(b'@', comment_str.as_bytes())?;
if at_sign_index > 0 {
let prev_byte = comment_str.as_bytes()[at_sign_index - 1];
if !matches!(prev_byte, b' ' | b'\t' | b'\r' | b'\n' | b'*') {
comment_str = unsafe { comment_str.get_unchecked(at_sign_index + 1..) };
continue;
}
}
let next4 = comment_str.as_bytes().get(at_sign_index..at_sign_index + 4)?;
if next4 != b"@jsx" {
comment_str = unsafe { comment_str.get_unchecked(at_sign_index + 1..) };
continue;
}
comment_str = unsafe { comment_str.get_unchecked(at_sign_index + 4..) };
let space_index = comment_str.as_bytes().iter().position(|&b| matches!(b, b' ' | b'\t'))?;
let keyword_str = unsafe { comment_str.get_unchecked(..space_index) };
comment_str = unsafe { comment_str.get_unchecked(space_index + 1..) };
pragma_type = match keyword_str {
"" => PragmaType::Jsx,
"Runtime" => PragmaType::JsxRuntime,
"ImportSource" => PragmaType::JsxImportSource,
"Frag" => PragmaType::JsxFrag,
_ => {
continue;
}
};
break;
}
loop {
let next_byte = *comment_str.as_bytes().first()?;
if !matches!(next_byte, b' ' | b'\t') {
break;
}
comment_str = unsafe { comment_str.get_unchecked(1..) };
}
let space_index = comment_str.as_bytes().iter().position(|&b| is_ascii_whitespace(b));
let value;
if let Some(space_index) = space_index {
value = unsafe { comment_str.get_unchecked(..space_index) };
comment_str = unsafe { comment_str.get_unchecked(space_index + 1..) };
} else {
value = comment_str;
comment_str = "";
}
if value.is_empty() { None } else { Some((pragma_type, value, comment_str)) }
}
#[inline]
fn is_ascii_whitespace(byte: u8) -> bool {
const VT: u8 = 0x0B;
const FF: u8 = 0x0C;
matches!(byte, b' ' | b'\t' | b'\r' | b'\n' | VT | FF)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_find_jsx_pragma() {
let cases: &[(&str, &[(PragmaType, &str)])] = &[
("", &[]),
("blah blah blah", &[]),
("@jsxDonkey abc", &[]),
("@jsx h", &[(PragmaType::Jsx, "h")]),
("@jsx React.createDumpling", &[(PragmaType::Jsx, "React.createDumpling")]),
("@jsxRuntime classic", &[(PragmaType::JsxRuntime, "classic")]),
("@jsxImportSource preact", &[(PragmaType::JsxImportSource, "preact")]),
("@jsxFrag Fraggy", &[(PragmaType::JsxFrag, "Fraggy")]),
(
"@jsx h @jsxRuntime classic",
&[(PragmaType::Jsx, "h"), (PragmaType::JsxRuntime, "classic")],
),
(
"* @jsx h\n * @jsxRuntime classic\n *",
&[(PragmaType::Jsx, "h"), (PragmaType::JsxRuntime, "classic")],
),
(
"@jsx h @jsxRuntime classic @jsxImportSource importer-a-go-go @jsxFrag F",
&[
(PragmaType::Jsx, "h"),
(PragmaType::JsxRuntime, "classic"),
(PragmaType::JsxImportSource, "importer-a-go-go"),
(PragmaType::JsxFrag, "F"),
],
),
(
"* @jsx h\n * @jsxRuntime classic\n * @jsxImportSource importer-a-go-go\n * @jsxFrag F\n *",
&[
(PragmaType::Jsx, "h"),
(PragmaType::JsxRuntime, "classic"),
(PragmaType::JsxImportSource, "importer-a-go-go"),
(PragmaType::JsxFrag, "F"),
],
),
(
"@jsx h blah blah @jsxRuntime classic",
&[(PragmaType::Jsx, "h"), (PragmaType::JsxRuntime, "classic")],
),
(
"blah blah\n * @jsx h \n * blah blah\n * @jsxRuntime classic \n * blah blah",
&[(PragmaType::Jsx, "h"), (PragmaType::JsxRuntime, "classic")],
),
("@jsx", &[]),
("@jsxRuntime", &[]),
("@moon @jsx h @moon", &[(PragmaType::Jsx, "h")]),
("@jsxX @jsx h @jsxX", &[(PragmaType::Jsx, "h")]),
("@jsxMoon @jsx h @jsxMoon", &[(PragmaType::Jsx, "h")]),
("@jsx @jsx h", &[(PragmaType::Jsx, "@jsx")]),
("@@@@@jsx h", &[(PragmaType::Jsx, "h")]),
("`@jsxImportSource custom/source`", &[]),
("`@jsx h`", &[]),
("This mentions `@jsxImportSource custom/source` in docs", &[]),
(
"@jsxImportSource react\n * This mentions `@jsxImportSource custom/source` in docs",
&[(PragmaType::JsxImportSource, "react")],
),
];
let prefixes = ["", " ", "\n\n", "*\n* "];
let postfixes = ["", " ", "\n\n", "\n*"];
for (comment_str, expected) in cases {
for prefix in prefixes {
for postfix in postfixes {
let comment_str = format!("{prefix}{comment_str}{postfix}");
let mut comment_str = comment_str.as_str();
let mut pragmas = vec![];
while let Some((pragma_type, value, remaining)) = find_jsx_pragma(comment_str) {
pragmas.push((pragma_type, value));
comment_str = remaining;
}
assert_eq!(&pragmas, expected);
}
}
}
}
}