pub mod case;
mod dag;
mod frontend;
pub mod ir;
mod to_tokens;
mod transformer;
use frontend::FrontendState;
use std::{
collections::HashMap,
path::{Path, PathBuf},
};
use swc_common::{
self,
errors::{ColorConfig, Handler},
sync::Lrc,
SourceMap,
};
use swc_ecma_parser::{lexer::Lexer, Capturing, Parser, StringInput, Syntax};
use ir::{type_deps, LiteralKeyMap, RustAlias, RustSegment, RustType, TypeName};
pub fn dts2rs(dts_file: &PathBuf) -> proc_macro2::TokenStream {
let ExtractedModule { module, comments } = extract_module(dts_file);
let mut segments = Vec::new();
let mut st = FrontendState {
segments: &mut segments,
comments: &comments,
name_types: Default::default(),
};
let mut lkm: LiteralKeyMap = HashMap::new();
for b in &module.body {
let b = b.as_module_decl().unwrap();
let b = b.as_export_decl().expect("module have only exports");
let comment = st.get_comment(b.span.lo);
let decl = &b.decl;
match decl {
swc_ecma_ast::Decl::TsInterface(interface) => {
frontend::interface2struct(&mut st, interface, comment, &mut lkm);
}
swc_ecma_ast::Decl::TsTypeAlias(talias) => {
let ident = talias.id.sym.as_ref();
if ident == "WebhookEvents" {
st.segments.push(RustSegment::Alias(RustAlias {
name: "WebhookEvents".to_owned(),
is_borrowed: true,
comment,
ty: RustType::Array(Box::new(RustType::String { is_borrowed: true })),
}));
continue; }
let typ = &talias.type_ann;
match typ.as_ref() {
swc_ecma_ast::TsType::TsTypeRef(tref) => {
let rhs = tref.type_name.as_ident().unwrap().sym.as_ref();
let rhs = rhs.to_owned();
let a = RustSegment::Alias(RustAlias {
name: ident.to_owned(),
is_borrowed: false,
comment,
ty: RustType::Custom(TypeName {
name: rhs,
is_borrowed: false,
}),
});
st.segments.push(a);
}
swc_ecma_ast::TsType::TsUnionOrIntersectionType(tuoi) => {
frontend::tunion2enum(&mut st, ident, tuoi, comment, &mut lkm, true);
}
swc_ecma_ast::TsType::TsKeywordType(..)
| swc_ecma_ast::TsType::TsArrayType(..) => {
let typ =
frontend::ts_type_to_rs(&mut st, &mut None, typ, None, &mut lkm).1;
let a = RustSegment::Alias(RustAlias {
name: ident.to_owned(),
is_borrowed: false,
comment,
ty: typ,
});
st.segments.push(a);
}
swc_ecma_ast::TsType::TsTypeOperator(_toperator) => {
continue;
}
_ => {
dbg!(typ);
unreachable!()
}
}
}
_ => unreachable!(),
};
}
for segment in &mut segments {
transformer::adapt_internal_tag(segment, &lkm);
transformer::adapt_rename_all(segment);
}
transformer::flatten_type(&mut segments);
let type_deps = type_deps(&segments);
transformer::adapt_borrow(&mut segments, &type_deps);
segments
.into_iter()
.flat_map(|rss| rss.into_token_stream())
.collect()
}
struct ExtractedModule {
module: swc_ecma_ast::Module,
comments: swc_common::comments::SingleThreadedComments,
}
fn extract_module(dts_file: &PathBuf) -> ExtractedModule {
let cm: Lrc<SourceMap> = Default::default();
let handler = Handler::with_tty_emitter(ColorConfig::Auto, true, false, Some(cm.clone()));
let fm = cm
.load_file(Path::new(dts_file))
.unwrap_or_else(|_| panic!("failed to load {}", &dts_file.display()));
let comments = swc_common::comments::SingleThreadedComments::default();
let lexer = Lexer::new(
Syntax::Typescript(Default::default()),
Default::default(),
StringInput::from(&*fm),
Some(&comments),
);
let capturing = Capturing::new(lexer);
let mut parser = Parser::new_from(capturing);
for e in parser.take_errors() {
e.into_diagnostic(&handler).emit();
}
ExtractedModule {
module: parser
.parse_module()
.map_err(|e| e.into_diagnostic(&handler).emit())
.expect("Failed to parse module."),
comments,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_module() {
let ExtractedModule { module, .. } = extract_module(&PathBuf::from("test.ts"));
let ice = module.body[1]
.as_module_decl()
.unwrap()
.as_export_decl()
.unwrap()
.decl
.as_ts_type_alias()
.unwrap()
.type_ann
.as_ts_union_or_intersection_type()
.unwrap()
.as_ts_union_type()
.unwrap()
.types[0]
.as_ts_type_ref()
.unwrap()
.type_name
.as_ident()
.unwrap()
.as_ref();
assert_eq!(ice, "IssueCommentCreatedEvent");
}
}