aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_utils/mdx.rs
blob: 4481b78963b77fd07b39c3cc11ff959d8ed47895 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
use crate::test_utils::{
    hast_util_to_swc::hast_util_to_swc,
    mdast_util_to_hast::mdast_util_to_hast,
    mdx_plugin_recma_document::{mdx_plugin_recma_document, Options as DocumentOptions},
    mdx_plugin_recma_jsx_rewrite::{mdx_plugin_recma_jsx_rewrite, Options as RewriteOptions},
    swc::{parse_esm, parse_expression, serialize},
};
use markdown::{to_mdast, Constructs, Location, ParseOptions};

pub use super::mdx_plugin_recma_document::JsxRuntime;

use swc_common::comments::{Comments, SingleThreadedComments};
// use swc_ecma_transforms_react::{jsx, Options as JsxBuildOptions};
// use swc_ecma_visit::VisitMutWith;

// To do: use `Format`.
/// Format the file is in (default: `Format::Detect`).
#[allow(dead_code)]
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub enum Format {
    /// Use `Format::Markdown` for files with an extension in `md_extensions`
    /// and `Format::Mdx` otherwise.
    #[default]
    Detect,
    /// Treat file as MDX.
    Mdx,
    /// Treat file as plain vanilla markdown.
    Markdown,
}

// To do: use `OutputFormat`.
/// Output format to generate (default: `OutputFormat::Program`).
#[allow(dead_code)]
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub enum OutputFormat {
    /// The `Program` format will use import statements to import the JSX
    /// runtime (and optionally provider) and use an export statement to yield
    /// the `MDXContent` component.
    #[default]
    Program,
    /// The `FunctionBody` format will get the JSX runtime (and optionally
    /// provider) from `arguments[0]`, rewrite export statements, and use a
    /// return statement to yield what was exported.
    /// Normally, this output format will throw on `import` (and
    /// `export … from`) statements, but you can support them by setting
    /// `options.useDynamicImport`.
    FunctionBody,
}

/// Configuration (optional).
#[derive(Clone, Debug, Default)]
pub struct Options {
    // /// List of markdown extensions, with dot.
    // ///
    // /// Default: `vec![".md".into(), ".markdown".into(), ".mdown".into(), ".mkdn".into(), ".mkd".into(), ".mdwn".into(), ".mkdown".into(), ".ron".into()]`.
    // pub md_extensions: Option<Vec<String>>,
    // /// List of MDX extensions, with dot.
    // ///
    // /// Default: `vec![".mdx".into()]`.
    // pub mdx_extensions: Option<Vec<String>>,
    // /// Format the file is in (default: `Format::Detect`).
    // pub format: Format,
    // /// To do: support `output_format: FunctionBody
    // /// Output format to generate (default: `OutputFormat::Program`).
    // ///
    // /// In most cases `OutputFormat::Program` should be used, as it results in a
    // /// whole program.
    // /// `OutputFormat::FunctionBody` can be used to compile to code that can be
    // /// `eval`ed.
    // /// In some cases, you might want to do that, such as when compiling on the
    // /// server and running on the client.
    // pub output_format: OutputFormat,
    // /// Whether to compile to dynamic import expressions (default:
    // /// `false`).
    // ///
    // /// This option applies when `options.output_format` is
    // /// `OutputFormat::FunctionBody`.
    // ///
    // /// This project can turn import statements (`import x from 'y'`) into
    // /// dynamic imports (`const {x} = await import('y')`).
    // /// This is useful because import statements only work at the top level of
    // /// JavaScript modules, whereas `import()` is available inside function
    // /// bodies.
    // ///
    // /// When you turn `use_dynamic_import` on, you should probably set
    // /// `options.base_url` too.
    // pub use_dynamic_import: bool,
    // /// Resolve `import`s (and `export … from`, `import.meta.url`) from this
    // /// URL (default: `None`, example: `Some("https://example.com/".into())`).
    // ///
    // /// Relative specifiers are non-absolute URLs that start with `/`, `./`, or
    // /// `../`.
    // /// For example: `/index.js`, `./folder/file.js`, or `../main.js`.
    // ///
    // /// This option is useful when code will run in a different place.
    // /// One example is when `.mdx` files are in path *a* but compiled to path
    // /// *b* and imports should run relative the path *b*.
    // /// Another example is when evaluating code, whether in Node or a browser.
    // pub base_url: Option<String>,
    /// Whether to add extra information to error messages in generated code
    /// (default: `false`).
    pub development: bool,

