quasiquodo_ts_core/
lib.rs1mod context;
2pub mod input;
3mod lexer;
4mod lift;
5
6use proc_macro2::Span;
7use quote::ToTokens;
8use swc_common::comments::SingleThreadedComments;
9use swc_common::sync::Lrc;
10use swc_common::{FileName, SourceMap};
11use swc_ecma_parser::{Lexer, Parser, StringInput, Syntax, TsSyntax, error::Error as ParserError};
12use syn::parse_quote;
13
14use self::{
15 context::context,
16 input::{MacroInput, OutputKind},
17 lift::{CodeFragment, Lift},
18};
19
20#[cfg(test)]
21mod tests;
22
23pub fn expand(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
26 match expand_inner(input) {
27 Ok(tokens) => tokens,
28 Err(err) => err.to_compile_error(),
29 }
30}
31
32fn expand_inner(input: proc_macro2::TokenStream) -> syn::Result<proc_macro2::TokenStream> {
33 let input: MacroInput = syn::parse2(input)?;
34
35 let (preprocessed, stand_ins) = lexer::preprocess(input.source.value(), &input.variables)
38 .map_err(|err| syn::Error::new(input.source.span(), err))?;
39
40 let (parsed, comments) = parse_source(preprocessed, &input.output_kind)
41 .map_err(|err| syn::Error::new(input.source.span(), err))?;
42
43 let (stmts, context) = context(&input, stand_ins, comments)?;
44 let expr = match parsed.lift(&context)? {
45 CodeFragment::Single(expr) => expr,
46 CodeFragment::Splice(_) => {
47 return Err(syn::Error::new(
48 Span::call_site(),
49 "`Vec<T>` or `Option<T>` variable used in non-iterable position",
50 ));
51 }
52 };
53
54 let block: syn::Expr = parse_quote!({
55 #(#stmts)*
56 #expr
57 });
58
59 Ok(block.to_token_stream())
60}
61
62fn parse_source(
63 source: String,
64 kind: &OutputKind,
65) -> Result<(Box<dyn Lift>, SingleThreadedComments), ParseSourceError> {
66 use swc_ecma_ast::*;
67
68 let input = match kind {
70 OutputKind::TsType => format!("type T = {source};"),
71 OutputKind::ClassMember => format!("class C {{ {source} }}"),
72 OutputKind::TsTypeElement => format!("interface I {{ {source} }}"),
73 OutputKind::ParamOrTsParamProp => {
74 format!("class C {{ constructor({source}) {{}} }}")
75 }
76 OutputKind::Param => format!("function f({source}) {{}}"),
77 OutputKind::ImportSpecifier => format!(r#"import {{{source} }} from "";"#),
78 OutputKind::ExportSpecifier => format!(r#"export {{{source} }} from "";"#),
79 OutputKind::Ident
80 | OutputKind::Expr
81 | OutputKind::Stmt
82 | OutputKind::ModuleItem
83 | OutputKind::Decl => source,
84 };
85
86 let source_map = Lrc::new(SourceMap::default());
87 let source_file = source_map.new_source_file(FileName::Anon.into(), input);
88 let comments = SingleThreadedComments::default();
89 let lexer = Lexer::new(
90 Syntax::Typescript(TsSyntax::default()),
91 EsVersion::latest(),
92 StringInput::from(&*source_file),
93 Some(&comments),
94 );
95 let mut parser = Parser::new_from(lexer);
96
97 let parsed: Box<dyn Lift> = match kind {
98 OutputKind::TsType => match parser.parse_module_item()? {
99 ModuleItem::Stmt(Stmt::Decl(Decl::TsTypeAlias(alias))) => Box::new(*alias.type_ann),
100 _ => Err(Expected::TypeAlias)?,
101 },
102 OutputKind::ClassMember => match parser.parse_module_item()? {
103 ModuleItem::Stmt(Stmt::Decl(Decl::Class(class_decl))) => {
104 let mut body = class_decl.class.body;
105 if body.len() != 1 {
106 Err(Expected::ClassMember)?;
107 }
108 Box::new(body.swap_remove(0))
109 }
110 _ => Err(Expected::Class)?,
111 },
112 OutputKind::TsTypeElement => match parser.parse_module_item()? {
113 ModuleItem::Stmt(Stmt::Decl(Decl::TsInterface(iface))) => {
114 let mut body = iface.body.body;
115 if body.len() != 1 {
116 Err(Expected::TypeElement)?;
117 }
118 Box::new(body.swap_remove(0))
119 }
120 _ => Err(Expected::Interface)?,
121 },
122 OutputKind::ParamOrTsParamProp => match parser.parse_module_item()? {
123 ModuleItem::Stmt(Stmt::Decl(Decl::Class(class_decl))) => {
124 let mut body = class_decl.class.body;
125 let Some(ClassMember::Constructor(mut ctor)) = body.pop() else {
126 Err(Expected::Constructor)?
127 };
128 if ctor.params.len() != 1 {
129 Err(Expected::ConstructorParam)?;
130 }
131 Box::new(ctor.params.swap_remove(0))
132 }
133 _ => Err(Expected::Class)?,
134 },
135 OutputKind::Param => match parser.parse_module_item()? {
136 ModuleItem::Stmt(Stmt::Decl(Decl::Fn(fn_decl))) => {
137 let mut params = fn_decl.function.params;
138 if params.len() != 1 {
139 Err(Expected::Param)?;
140 }
141 Box::new(params.swap_remove(0))
142 }
143 _ => Err(Expected::Function)?,
144 },
145 OutputKind::ImportSpecifier => match parser.parse_module_item()? {
146 ModuleItem::ModuleDecl(ModuleDecl::Import(import)) => {
147 let mut specifiers = import.specifiers;
148 if specifiers.len() != 1 {
149 Err(Expected::ImportSpecifier)?;
150 }
151 Box::new(specifiers.swap_remove(0))
152 }
153 _ => Err(Expected::Import)?,
154 },
155 OutputKind::ExportSpecifier => match parser.parse_module_item()? {
156 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(export)) => {
157 let mut specifiers = export.specifiers;
158 if specifiers.len() != 1 {
159 Err(Expected::ExportSpecifier)?;
160 }
161 Box::new(specifiers.swap_remove(0))
162 }
163 _ => Err(Expected::NamedExport)?,
164 },
165 OutputKind::Ident => match *parser.parse_expr()? {
166 Expr::Ident(ident) => Box::new(ident),
167 _ => Err(Expected::Identifier)?,
168 },
169 OutputKind::Expr => Box::new(*parser.parse_expr()?),
170 OutputKind::Stmt => {
171 Box::new(parser.parse_stmt_list_item()?)
174 }
175 OutputKind::ModuleItem => Box::new(parser.parse_module_item()?),
176 OutputKind::Decl => match parser.parse_module_item()? {
177 ModuleItem::Stmt(Stmt::Decl(decl)) => Box::new(decl),
178 _ => Err(Expected::Declaration)?,
179 },
180 };
181
182 Ok((parsed, comments))
183}
184
185#[derive(Debug, thiserror::Error)]
186enum ParseSourceError {
187 #[error("failed to parse TypeScript: {0:?}")]
188 Parser(ParserError),
189 #[error(transparent)]
190 Expected(#[from] Expected),
191}
192
193impl From<ParserError> for ParseSourceError {
194 fn from(err: ParserError) -> Self {
195 Self::Parser(err)
196 }
197}
198
199#[derive(Debug, thiserror::Error)]
200enum Expected {
201 #[error("expected a class declaration")]
202 Class,
203 #[error("expected exactly one class member")]
204 ClassMember,
205 #[error("expected exactly one type element")]
206 TypeElement,
207 #[error("expected a function declaration")]
208 Function,
209 #[error("expected exactly one param")]
210 Param,
211 #[error("expected a constructor")]
212 Constructor,
213 #[error("expected exactly one constructor param")]
214 ConstructorParam,
215 #[error("expected an import declaration")]
216 Import,
217 #[error("expected exactly one import specifier")]
218 ImportSpecifier,
219 #[error("expected a named export declaration")]
220 NamedExport,
221 #[error("expected exactly one export specifier")]
222 ExportSpecifier,
223 #[error("expected a type alias declaration")]
224 TypeAlias,
225 #[error("expected a declaration")]
226 Declaration,
227 #[error("expected an identifier")]
228 Identifier,
229 #[error("expected an interface declaration")]
230 Interface,
231}