1#![feature(min_specialization)]
2#![feature(arbitrary_self_types)]
3#![feature(arbitrary_self_types_pointers)]
4
5use anyhow::Result;
6use mdxjs::{MdxParseOptions, Options, compile};
7use turbo_rcstr::{RcStr, rcstr};
8use turbo_tasks::{ResolvedVc, ValueDefault, Vc};
9use turbo_tasks_fs::{File, FileContent, FileSystemPath, rope::Rope};
10use turbopack_core::{
11 asset::{Asset, AssetContent},
12 ident::AssetIdent,
13 issue::{
14 Issue, IssueExt, IssueSource, IssueStage, OptionIssueSource, OptionStyledString,
15 StyledString,
16 },
17 source::Source,
18 source_pos::SourcePos,
19 source_transform::SourceTransform,
20};
21
22#[turbo_tasks::value(shared, operation)]
23#[derive(Hash, Debug, Clone)]
24#[serde(rename_all = "camelCase")]
25pub enum MdxParseConstructs {
26 Commonmark,
27 Gfm,
28}
29
30#[turbo_tasks::value(shared, operation)]
34#[derive(Hash, Debug, Clone)]
35#[serde(rename_all = "camelCase", default)]
36pub struct MdxTransformOptions {
37 pub development: Option<bool>,
38 pub jsx: Option<bool>,
39 pub jsx_runtime: Option<RcStr>,
40 pub jsx_import_source: Option<RcStr>,
41 pub provider_import_source: Option<RcStr>,
45 pub mdx_type: Option<MdxParseConstructs>,
47}
48
49impl Default for MdxTransformOptions {
50 fn default() -> Self {
51 Self {
52 development: Some(true),
53 jsx: Some(false),
54 jsx_runtime: None,
55 jsx_import_source: None,
56 provider_import_source: None,
57 mdx_type: Some(MdxParseConstructs::Commonmark),
58 }
59 }
60}
61
62#[turbo_tasks::value_impl]
63impl MdxTransformOptions {
64 #[turbo_tasks::function]
65 fn default_private() -> Vc<Self> {
66 Self::cell(Default::default())
67 }
68}
69
70impl ValueDefault for MdxTransformOptions {
71 fn value_default() -> Vc<Self> {
72 Self::default_private()
73 }
74}
75
76#[turbo_tasks::value]
77pub struct MdxTransform {
78 options: ResolvedVc<MdxTransformOptions>,
79}
80
81#[turbo_tasks::value_impl]
82impl MdxTransform {
83 #[turbo_tasks::function]
84 pub fn new(options: ResolvedVc<MdxTransformOptions>) -> Vc<Self> {
85 MdxTransform { options }.cell()
86 }
87}
88
89#[turbo_tasks::value_impl]
90impl SourceTransform for MdxTransform {
91 #[turbo_tasks::function]
92 fn transform(&self, source: ResolvedVc<Box<dyn Source>>) -> Vc<Box<dyn Source>> {
93 Vc::upcast(
94 MdxTransformedAsset {
95 options: self.options,
96 source,
97 }
98 .cell(),
99 )
100 }
101}
102
103#[turbo_tasks::value]
104struct MdxTransformedAsset {
105 options: ResolvedVc<MdxTransformOptions>,
106 source: ResolvedVc<Box<dyn Source>>,
107}
108
109#[turbo_tasks::value_impl]
110impl Source for MdxTransformedAsset {
111 #[turbo_tasks::function]
112 fn ident(&self) -> Vc<AssetIdent> {
113 self.source.ident().rename_as(rcstr!("*.tsx"))
114 }
115}
116
117#[turbo_tasks::value_impl]
118impl Asset for MdxTransformedAsset {
119 #[turbo_tasks::function]
120 async fn content(self: Vc<Self>) -> Result<Vc<AssetContent>> {
121 Ok(*self.process().await?.content)
122 }
123}
124
125#[turbo_tasks::value_impl]
126impl MdxTransformedAsset {
127 #[turbo_tasks::function]
128 async fn process(&self) -> Result<Vc<MdxTransformResult>> {
129 let content = self.source.content().await?;
130 let transform_options = self.options.await?;
131
132 let AssetContent::File(file) = &*content else {
133 anyhow::bail!("Unexpected mdx asset content");
134 };
135
136 let FileContent::Content(file) = &*file.await? else {
137 anyhow::bail!("Not able to read mdx file content");
138 };
139
140 let jsx_runtime = if let Some(runtime) = &transform_options.jsx_runtime {
141 match runtime.as_str() {
142 "automatic" => Some(mdxjs::JsxRuntime::Automatic),
143 "classic" => Some(mdxjs::JsxRuntime::Classic),
144 _ => None,
145 }
146 } else {
147 None
148 };
149
150 let parse_options = match transform_options.mdx_type {
151 Some(MdxParseConstructs::Gfm) => MdxParseOptions::gfm(),
152 _ => MdxParseOptions::default(),
153 };
154
155 let options = Options {
156 parse: parse_options,
157 development: transform_options.development.unwrap_or(false),
158 provider_import_source: transform_options
159 .provider_import_source
160 .clone()
161 .map(RcStr::into_owned),
162 jsx: transform_options.jsx.unwrap_or(false), jsx_runtime,
164 jsx_import_source: transform_options
165 .jsx_import_source
166 .clone()
167 .map(RcStr::into_owned),
168 filepath: Some(self.source.ident().path().await?.to_string()),
169 ..Default::default()
170 };
171
172 let result = compile(&file.content().to_str()?, &options);
173
174 match result {
175 Ok(mdx_jsx_component) => Ok(MdxTransformResult {
176 content: AssetContent::file(File::from(Rope::from(mdx_jsx_component)).into())
177 .to_resolved()
178 .await?,
179 }
180 .cell()),
181 Err(err) => {
182 let source = match err.place {
183 Some(p) => {
184 let (start, end) = match *p {
185 markdown::message::Place::Position(p) => (
188 SourcePos {
189 line: (p.start.line - 1) as u32,
190 column: (p.start.column - 1) as u32,
191 },
192 SourcePos {
193 line: (p.end.line - 1) as u32,
194 column: (p.end.column - 1) as u32,
195 },
196 ),
197 markdown::message::Place::Point(p) => {
198 let p = SourcePos {
199 line: (p.line - 1) as u32,
200 column: (p.column - 1) as u32,
201 };
202 (p, p)
203 }
204 };
205
206 IssueSource::from_line_col(self.source, start, end)
207 }
208 None => IssueSource::from_source_only(self.source),
209 };
210
211 MdxIssue {
212 source,
213 reason: RcStr::from(err.reason),
214 mdx_rule_id: RcStr::from(*err.rule_id),
215 mdx_source: RcStr::from(*err.source),
216 }
217 .resolved_cell()
218 .emit();
219
220 Ok(MdxTransformResult {
221 content: AssetContent::File(FileContent::NotFound.resolved_cell())
222 .resolved_cell(),
223 }
224 .cell())
225 }
226 }
227 }
228}
229
230#[turbo_tasks::value]
231struct MdxTransformResult {
232 content: ResolvedVc<AssetContent>,
233}
234
235#[turbo_tasks::value]
236struct MdxIssue {
237 source: IssueSource,
239 reason: RcStr,
241 mdx_rule_id: RcStr,
243 mdx_source: RcStr,
245}
246
247#[turbo_tasks::value_impl]
248impl Issue for MdxIssue {
249 #[turbo_tasks::function]
250 fn file_path(&self) -> Vc<FileSystemPath> {
251 self.source.file_path()
252 }
253
254 #[turbo_tasks::function]
255 fn source(&self) -> Vc<OptionIssueSource> {
256 Vc::cell(Some(self.source))
257 }
258
259 #[turbo_tasks::function]
260 fn stage(self: Vc<Self>) -> Vc<IssueStage> {
261 IssueStage::Parse.cell()
262 }
263
264 #[turbo_tasks::function]
265 fn title(self: Vc<Self>) -> Vc<StyledString> {
266 StyledString::Text(rcstr!("MDX Parse Error")).cell()
267 }
268
269 #[turbo_tasks::function]
270 fn description(&self) -> Vc<OptionStyledString> {
271 Vc::cell(Some(
272 StyledString::Text(self.reason.clone()).resolved_cell(),
273 ))
274 }
275}