    // To do: some alternative to generate source maps.
    // SourceMapGenerator
    /// Place to import a provider from (default: `None`, example:
    /// `Some("@mdx-js/react").into()`).
    ///
    /// Useful for runtimes that support context (React, Preact).
    /// The provider must export a `useMDXComponents`, which is called to
    /// access an object of components.
    pub provider_import_source: Option<String>,

    /// Whether to keep JSX (default: `false`).
    ///
    /// The default is to compile JSX away so that the resulting file is
    /// immediately runnable.
    pub jsx: bool,

    /// JSX runtime to use (default: `Some(JsxRuntime::Automatic)`).
    ///
    /// The classic runtime compiles to calls such as `h('p')`, the automatic
    /// runtime compiles to
    /// `import _jsx from '$importSource/jsx-runtime'\n_jsx('p')`.
    pub jsx_runtime: Option<JsxRuntime>,

    /// Place to import automatic JSX runtimes from (`Option<String>`, default:
    /// `Some("react".into())`).
    ///
    /// When in the automatic runtime, this is used to define an import for
    /// `_Fragment`, `_jsx`, and `_jsxs`.
    pub jsx_import_source: Option<String>,

    /// Pragma for JSX (default: `Some("React.createElement".into())`).
    ///
    /// When in the classic runtime, this is used as an identifier for function
    /// calls: `<x />` to `React.createElement('x')`.
    ///
    /// You should most probably define `pragma_frag` and `pragma_import_source`
    /// too when changing this.
    pub pragma: Option<String>,

    /// Pragma for JSX fragments (default: `Some("React.Fragment".into())`).
    ///
    /// When in the classic runtime, this is used as an identifier for
    /// fragments: `<>` to `React.createElement(React.Fragment)`.
    ///
    /// You should most probably define `pragma` and `pragma_import_source`
    /// too when changing this.
    pub pragma_frag: Option<String>,

    /// Where to import the identifier of `pragma` from (default:
    /// `Some("react".into())`).
    ///
    /// When in the classic runtime, this is used to import the `pragma`
    /// function.
    /// To illustrate with an example: when `pragma` is `"a.b"` and
    /// `pragma_import_source` is `"c"`, the following will be generated:
    /// `import a from 'c'`.
    pub pragma_import_source: Option<String>,
}

#[allow(dead_code)]
pub fn mdx(value: &str, filepath: Option<String>, options: &Options) -> Result<String, String> {
    let parse_options = ParseOptions {
        constructs: Constructs::mdx(),
        mdx_esm_parse: Some(Box::new(parse_esm)),
        mdx_expression_parse: Some(Box::new(parse_expression)),
        ..ParseOptions::default()
    };
    let document_options = DocumentOptions {
        pragma: options.pragma.clone(),
        pragma_frag: options.pragma_frag.clone(),
        pragma_import_source: options.pragma_import_source.clone(),
        jsx_import_source: options.jsx_import_source.clone(),
        jsx_runtime: options.jsx_runtime,
    };
    let rewrite_options = RewriteOptions {
        development: options.development,
        provider_import_source: options.provider_import_source.clone(),
    };

    let location = Location::new(value.as_bytes());
    let mdast = to_mdast(value, &parse_options)?;
    let hast = mdast_util_to_hast(&mdast);
    let mut program = hast_util_to_swc(&hast, filepath, Some(&location))?;
    mdx_plugin_recma_document(&mut program, &document_options, Some(&location))?;
    mdx_plugin_recma_jsx_rewrite(&mut program, &rewrite_options, Some(&location));

    // Add our comments.
    let comments = SingleThreadedComments::default();

    for c in program.comments {
        comments.add_leading(c.span.lo, c);
    }

    println!("comments for swc: {:?}", comments);

    if !options.jsx {
        // let cm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
        // let build_options = JsxBuildOptions {
        //     next: Some(true),
        //     throw_if_namespace: Some(false),
        //     development: Some(options.development),
        //     use_spread: Some(true),
        //     refresh: None,
        //     runtime: None,
        //     import_source: None,
        //     pragma: None,
        //     pragma_frag: None,
        //     use_builtins: None,
        // };
        // let mark = Mark::fresh(Mark::default());
        // program
        //     .module
        //     .visit_mut_with(&mut jsx(cm, Some(comments), build_options, mark));
    }

    // To do: include comments.
    Ok(serialize(&program.module))
}