diff --git a/lib/lib.rs b/lib/lib.rs index 3742f285a..6ba17d7ae 100644 --- a/lib/lib.rs +++ b/lib/lib.rs @@ -260,6 +260,7 @@ pub async fn js_create_graph( reporter: None, workspace_fast_check: false, workspace_members: Vec::new(), + fast_check_dts: false, }, ) .await; diff --git a/src/fast_check/mod.rs b/src/fast_check/mod.rs index 19e731604..e53691142 100644 --- a/src/fast_check/mod.rs +++ b/src/fast_check/mod.rs @@ -11,6 +11,8 @@ mod range_finder; mod swc_helpers; #[cfg(feature = "fast_check")] mod transform; +#[cfg(feature = "fast_check")] +mod transform_dts; use deno_ast::diagnostics::DiagnosticLevel; use deno_ast::diagnostics::DiagnosticLocation; @@ -22,6 +24,8 @@ use deno_ast::diagnostics::DiagnosticSourceRange; use deno_ast::SourceRange; use deno_ast::SourceTextInfo; #[cfg(feature = "fast_check")] +pub use transform::FastCheckDtsModule; +#[cfg(feature = "fast_check")] pub use transform::FastCheckModule; #[cfg(feature = "fast_check")] pub use transform::TransformOptions; diff --git a/src/fast_check/swc_helpers.rs b/src/fast_check/swc_helpers.rs index 34b30b2bb..811089fc2 100644 --- a/src/fast_check/swc_helpers.rs +++ b/src/fast_check/swc_helpers.rs @@ -9,9 +9,17 @@ use deno_ast::swc::ast::PropName; use deno_ast::swc::ast::PropOrSpread; use deno_ast::swc::ast::ReturnStmt; use deno_ast::swc::ast::Stmt; +use deno_ast::swc::ast::TsEntityName; use deno_ast::swc::ast::TsKeywordType; use deno_ast::swc::ast::TsKeywordTypeKind; +use deno_ast::swc::ast::TsLit; +use deno_ast::swc::ast::TsLitType; +use deno_ast::swc::ast::TsTupleElement; use deno_ast::swc::ast::TsType; +use deno_ast::swc::ast::TsTypeAnn; +use deno_ast::swc::ast::TsTypeOperator; +use deno_ast::swc::ast::TsTypeOperatorOp; +use deno_ast::swc::ast::TsTypeRef; use deno_ast::swc::common::DUMMY_SP; pub fn ident(name: String) -> Ident { @@ -187,3 +195,75 @@ fn is_keyword_type(return_type: &TsType, kind: TsKeywordTypeKind) -> bool { _ => false, } } + +pub fn any_type_ann() -> Box { + type_ann(ts_keyword_type(TsKeywordTypeKind::TsAnyKeyword)) +} + +pub fn ts_readonly(ann: TsType) -> TsType { + TsType::TsTypeOperator(TsTypeOperator { + span: DUMMY_SP, + op: TsTypeOperatorOp::ReadOnly, + type_ann: Box::new(ann), + }) +} + +pub fn type_ann(ts_type: TsType) -> Box { + Box::new(TsTypeAnn { + span: DUMMY_SP, + type_ann: Box::new(ts_type), + }) +} + +pub fn type_ref(name: String) -> TsTypeRef { + TsTypeRef { + span: DUMMY_SP, + type_name: TsEntityName::Ident(Ident::new(name.into(), DUMMY_SP)), + type_params: None, + } +} + +pub fn ts_lit_type(lit: TsLit) -> TsType { + TsType::TsLitType(TsLitType { + lit, + span: DUMMY_SP, + }) +} + +pub fn regex_type() -> TsType { + TsType::TsTypeRef(type_ref("RegExp".to_string())) +} + +pub fn ts_tuple_element(ts_type: TsType) -> TsTupleElement { + TsTupleElement { + label: None, + span: DUMMY_SP, + ty: Box::new(ts_type), + } +} + +pub fn maybe_lit_to_ts_type_const(lit: &Lit) -> Option { + match lit { + Lit::Str(lit_str) => Some(ts_lit_type(TsLit::Str(lit_str.clone()))), + Lit::Bool(lit_bool) => Some(ts_lit_type(TsLit::Bool(*lit_bool))), + Lit::Null(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsNullKeyword)), + Lit::Num(lit_num) => Some(ts_lit_type(TsLit::Number(lit_num.clone()))), + Lit::BigInt(lit_bigint) => { + Some(ts_lit_type(TsLit::BigInt(lit_bigint.clone()))) + } + Lit::Regex(_) => Some(regex_type()), + Lit::JSXText(_) => None, + } +} + +pub fn maybe_lit_to_ts_type(lit: &Lit) -> Option { + match lit { + Lit::Str(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsStringKeyword)), + Lit::Bool(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsBooleanKeyword)), + Lit::Null(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsNullKeyword)), + Lit::Num(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsNumberKeyword)), + Lit::BigInt(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsBigIntKeyword)), + Lit::Regex(_) => Some(regex_type()), + Lit::JSXText(_) => None, + } +} diff --git a/src/fast_check/transform.rs b/src/fast_check/transform.rs index a0973abdd..e5e7f0935 100644 --- a/src/fast_check/transform.rs +++ b/src/fast_check/transform.rs @@ -91,15 +91,19 @@ use crate::ModuleInfo; use crate::WorkspaceMember; use super::range_finder::ModulePublicRanges; +use super::swc_helpers::any_type_ann; use super::swc_helpers::get_return_stmts_with_arg_from_function_body; use super::swc_helpers::ident; use super::swc_helpers::is_never_type; use super::swc_helpers::is_void_type; +use super::swc_helpers::maybe_lit_to_ts_type; use super::swc_helpers::ts_keyword_type; +use super::transform_dts::FastCheckDtsDiagnostic; +use super::transform_dts::FastCheckDtsTransformer; use super::FastCheckDiagnostic; use super::FastCheckDiagnosticRange; -struct CommentsMut { +pub struct CommentsMut { leading: SingleThreadedCommentsMapInner, trailing: SingleThreadedCommentsMapInner, } @@ -139,15 +143,23 @@ impl CommentsMut { } } +#[derive(Debug, Clone)] +pub struct FastCheckDtsModule { + pub text: String, + pub diagnostics: Vec, +} + pub struct FastCheckModule { pub module_info: ModuleInfo, pub text: String, + pub dts: Option, pub source_map: Vec, } pub struct TransformOptions<'a> { pub workspace_members: &'a [WorkspaceMember], pub should_error_on_first_diagnostic: bool, + pub dts: bool, } pub fn transform( @@ -175,8 +187,9 @@ pub fn transform( &comments, ); - // now emit let comments = comments.into_single_threaded(); + + // now emit let (text, source_map) = emit(specifier, &comments, parsed_source.text_info(), &module).map_err( |e| { @@ -187,9 +200,33 @@ pub fn transform( }, )?; + let dts = if options.dts { + let mut dts_transformer = + FastCheckDtsTransformer::new(parsed_source, specifier); + + let module = dts_transformer.transform(module)?; + let (text, _source_map) = + emit(specifier, &comments, parsed_source.text_info(), &module).map_err( + |e| { + vec![FastCheckDiagnostic::Emit { + specifier: specifier.clone(), + inner: Arc::new(e), + }] + }, + )?; + + Some(FastCheckDtsModule { + text, + diagnostics: dts_transformer.diagnostics, + }) + } else { + None + }; + Ok(FastCheckModule { module_info, text, + dts, source_map, }) } @@ -1361,27 +1398,7 @@ impl<'a> FastCheckTransformer<'a> { match expr { Expr::TsTypeAssertion(n) => infer_simple_type_from_type(&n.type_ann), Expr::TsAs(n) => infer_simple_type_from_type(&n.type_ann), - Expr::Lit(lit) => match lit { - Lit::Str(_) => { - Some(ts_keyword_type(TsKeywordTypeKind::TsStringKeyword)) - } - Lit::Bool(_) => { - Some(ts_keyword_type(TsKeywordTypeKind::TsBooleanKeyword)) - } - Lit::Null(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsNullKeyword)), - Lit::Num(_) => { - Some(ts_keyword_type(TsKeywordTypeKind::TsNumberKeyword)) - } - Lit::BigInt(_) => { - Some(ts_keyword_type(TsKeywordTypeKind::TsBigIntKeyword)) - } - Lit::Regex(_) => Some(TsType::TsTypeRef(TsTypeRef { - span: DUMMY_SP, - type_name: TsEntityName::Ident(Ident::new("RegExp".into(), DUMMY_SP)), - type_params: None, - })), - Lit::JSXText(_) => None, - }, + Expr::Lit(lit) => maybe_lit_to_ts_type(lit), Expr::Call(call_expr) => { if self.is_call_expr_symbol_create(call_expr) { Some(TsType::TsTypeOperator(TsTypeOperator { @@ -1497,7 +1514,7 @@ fn void_or_promise_void(is_async: bool) -> Box { if is_async { Box::new(TsType::TsTypeRef(TsTypeRef { span: DUMMY_SP, - type_name: TsEntityName::Ident(ident("Promise".into())), + type_name: TsEntityName::Ident(Ident::new("Promise".into(), DUMMY_SP)), type_params: Some(Box::new(TsTypeParamInstantiation { span: DUMMY_SP, params: vec![void_type], @@ -1561,7 +1578,7 @@ fn prefix_idents_in_pat(pat: &mut Pat, prefix: &str) { } } -fn emit( +pub fn emit( specifier: &ModuleSpecifier, comments: &SingleThreadedComments, text_info: &SourceTextInfo, @@ -1786,13 +1803,6 @@ fn paren_expr(expr: Box) -> Expr { }) } -fn any_type_ann() -> Box { - Box::new(TsTypeAnn { - span: DUMMY_SP, - type_ann: Box::new(ts_keyword_type(TsKeywordTypeKind::TsAnyKeyword)), - }) -} - fn unknown_type_ann() -> Box { Box::new(TsTypeAnn { span: DUMMY_SP, diff --git a/src/fast_check/transform_dts.rs b/src/fast_check/transform_dts.rs new file mode 100644 index 000000000..6acb4a584 --- /dev/null +++ b/src/fast_check/transform_dts.rs @@ -0,0 +1,1509 @@ +use deno_ast::{ + swc::{ + ast::{ + BindingIdent, ClassMember, Decl, DefaultDecl, ExportDecl, + ExportDefaultDecl, ExportDefaultExpr, Expr, Ident, Lit, MethodKind, + Module, ModuleDecl, ModuleItem, OptChainBase, Pat, Prop, PropName, + PropOrSpread, Stmt, TsFnOrConstructorType, TsFnParam, TsFnType, + TsKeywordType, TsKeywordTypeKind, TsLit, TsNamespaceBody, + TsPropertySignature, TsTupleElement, TsTupleType, TsType, TsTypeAnn, + TsTypeElement, TsTypeLit, VarDecl, VarDeclKind, VarDeclarator, + }, + common::DUMMY_SP, + }, + ModuleSpecifier, ParsedSource, SourceRange, SourceRangedForSpanned, +}; + +use crate::{FastCheckDiagnostic, FastCheckDiagnosticRange}; + +use super::swc_helpers::{ + any_type_ann, maybe_lit_to_ts_type, maybe_lit_to_ts_type_const, ts_readonly, + ts_tuple_element, type_ann, +}; + +#[derive(Debug, Clone, thiserror::Error)] +pub enum FastCheckDtsDiagnostic { + #[error("unable to infer type from expression or declaration")] + UnableToInferType { range: FastCheckDiagnosticRange }, + #[error("unable to infer type, falling back to any type")] + UnableToInferTypeFallbackAny { range: FastCheckDiagnosticRange }, + #[error("unable to infer type from object property, skipping")] + UnableToInferTypeFromProp { range: FastCheckDiagnosticRange }, + #[error("unable to infer type from spread, skipping")] + UnableToInferTypeFromSpread { range: FastCheckDiagnosticRange }, + #[error("cannot infer type from using, skipping")] + UnsupportedUsing { range: FastCheckDiagnosticRange }, +} + +impl FastCheckDtsDiagnostic { + pub fn specifier(&self) -> &ModuleSpecifier { + match self { + FastCheckDtsDiagnostic::UnableToInferType { range } => &range.specifier, + FastCheckDtsDiagnostic::UnableToInferTypeFallbackAny { range } => { + &range.specifier + } + FastCheckDtsDiagnostic::UnableToInferTypeFromProp { range } => { + &range.specifier + } + FastCheckDtsDiagnostic::UnableToInferTypeFromSpread { range } => { + &range.specifier + } + FastCheckDtsDiagnostic::UnsupportedUsing { range } => &range.specifier, + } + } + + pub fn range(&self) -> Option<&FastCheckDiagnosticRange> { + match self { + FastCheckDtsDiagnostic::UnableToInferType { range } => Some(range), + FastCheckDtsDiagnostic::UnableToInferTypeFallbackAny { range } => { + Some(range) + } + FastCheckDtsDiagnostic::UnableToInferTypeFromProp { range } => { + Some(range) + } + FastCheckDtsDiagnostic::UnableToInferTypeFromSpread { range } => { + Some(range) + } + FastCheckDtsDiagnostic::UnsupportedUsing { range } => Some(range), + } + } +} + +pub struct FastCheckDtsTransformer<'a> { + id_counter: usize, + parsed_source: &'a ParsedSource, + pub diagnostics: Vec, + specifier: &'a ModuleSpecifier, +} + +impl<'a> FastCheckDtsTransformer<'a> { + pub fn new( + parsed_source: &'a ParsedSource, + specifier: &'a ModuleSpecifier, + ) -> Self { + Self { + id_counter: 0, + parsed_source, + specifier, + diagnostics: vec![], + } + } + + fn gen_unique_name(&mut self) -> String { + self.id_counter += 1; + format!("_dts_{}", self.id_counter) + } + + fn mark_diagnostic(&mut self, diagnostic: FastCheckDtsDiagnostic) { + self.diagnostics.push(diagnostic) + } + + fn source_range_to_range( + &self, + range: SourceRange, + ) -> FastCheckDiagnosticRange { + FastCheckDiagnosticRange { + specifier: self.specifier.clone(), + text_info: self.parsed_source.text_info().clone(), + range, + } + } + + fn mark_diagnostic_unable_to_infer(&mut self, range: SourceRange) { + self.mark_diagnostic(FastCheckDtsDiagnostic::UnableToInferType { + range: self.source_range_to_range(range), + }) + } + + fn mark_diagnostic_any_fallback(&mut self, range: SourceRange) { + self.mark_diagnostic(FastCheckDtsDiagnostic::UnableToInferTypeFallbackAny { + range: self.source_range_to_range(range), + }) + } + + fn mark_diagnostic_unsupported_prop(&mut self, range: SourceRange) { + self.mark_diagnostic(FastCheckDtsDiagnostic::UnableToInferTypeFromProp { + range: self.source_range_to_range(range), + }) + } + + pub fn transform( + &mut self, + mut module: Module, + ) -> Result> { + let body = module.body; + + module.body = self.transform_module_items(body); + Ok(module) + } + + fn transform_module_items( + &mut self, + body: Vec, + ) -> Vec { + let mut new_items: Vec = vec![]; + let mut prev_is_overload = false; + + for item in body { + match item { + ModuleItem::ModuleDecl(module_decl) => match module_decl { + ModuleDecl::Import(_) => { + prev_is_overload = false; + new_items.push(ModuleItem::ModuleDecl(module_decl)); + } + ModuleDecl::ExportDecl(export_decl) => { + let is_overload = if let Decl::Fn(fn_decl) = &export_decl.decl { + fn_decl.function.body.is_none() + } else { + false + }; + + let should_keep = prev_is_overload && !is_overload; + prev_is_overload = is_overload; + if should_keep { + continue; + } + + if let Some(decl) = self.decl_to_type_decl(export_decl.decl.clone()) + { + new_items.push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl( + ExportDecl { + decl, + span: export_decl.span, + }, + ))); + } else { + self.mark_diagnostic(FastCheckDtsDiagnostic::UnableToInferType { + range: self.source_range_to_range(export_decl.range()), + }) + } + } + ModuleDecl::ExportDefaultDecl(export_decl) => { + let mut is_overload = false; + + let value = match export_decl.decl { + DefaultDecl::Class(mut class_expr) => { + class_expr.class.body = + self.class_body_to_type(class_expr.class.body); + ExportDefaultDecl { + span: export_decl.span, + decl: DefaultDecl::Class(class_expr), + } + } + DefaultDecl::Fn(mut fn_expr) => { + is_overload = fn_expr.function.body.is_none(); + + fn_expr.function.body = None; + ExportDefaultDecl { + span: export_decl.span, + decl: DefaultDecl::Fn(fn_expr), + } + } + DefaultDecl::TsInterfaceDecl(_) => export_decl, + }; + + let should_keep = prev_is_overload && !is_overload; + prev_is_overload = is_overload; + if should_keep { + continue; + } + + new_items.push(ModuleItem::ModuleDecl( + ModuleDecl::ExportDefaultDecl(value), + )) + } + ModuleDecl::ExportDefaultExpr(export_default_expr) => { + let is_overload = + if let Expr::Fn(fn_expr) = &*export_default_expr.expr { + fn_expr.function.body.is_none() + } else { + false + }; + let should_keep = prev_is_overload && !is_overload; + prev_is_overload = is_overload; + if should_keep { + continue; + } + + let name = self.gen_unique_name(); + let name_ident = Ident::new(name.into(), DUMMY_SP); + let type_ann = self + .expr_to_ts_type(*export_default_expr.expr.clone(), false, true) + .map(type_ann); + + if let Some(type_ann) = type_ann { + new_items.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var( + Box::new(VarDecl { + span: DUMMY_SP, + kind: VarDeclKind::Const, + declare: true, + decls: vec![VarDeclarator { + span: DUMMY_SP, + name: Pat::Ident(BindingIdent { + id: name_ident.clone(), + type_ann: Some(type_ann), + }), + init: None, + definite: false, + }], + }), + )))); + + new_items.push(ModuleItem::ModuleDecl( + ModuleDecl::ExportDefaultExpr(ExportDefaultExpr { + span: export_default_expr.span, + expr: Box::new(Expr::Ident(name_ident)), + }), + )) + } else { + new_items.push(ModuleItem::ModuleDecl( + ModuleDecl::ExportDefaultExpr(ExportDefaultExpr { + span: export_default_expr.span, + expr: export_default_expr.expr, + }), + )) + } + } + // Keep all these + ModuleDecl::TsImportEquals(_) + | ModuleDecl::TsNamespaceExport(_) + | ModuleDecl::TsExportAssignment(_) + | ModuleDecl::ExportNamed(_) + | ModuleDecl::ExportAll(_) => { + prev_is_overload = false; + new_items.push(ModuleItem::ModuleDecl(module_decl)); + } + }, + ModuleItem::Stmt(stmt) => { + prev_is_overload = false; + if let Stmt::Decl(decl) = stmt { + match decl { + Decl::TsEnum(_) + | Decl::Class(_) + | Decl::Fn(_) + | Decl::Var(_) + | Decl::TsModule(_) => { + if let Some(decl) = self.decl_to_type_decl(decl.clone()) { + new_items.push(ModuleItem::Stmt(Stmt::Decl(decl))); + } else { + self.mark_diagnostic_unable_to_infer(decl.range()) + } + } + + Decl::TsInterface(_) | Decl::TsTypeAlias(_) | Decl::Using(_) => { + new_items.push(ModuleItem::Stmt(Stmt::Decl(decl))); + } + } + } + } + } + } + + new_items + } + + fn expr_to_ts_type( + &mut self, + expr: Expr, + as_const: bool, + as_readonly: bool, + ) -> Option { + match expr { + Expr::Array(arr) => { + let mut elem_types: Vec = vec![]; + + for elems in arr.elems { + if let Some(expr_or_spread) = elems { + if let Some(ts_expr) = self.expr_to_ts_type( + *expr_or_spread.expr.clone(), + as_const, + as_readonly, + ) { + elem_types.push(ts_tuple_element(ts_expr)); + } else { + self.mark_diagnostic_unable_to_infer(expr_or_spread.range()); + } + } else { + // TypeScript converts holey arrays to any + // Example: const a = [,,] -> const a = [any, any, any] + elem_types.push(ts_tuple_element(TsType::TsKeywordType( + TsKeywordType { + kind: TsKeywordTypeKind::TsAnyKeyword, + span: DUMMY_SP, + }, + ))) + } + } + + let mut result = TsType::TsTupleType(TsTupleType { + span: arr.span, + elem_types, + }); + + if as_readonly { + result = ts_readonly(result); + } + Some(result) + } + Expr::Object(obj) => { + let mut members: Vec = vec![]; + + // TODO: Prescan all object properties to know which ones + // have a getter or a setter. This allows us to apply + // TypeScript's `readonly` keyword accordingly. + + for item in obj.props { + match item { + PropOrSpread::Prop(prop_box) => { + let prop = *prop_box; + match prop { + Prop::KeyValue(key_value) => { + let (key, computed) = match key_value.key { + PropName::Ident(ident) => (Expr::Ident(ident), false), + PropName::Str(str_prop) => { + (Expr::Lit(Lit::Str(str_prop)), false) + } + PropName::Num(num) => (Expr::Lit(Lit::Num(num)), true), + PropName::Computed(computed) => (*computed.expr, true), + PropName::BigInt(big_int) => { + (Expr::Lit(Lit::BigInt(big_int)), true) + } + }; + + let init_type = self + .expr_to_ts_type(*key_value.value, as_const, as_readonly) + .map(type_ann); + + members.push(TsTypeElement::TsPropertySignature( + TsPropertySignature { + span: DUMMY_SP, + readonly: as_readonly, + key: Box::new(key), + computed, + optional: false, + init: None, + params: vec![], + type_ann: init_type, + type_params: None, + }, + )); + } + Prop::Shorthand(_) + | Prop::Assign(_) + | Prop::Getter(_) + | Prop::Setter(_) + | Prop::Method(_) => { + self.mark_diagnostic_unsupported_prop(prop.range()); + } + } + } + PropOrSpread::Spread(_) => self.mark_diagnostic( + FastCheckDtsDiagnostic::UnableToInferTypeFromSpread { + range: self.source_range_to_range(item.range()), + }, + ), + } + } + + Some(TsType::TsTypeLit(TsTypeLit { + span: obj.span, + members, + })) + } + Expr::Lit(lit) => { + if as_const { + maybe_lit_to_ts_type_const(&lit) + } else { + maybe_lit_to_ts_type(&lit) + } + } + Expr::TsConstAssertion(ts_const) => { + self.expr_to_ts_type(*ts_const.expr, true, true) + } + Expr::TsSatisfies(satisifies) => { + self.expr_to_ts_type(*satisifies.expr, as_const, as_readonly) + } + Expr::TsAs(ts_as) => Some(*ts_as.type_ann), + Expr::Fn(fn_expr) => { + let return_type = fn_expr + .function + .return_type + .map_or(any_type_ann(), |val| val); + + let params: Vec = fn_expr + .function + .params + .into_iter() + .filter_map(|param| self.pat_to_ts_fn_param(param.pat)) + .collect(); + + Some(TsType::TsFnOrConstructorType( + TsFnOrConstructorType::TsFnType(TsFnType { + span: fn_expr.function.span, + params, + type_ann: return_type, + type_params: fn_expr.function.type_params, + }), + )) + } + Expr::Arrow(arrow_expr) => { + let return_type = + arrow_expr.return_type.map_or(any_type_ann(), |val| val); + + let params = arrow_expr + .params + .into_iter() + .filter_map(|pat| self.pat_to_ts_fn_param(pat)) + .collect(); + + Some(TsType::TsFnOrConstructorType( + TsFnOrConstructorType::TsFnType(TsFnType { + span: arrow_expr.span, + params, + type_ann: return_type, + type_params: arrow_expr.type_params, + }), + )) + } + // Since fast check requires explicit type annotations these + // can be dropped as they are not part of an export declaration + Expr::This(_) + | Expr::Unary(_) + | Expr::Update(_) + | Expr::Bin(_) + | Expr::Assign(_) + | Expr::Member(_) + | Expr::SuperProp(_) + | Expr::Cond(_) + | Expr::Call(_) + | Expr::New(_) + | Expr::Seq(_) + | Expr::Ident(_) + | Expr::Tpl(_) + | Expr::TaggedTpl(_) + | Expr::Class(_) + | Expr::Yield(_) + | Expr::MetaProp(_) + | Expr::Await(_) + | Expr::Paren(_) + | Expr::JSXMember(_) + | Expr::JSXNamespacedName(_) + | Expr::JSXEmpty(_) + | Expr::JSXElement(_) + | Expr::JSXFragment(_) + | Expr::TsTypeAssertion(_) + | Expr::TsNonNull(_) + | Expr::TsInstantiation(_) + | Expr::PrivateName(_) + | Expr::OptChain(_) + | Expr::Invalid(_) => None, + } + } + + fn decl_to_type_decl(&mut self, decl: Decl) -> Option { + match decl { + Decl::Class(mut class_decl) => { + class_decl.class.body = self.class_body_to_type(class_decl.class.body); + class_decl.declare = true; + Some(Decl::Class(class_decl)) + } + Decl::Fn(mut fn_decl) => { + fn_decl.function.body = None; + + for param in &mut fn_decl.function.params { + match &mut param.pat { + Pat::Ident(ident) => { + if ident.type_ann.is_none() { + self.mark_diagnostic_any_fallback(ident.range()); + ident.type_ann = Some(any_type_ann()); + } + } + Pat::Assign(assign_pat) => { + match &mut *assign_pat.left { + Pat::Ident(ident) => { + if ident.type_ann.is_none() { + ident.type_ann = self.infer_expr_fallback_any( + *assign_pat.right.clone(), + false, + false, + ); + } + + ident.optional = true; + param.pat = Pat::Ident(ident.clone()); + } + Pat::Array(arr_pat) => { + if arr_pat.type_ann.is_none() { + arr_pat.type_ann = self.infer_expr_fallback_any( + *assign_pat.right.clone(), + false, + false, + ); + } + + arr_pat.optional = true; + param.pat = Pat::Array(arr_pat.clone()); + } + Pat::Object(obj_pat) => { + if obj_pat.type_ann.is_none() { + obj_pat.type_ann = self.infer_expr_fallback_any( + *assign_pat.right.clone(), + false, + false, + ); + } + + obj_pat.optional = true; + param.pat = Pat::Object(obj_pat.clone()); + } + Pat::Rest(_) + | Pat::Assign(_) + | Pat::Expr(_) + | Pat::Invalid(_) => {} + }; + } + Pat::Array(_) + | Pat::Rest(_) + | Pat::Object(_) + | Pat::Invalid(_) + | Pat::Expr(_) => {} + } + } + + Some(Decl::Fn(fn_decl)) + } + Decl::Var(mut var_decl) => { + var_decl.declare = true; + + for decl in &mut var_decl.decls { + if let Pat::Ident(ident) = &mut decl.name { + if ident.type_ann.is_some() { + decl.init = None; + continue; + } + + let ts_type = decl + .init + .as_ref() + .and_then(|init_box| { + let init = *init_box.clone(); + self.expr_to_ts_type(init, false, true) + }) + .map(type_ann) + .or_else(|| { + self.mark_diagnostic_any_fallback(ident.range()); + Some(any_type_ann()) + }); + ident.type_ann = ts_type; + } else { + self.mark_diagnostic_unable_to_infer(decl.range()); + } + + decl.init = None; + } + + Some(Decl::Var(var_decl)) + } + Decl::TsEnum(mut ts_enum) => { + ts_enum.declare = true; + + for member in &mut ts_enum.members { + if let Some(init) = &member.init { + // Support for expressions is limited in enums, + // see https://www.typescriptlang.org/docs/handbook/enums.html + member.init = if self.valid_enum_init_expr(*init.clone()) { + Some(init.clone()) + } else { + None + }; + } + } + + Some(Decl::TsEnum(ts_enum)) + } + Decl::TsModule(mut ts_module) => { + ts_module.declare = true; + + if let Some(body) = ts_module.body.clone() { + ts_module.body = Some(self.transform_ts_ns_body(body)); + + Some(Decl::TsModule(ts_module)) + } else { + Some(Decl::TsModule(ts_module)) + } + } + Decl::TsInterface(_) | Decl::TsTypeAlias(_) => Some(decl), + Decl::Using(_) => { + self.mark_diagnostic(FastCheckDtsDiagnostic::UnsupportedUsing { + range: self.source_range_to_range(decl.range()), + }); + None + } + } + } + + fn transform_ts_ns_body(&mut self, ns: TsNamespaceBody) -> TsNamespaceBody { + match ns { + TsNamespaceBody::TsModuleBlock(mut ts_module_block) => { + ts_module_block.body = + self.transform_module_items(ts_module_block.body); + TsNamespaceBody::TsModuleBlock(ts_module_block) + } + TsNamespaceBody::TsNamespaceDecl(ts_ns) => { + self.transform_ts_ns_body(*ts_ns.body) + } + } + } + + // Support for expressions is limited in enums, + // see https://www.typescriptlang.org/docs/handbook/enums.html + fn valid_enum_init_expr(&mut self, expr: Expr) -> bool { + match expr { + Expr::Bin(bin_expr) => { + if !self.valid_enum_init_expr(*bin_expr.left) { + false + } else { + self.valid_enum_init_expr(*bin_expr.right) + } + } + + Expr::Member(member_expr) => self.valid_enum_init_expr(*member_expr.obj), + Expr::OptChain(opt_expr) => match *opt_expr.base { + OptChainBase::Member(member_expr) => { + self.valid_enum_init_expr(Expr::Member(member_expr)) + } + OptChainBase::Call(_) => false, + }, + // TS does infer the type of identifiers + Expr::Ident(_) => true, + Expr::Lit(lit) => match lit { + Lit::Num(_) | Lit::Str(_) => true, + Lit::Bool(_) + | Lit::Null(_) + | Lit::BigInt(_) + | Lit::Regex(_) + | Lit::JSXText(_) => false, + }, + Expr::Tpl(tpl_expr) => { + for expr in tpl_expr.exprs { + if !self.valid_enum_init_expr(*expr) { + return false; + } + } + true + } + + Expr::Paren(paren_expr) => self.valid_enum_init_expr(*paren_expr.expr), + + Expr::TsTypeAssertion(ts_ass) => { + // Only assertions to number are allowed for computed + // enum members. + match *ts_ass.type_ann { + TsType::TsLitType(ts_lit) => match ts_lit.lit { + TsLit::Number(_) => true, + TsLit::Str(_) + | TsLit::Bool(_) + | TsLit::BigInt(_) + | TsLit::Tpl(_) => false, + }, + TsType::TsKeywordType(_) + | TsType::TsThisType(_) + | TsType::TsFnOrConstructorType(_) + | TsType::TsTypeRef(_) + | TsType::TsTypeQuery(_) + | TsType::TsTypeLit(_) + | TsType::TsArrayType(_) + | TsType::TsTupleType(_) + | TsType::TsOptionalType(_) + | TsType::TsRestType(_) + | TsType::TsUnionOrIntersectionType(_) + | TsType::TsConditionalType(_) + | TsType::TsInferType(_) + | TsType::TsParenthesizedType(_) + | TsType::TsTypeOperator(_) + | TsType::TsIndexedAccessType(_) + | TsType::TsMappedType(_) + | TsType::TsTypePredicate(_) + | TsType::TsImportType(_) => false, + } + } + + Expr::TsAs(ts_as) => self.valid_enum_ts_type(*ts_as.type_ann), + + // These are not valid as enum member initializer and + // TS will throw a type error. For declaration generation + // they will be dropped in TS so we do that too. + Expr::TsInstantiation(_) + | Expr::Call(_) + | Expr::Update(_) + | Expr::PrivateName(_) + | Expr::TsSatisfies(_) + | Expr::TsNonNull(_) + | Expr::TsConstAssertion(_) + | Expr::Cond(_) + | Expr::Seq(_) + | Expr::TaggedTpl(_) + | Expr::Object(_) + | Expr::Array(_) + | Expr::Arrow(_) + | Expr::Class(_) + | Expr::Await(_) + | Expr::MetaProp(_) + | Expr::New(_) + | Expr::JSXMember(_) + | Expr::JSXNamespacedName(_) + | Expr::JSXEmpty(_) + | Expr::JSXElement(_) + | Expr::JSXFragment(_) + | Expr::Unary(_) + | Expr::Assign(_) + | Expr::Yield(_) + | Expr::SuperProp(_) + | Expr::Fn(_) + | Expr::This(_) + | Expr::Invalid(_) => false, + } + } + + fn valid_enum_ts_type(&mut self, ts_type: TsType) -> bool { + match ts_type { + TsType::TsLitType(ts_lit) => match ts_lit.lit { + TsLit::Number(_) => true, + TsLit::Str(_) | TsLit::Bool(_) | TsLit::BigInt(_) | TsLit::Tpl(_) => { + false + } + }, + TsType::TsKeywordType(_) + | TsType::TsThisType(_) + | TsType::TsFnOrConstructorType(_) + | TsType::TsTypeRef(_) + | TsType::TsTypeQuery(_) + | TsType::TsTypeLit(_) + | TsType::TsArrayType(_) + | TsType::TsTupleType(_) + | TsType::TsOptionalType(_) + | TsType::TsRestType(_) + | TsType::TsUnionOrIntersectionType(_) + | TsType::TsConditionalType(_) + | TsType::TsInferType(_) + | TsType::TsParenthesizedType(_) + | TsType::TsTypeOperator(_) + | TsType::TsIndexedAccessType(_) + | TsType::TsMappedType(_) + | TsType::TsTypePredicate(_) + | TsType::TsImportType(_) => false, + } + } + + fn infer_expr_fallback_any( + &mut self, + expr: Expr, + as_const: bool, + as_readonly: bool, + ) -> Option> { + if let Some(ts_type) = + self.expr_to_ts_type(expr.clone(), as_const, as_readonly) + { + Some(type_ann(ts_type)) + } else { + self.mark_diagnostic_any_fallback(expr.range()); + Some(any_type_ann()) + } + } + + fn class_body_to_type(&mut self, body: Vec) -> Vec { + // Track if the previous member was an overload signature or not. + // When overloads are present the last item has the implementation + // body. For declaration files the implementation always needs to + // be dropped. Needs to be unique for each class because another + // class could be created inside a class method. + let mut prev_is_overload = false; + + body + .into_iter() + .filter(|member| match member { + ClassMember::Constructor(class_constructor) => { + let is_overload = class_constructor.body.is_none(); + if !prev_is_overload || is_overload { + prev_is_overload = is_overload; + true + } else { + prev_is_overload = false; + false + } + } + ClassMember::Method(method) => { + let is_overload = method.function.body.is_none(); + if !prev_is_overload || is_overload { + prev_is_overload = is_overload; + true + } else { + prev_is_overload = false; + false + } + } + ClassMember::TsIndexSignature(_) + | ClassMember::ClassProp(_) + | ClassMember::PrivateProp(_) + | ClassMember::Empty(_) + | ClassMember::StaticBlock(_) + | ClassMember::AutoAccessor(_) + | ClassMember::PrivateMethod(_) => { + prev_is_overload = false; + true + } + }) + .filter_map(|member| match member { + ClassMember::Constructor(mut class_constructor) => { + class_constructor.body = None; + Some(ClassMember::Constructor(class_constructor)) + } + ClassMember::Method(mut method) => { + method.function.body = None; + if method.kind == MethodKind::Setter { + method.function.return_type = None; + } + Some(ClassMember::Method(method)) + } + ClassMember::ClassProp(mut prop) => { + if prop.type_ann.is_none() { + if let Some(value) = prop.value { + prop.type_ann = self + .expr_to_ts_type(*value, false, false) + .map(type_ann) + .or_else(|| Some(any_type_ann())); + } + } + prop.value = None; + prop.definite = false; + prop.declare = false; + + Some(ClassMember::ClassProp(prop)) + } + ClassMember::TsIndexSignature(index_sig) => { + Some(ClassMember::TsIndexSignature(index_sig)) + } + + // These can be removed as they are not relevant for types + ClassMember::PrivateMethod(_) + | ClassMember::PrivateProp(_) + | ClassMember::Empty(_) + | ClassMember::StaticBlock(_) + | ClassMember::AutoAccessor(_) => None, + }) + .collect() + } + + fn pat_to_ts_fn_param(&mut self, pat: Pat) -> Option { + match pat { + Pat::Ident(binding_id) => Some(TsFnParam::Ident(binding_id)), + Pat::Array(arr_pat) => Some(TsFnParam::Array(arr_pat)), + Pat::Rest(rest_pat) => Some(TsFnParam::Rest(rest_pat)), + Pat::Object(obj) => Some(TsFnParam::Object(obj)), + Pat::Assign(assign_pat) => self + .expr_to_ts_type(*assign_pat.right, false, false) + .map(|param| { + let name = if let Pat::Ident(ident) = *assign_pat.left { + ident.id.sym.as_str().to_string() + } else { + self.gen_unique_name() + }; + + TsFnParam::Ident(BindingIdent { + id: Ident::new(name.into(), assign_pat.span), + type_ann: Some(type_ann(param)), + }) + }), + Pat::Expr(expr) => { + self.mark_diagnostic_unable_to_infer(expr.range()); + None + } + // Invalid code is invalid, not sure why SWC doesn't throw + // a parse error here. + Pat::Invalid(_) => None, + } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + fast_check::{transform::emit, transform_dts::FastCheckDtsTransformer}, + source::{MemoryLoader, Source}, + symbols::RootSymbol, + BuildOptions, DefaultModuleParser, GraphKind, ModuleGraph, + }; + use url::Url; + + async fn transform_dts_test(source: &str, expected: &str) { + let specifier = Url::parse("file:///mod.ts").unwrap(); + + let mut loader = MemoryLoader::new( + vec![( + specifier.to_string(), + Source::Module { + specifier: specifier.to_string(), + maybe_headers: None, + content: source.to_string(), + }, + )], + vec![], + ); + let parser = DefaultModuleParser {}; + let mut graph = ModuleGraph::new(GraphKind::All); + graph + .build( + vec![specifier.clone()], + &mut loader, + BuildOptions { + module_parser: Some(&parser), + ..Default::default() + }, + ) + .await; + + let root_sym = RootSymbol::new(&graph, &parser); + + let module_info = root_sym + .module_from_specifier(&specifier) + .unwrap() + .esm() + .unwrap(); + + let parsed_source = module_info.source(); + let module = parsed_source.module().to_owned(); + + let mut transformer = + FastCheckDtsTransformer::new(parsed_source, &specifier); + let module = transformer.transform(module).unwrap(); + + let comments = parsed_source.comments().as_single_threaded(); + + let (actual, _) = + emit(&specifier, &comments, parsed_source.text_info(), &module).unwrap(); + + assert_eq!(actual.trim(), expected.trim()); + } + + #[tokio::test] + async fn dts_function_test() { + transform_dts_test( + r#"export function foo(a: number): number { + return {}; +}"#, + "export function foo(a: number): number;", + ) + .await; + transform_dts_test( + r#"export function foo(a: string): number; +export function foo(a: any): number { + return {}; +}"#, + r#"export function foo(a: string): number;"#, + ) + .await; + transform_dts_test( + r#"export function foo(a = 2): number { + return 2; +}"#, + r#"export function foo(a?: number): number;"#, + ) + .await; + transform_dts_test( + r#"export function foo(a: string = 2): number { + return 2; +}"#, + r#"export function foo(a?: string): number;"#, + ) + .await; + transform_dts_test( + r#"export function foo([a, b] = [1, 2]): number { + return 2; +}"#, + r#"export function foo([a, b]?: [number, number]): number;"#, + ) + .await; + transform_dts_test( + r#"export function foo({a, b} = { a: 1, b: 2 }): number { + return 2; +}"#, + r#"export function foo({ a, b }?: { + a: number; + b: number; +}): number;"#, + ) + .await; + } + + #[tokio::test] + async fn dts_class_decl_test() { + transform_dts_test( + r#"export class Foo { + a: number = 2; + static b: number = 1; + #b: number = 3; + constructor(value: string) { + return 42; + } + foo(): string { + return "abc"; + } + #bar(): number { + return 2 + } + get asdf(): number { + + } + set asdf(value: number) { + + } + + static { + + } +}"#, + r#"export declare class Foo { + a: number; + static b: number; + constructor(value: string); + foo(): string; + get asdf(): number; + set asdf(value: number); +}"#, + ) + .await; + } + + #[tokio::test] + async fn dts_class_decl_rest_test() { + transform_dts_test( + r#"export class Foo { + constructor(...args: string[]) {} +}"#, + r#"export declare class Foo { + constructor(...args: string[]); +}"#, + ) + .await; + } + + #[tokio::test] + async fn dts_class_decl_overloads_test() { + transform_dts_test( + r#"export class Foo { + constructor(arg: string); + constructor(arg: number); + constructor(arg: any) {} +}"#, + r#"export declare class Foo { + constructor(arg: string); + constructor(arg: number); +}"#, + ) + .await; + + transform_dts_test( + r#"export class Foo { + foo(arg: string); + foo(arg: number); + foo(arg: any) {} +}"#, + r#"export declare class Foo { + foo(arg: string); + foo(arg: number); +}"#, + ) + .await; + + transform_dts_test( + r#"export class Foo { + constructor(arg: string); + constructor(arg: number); + constructor(arg: any) {} + + bar(arg: number): number { + return 2 + } + + foo(arg: string); + foo(arg: number); + foo(arg: any) {} +}"#, + r#"export declare class Foo { + constructor(arg: string); + constructor(arg: number); + bar(arg: number): number; + foo(arg: string); + foo(arg: number); +}"#, + ) + .await; + } + + #[tokio::test] + async fn dts_class_decl_prop_test() { + transform_dts_test( + r#"export class Foo { private a!: string }"#, + r#"export declare class Foo { + private a: string; +}"#, + ) + .await; + + transform_dts_test( + r#"export class Foo { declare a: string }"#, + r#"export declare class Foo { + a: string; +}"#, + ) + .await; + } + + #[tokio::test] + async fn dts_class_decl_prop_infer_test() { + transform_dts_test( + r#"export class Foo { foo = (a: string): string => ({} as any) }"#, + r#"export declare class Foo { + foo: (a: string) => string; +}"#, + ) + .await; + transform_dts_test( + r#"export class Foo { foo = function(a: string): void {} }"#, + r#"export declare class Foo { + foo: (a: string) => void; +}"#, + ) + .await; + } + + #[tokio::test] + async fn dts_var_decl_test() { + transform_dts_test( + r#"export const foo: number = 42;"#, + "export declare const foo: number;", + ) + .await; + + transform_dts_test( + r#"export var foo: number = 42;"#, + "export declare var foo: number;", + ) + .await; + + transform_dts_test( + r#"export let foo: number = 42;"#, + "export declare let foo: number;", + ) + .await; + + // Default to any if it cannot be determined + transform_dts_test( + r#"export const foo = adsf.b;"#, + "export declare const foo: any;", + ) + .await; + transform_dts_test(r#"export let foo;"#, "export declare let foo: any;") + .await; + } + + #[tokio::test] + async fn dts_global_declare() { + transform_dts_test( + r#"declare global { + interface String { + fancyFormat(opts: StringFormatOptions): string; + } +}"#, + r#"declare global { + interface String { + fancyFormat(opts: StringFormatOptions): string; + } +}"#, + ) + .await; + } + + #[tokio::test] + async fn dts_inference() { + transform_dts_test( + r#"export const foo = null as string as number;"#, + "export declare const foo: number;", + ) + .await; + } + + #[tokio::test] + async fn dts_as_const() { + transform_dts_test( + r#"export const foo = [1, 2] as const;"#, + "export declare const foo: readonly [1, 2];", + ) + .await; + transform_dts_test( + r#"export const foo = [1, ,2] as const;"#, + "export declare const foo: readonly [1, any, 2];", + ) + .await; + + transform_dts_test( + r#"export const foo = { str: "bar", bool: true, bool2: false, num: 42, nullish: null } as const;"#, + r#"export declare const foo: { + readonly str: "bar"; + readonly bool: true; + readonly bool2: false; + readonly num: 42; + readonly nullish: null; +};"#, + ).await; + + transform_dts_test( + r#"export const foo = { str: [1, 2] as const } as const;"#, + r#"export declare const foo: { + readonly str: readonly [1, 2]; +};"#, + ) + .await; + + // TODO: Requires type resolving + transform_dts_test( + r#"export const foo = { bar } as const;"#, + r#"export declare const foo: { +};"#, + ) + .await; + } + + #[tokio::test] + async fn dts_literal_inference_ann() { + transform_dts_test( + r#"export const foo: number = "abc";"#, + "export declare const foo: number;", + ) + .await; + transform_dts_test( + r#"export let foo: number = "abc";"#, + "export declare let foo: number;", + ) + .await; + transform_dts_test( + r#"export var foo: number = "abc";"#, + "export declare var foo: number;", + ) + .await; + } + + #[tokio::test] + async fn dts_literal_inference() { + transform_dts_test( + r#"export const foo = 42;"#, + "export declare const foo: number;", + ) + .await; + transform_dts_test( + r#"export const foo = "foo";"#, + "export declare const foo: string;", + ) + .await; + transform_dts_test( + r#"export const foo = true;"#, + "export declare const foo: boolean;", + ) + .await; + transform_dts_test( + r#"export const foo = false;"#, + "export declare const foo: boolean;", + ) + .await; + transform_dts_test( + r#"export const foo = null;"#, + "export declare const foo: null;", + ) + .await; + transform_dts_test( + r#"export let foo = undefined;"#, + "export declare let foo: any;", + ) + .await; + transform_dts_test( + r#"export let foo = 10n;"#, + "export declare let foo: bigint;", + ) + .await; + transform_dts_test( + r#"export let foo = /foo/;"#, + "export declare let foo: RegExp;", + ) + .await; + } + + #[tokio::test] + async fn dts_fn_expr() { + transform_dts_test( + r#"export let foo = function add(a: number, b: number): number { + return a + b; +}"#, + "export declare let foo: (a: number, b: number) => number;", + ) + .await; + transform_dts_test( + r#"export let foo = function add([a, b]: T): void {}"#, + "export declare let foo: ([a, b]: T) => void;", + ) + .await; + transform_dts_test( + r#"export let foo = function add({a, b}: T): void {}"#, + "export declare let foo: ({ a, b }: T) => void;", + ) + .await; + transform_dts_test( + r#"export let foo = function add(a = 2): void {}"#, + "export declare let foo: (a: number) => void;", + ) + .await; + transform_dts_test( + r#"export let foo = function add(...params: any[]): void {}"#, + "export declare let foo: (...params: any[]) => void;", + ) + .await; + } + + #[tokio::test] + async fn dts_fn_arrow_expr() { + transform_dts_test( + r#"export let foo = (a: number, b: number): number => { + return a + b; +}"#, + "export declare let foo: (a: number, b: number) => number;", + ) + .await; + transform_dts_test( + r#"export let foo = ([a, b]: T): void => {}"#, + "export declare let foo: ([a, b]: T) => void;", + ) + .await; + transform_dts_test( + r#"export let foo = ({a, b}: T): void => {}"#, + "export declare let foo: ({ a, b }: T) => void;", + ) + .await; + transform_dts_test( + r#"export let foo = (a = 2): void => {}"#, + "export declare let foo: (a: number) => void;", + ) + .await; + + transform_dts_test( + r#"export let foo = (...params: any[]): void => {}"#, + "export declare let foo: (...params: any[]) => void;", + ) + .await; + } + + #[tokio::test] + async fn dts_type_export() { + transform_dts_test(r#"interface Foo {}"#, "interface Foo {\n}").await; + transform_dts_test(r#"type Foo = number;"#, "type Foo = number;").await; + + transform_dts_test( + r#"export interface Foo {}"#, + "export interface Foo {\n}", + ) + .await; + transform_dts_test( + r#"export type Foo = number;"#, + "export type Foo = number;", + ) + .await; + } + + #[tokio::test] + async fn dts_enum_export() { + transform_dts_test( + r#"export enum Foo { A, B }"#, + "export declare enum Foo {\n A,\n B\n}", + ) + .await; + transform_dts_test( + r#"export const enum Foo { A, B }"#, + "export declare const enum Foo {\n A,\n B\n}", + ) + .await; + + transform_dts_test( + r#"export enum Foo { A = "foo", B = "bar" }"#, + r#"export declare enum Foo { + A = "foo", + B = "bar" +}"#, + ) + .await; + + // TODO: Enum rules https://www.typescriptlang.org/docs/handbook/enums.html + } + + #[tokio::test] + async fn dts_default_export() { + transform_dts_test( + r#"export default function(a: number, b: number): number {};"#, + "export default function(a: number, b: number): number;", + ) + .await; + transform_dts_test( + r#"export default function(a: number, b: number): number; +export default function(a: number, b: number): any { + return foo +};"#, + "export default function(a: number, b: number): number;", + ) + .await; + transform_dts_test( + r#"export default class {foo = 2};"#, + r#"export default class { + foo: number; +}"#, + ) + .await; + transform_dts_test( + r#"export default 42;"#, + r#"declare const _dts_1: number; +export default _dts_1;"#, + ) + .await; + // TODO + // transform_dts_test( + // r#"const a: number = 42; export default a;"#, + // r#"declare const a: number; + // export default a;"#, + // ) + // .await; + } + + #[tokio::test] + async fn dts_default_export_named() { + transform_dts_test( + r#"export { foo, bar } from "foo";"#, + r#"export { foo, bar } from "foo";"#, + ) + .await; + } + + #[tokio::test] + async fn dts_default_export_all() { + transform_dts_test( + r#"export * as foo from "foo";"#, + r#"export * as foo from "foo";"#, + ) + .await; + } + + #[tokio::test] + async fn dts_imports() { + transform_dts_test( + r#"import { foo } from "bar";export const foobar = foo;"#, + r#"import { foo } from "bar"; +export declare const foobar: any;"#, + ) + .await; + } +} diff --git a/src/graph.rs b/src/graph.rs index afbbeafb9..cb8d62dfd 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -9,6 +9,8 @@ use crate::analyzer::ModuleInfo; use crate::analyzer::PositionRange; use crate::analyzer::SpecifierWithRange; use crate::analyzer::TypeScriptReference; +#[cfg(feature = "fast_check")] +use crate::fast_check::FastCheckDtsModule; use crate::CapturingModuleAnalyzer; use crate::ModuleParser; use crate::ReferrerImports; @@ -814,6 +816,8 @@ pub struct FastCheckTypeModule { pub dependencies: IndexMap, pub source: Arc, pub source_map: Arc<[u8]>, + #[cfg(feature = "fast_check")] + pub dts: Option, } #[derive(Debug, Clone, Serialize)] @@ -985,6 +989,7 @@ pub struct BuildOptions<'a> { /// Whether to fill workspace members with fast check TypeScript data. pub workspace_fast_check: bool, pub workspace_members: Vec, + pub fast_check_dts: bool, } #[derive(Debug, Copy, Clone)] @@ -1423,6 +1428,7 @@ impl ModuleGraph { options.reporter, options.workspace_fast_check, options.workspace_members, + options.fast_check_dts, ) .await } @@ -2748,6 +2754,8 @@ struct Builder<'a, 'graph> { #[cfg_attr(not(feature = "fast_check"), allow(dead_code))] workspace_fast_check: bool, workspace_members: Vec, + #[cfg_attr(not(feature = "fast_check"), allow(dead_code))] + fast_check_dts: bool, diagnostics: Vec, } @@ -2768,6 +2776,7 @@ impl<'a, 'graph> Builder<'a, 'graph> { reporter: Option<&'a dyn Reporter>, workspace_fast_check: bool, workspace_members: Vec, + fast_check_dts: bool, ) -> Vec { let fill_pass_mode = match graph.roots.is_empty() { true => FillPassMode::AllowRestart, @@ -2787,6 +2796,7 @@ impl<'a, 'graph> Builder<'a, 'graph> { fill_pass_mode, workspace_fast_check, workspace_members, + fast_check_dts, diagnostics: Vec::new(), }; builder.fill(roots, imports).await; @@ -3950,6 +3960,7 @@ impl<'a, 'graph> Builder<'a, 'graph> { &[] }, should_error_on_first_diagnostic: !self.workspace_fast_check, + dts: self.fast_check_dts, }, ); for (specifier, fast_check_module_result) in modules { @@ -3979,6 +3990,7 @@ impl<'a, 'graph> Builder<'a, 'graph> { dependencies, source: fast_check_module.text.into(), source_map: fast_check_module.source_map.into(), + dts: fast_check_module.dts, })) } Err(diagnostic) => FastCheckTypeModuleSlot::Error(diagnostic), @@ -5036,4 +5048,74 @@ mod tests { },] ); } + + #[tokio::test] + async fn fast_check_dts() { + struct TestLoader; + impl Loader for TestLoader { + fn load( + &mut self, + specifier: &ModuleSpecifier, + _is_dynamic: bool, + _cache_setting: CacheSetting, + ) -> LoadFuture { + let specifier = specifier.clone(); + match specifier.as_str() { + "file:///foo.ts" => Box::pin(async move { + Ok(Some(LoadResponse::Module { + specifier: specifier.clone(), + maybe_headers: None, + content: b" + export function add(a: number, b: number): number { + return a + b; + } + " + .to_vec() + .into(), + })) + }), + _ => unreachable!(), + } + } + } + + let mut exports = IndexMap::new(); + exports.insert(".".to_string(), "./foo.ts".to_string()); + + let mut graph = ModuleGraph::new(GraphKind::All); + graph + .build( + vec![Url::parse("file:///foo.ts").unwrap()], + &mut TestLoader, + BuildOptions { + workspace_fast_check: true, + fast_check_dts: true, + workspace_members: vec![WorkspaceMember { + base: Url::parse("file:///").unwrap(), + exports, + nv: PackageNv { + name: "foo".to_string(), + version: Version::parse_standard("1.0.0").unwrap(), + }, + }], + ..Default::default() + }, + ) + .await; + graph.valid().unwrap(); + let module = graph.get(&Url::parse("file:///foo.ts").unwrap()).unwrap(); + let module = module.js().unwrap(); + if let FastCheckTypeModuleSlot::Module(fsm) = + module.fast_check.clone().unwrap() + { + let dts = fsm.dts.unwrap(); + assert_eq!( + dts.text.to_string().trim(), + "export function add(a: number, b: number): number;" + ); + assert!(dts.diagnostics.is_empty()); + } else { + panic!() + } + } } diff --git a/tests/helpers/test_builder.rs b/tests/helpers/test_builder.rs index 3e6d45632..faaf781e7 100644 --- a/tests/helpers/test_builder.rs +++ b/tests/helpers/test_builder.rs @@ -131,6 +131,7 @@ impl TestBuilder { module_analyzer: Some(&capturing_analyzer), module_parser: Some(&capturing_analyzer), workspace_fast_check: self.workspace_fast_check, + fast_check_dts: true, ..Default::default() }, ) diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 938df8b60..d4e176896 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -108,6 +108,31 @@ async fn test_graph_specs() { indent(&fast_check.source) }, )); + + if let Some(dts) = &fast_check.dts { + if !dts.text.is_empty() { + output_text.push_str(&indent("--- DTS ---\n")); + output_text.push_str(&indent(&dts.text)); + } + if !dts.diagnostics.is_empty() { + output_text.push_str(&indent("--- DTS Diagnostics ---\n")); + let message = dts + .diagnostics + .iter() + .map(|d| match d.range() { + Some(range) => { + format!( + "{}\n at {}@{}", + d, range.specifier, range.range.start + ) + } + None => format!("{}\n at {}", d, d.specifier()), + }) + .collect::>() + .join("\n"); + output_text.push_str(&indent(&message)); + } + } } deno_graph::FastCheckTypeModuleSlot::Error(diagnostics) => { let message = diagnostics diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck.txt b/tests/specs/graph/JsrSpecifiers_FastCheck.txt index 9a349cbb8..5cc888882 100644 --- a/tests/specs/graph/JsrSpecifiers_FastCheck.txt +++ b/tests/specs/graph/JsrSpecifiers_FastCheck.txt @@ -211,16 +211,29 @@ Fast check https://jsr.io/@scope/a/1.0.0/a.ts: } export { A4 } from "./a4.ts"; export * from "./a6.ts"; + --- DTS --- + export declare class A1 { + } + export declare class A3 { + } + export { A4 } from "./a4.ts"; + export * from "./a6.ts"; Fast check https://jsr.io/@scope/a/1.0.0/a4.ts: {} export class A4 { } + --- DTS --- + export declare class A4 { + } Fast check https://jsr.io/@scope/a/1.0.0/a6.ts: {} export class A6 { } + --- DTS --- + export declare class A6 { + } Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: { @@ -254,3 +267,12 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: } private inner!: unknown; } + --- DTS --- + import { A1, A3 as RenamedA3, A4, A6 } from "./a.ts"; + export declare class Test { + noReturnTypeVoid(param?: number, param2?: string): void; + noReturnTypeAsync(): Promise; + returnTypeGenerator(): Generator; + hasReturnType(): A1 & RenamedA3 | A4 | A6; + private inner: unknown; + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_ClassCtors.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassCtors.txt index df6014dbf..a990c0623 100644 --- a/tests/specs/graph/JsrSpecifiers_FastCheck_ClassCtors.txt +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassCtors.txt @@ -159,3 +159,51 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: } class Public5 { } + --- DTS --- + export declare class ClassBasic { + constructor(prop: string, other?: number); + } + export declare class ClassPrivateCtor { + private constructor(); + } + export declare class ClassPrivateCtorWithOverloads { + private constructor(); + } + export declare class ClassProtectedCtor { + protected constructor(prop: string, other?: number); + } + export declare class ClassProtectedCtorWithOverloads { + protected constructor(prop: Public1); + } + export declare class ClassCtorWithOverloads { + constructor(prop: Public1); + } + export declare class ClassPrivateCtorPublicParamProp { + param1: Public2; + private param2: unknown; + private constructor(); + } + export declare class ClassCtorPublicParamProp { + param1: Public3; + private param2: unknown; + private param3: unknown; + constructor(param1: Public3, param2: Public4, param3?: Public5); + } + export declare class ClassCtorPublicParamPropInit { + param1: Public3; + constructor(param1?: Public3); + } + export declare class ClassCtorPublicParamPropOptional { + param1?: string; + constructor(param1?: string); + } + declare class Public1 { + } + declare class Public2 { + } + declare class Public3 { + } + declare class Public4 { + } + declare class Public5 { + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_ClassGettersAndSetters.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassGettersAndSetters.txt index d4dbbf05c..c9a18b238 100644 --- a/tests/specs/graph/JsrSpecifiers_FastCheck_ClassGettersAndSetters.txt +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassGettersAndSetters.txt @@ -71,3 +71,8 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: } set value(_value: string) {} } + --- DTS --- + export declare class MyClass { + get value(): string; + set value(_value: string); + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_ClassMemberRefFound.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassMemberRefFound.txt index ffee52165..6df4092b3 100644 --- a/tests/specs/graph/JsrSpecifiers_FastCheck_ClassMemberRefFound.txt +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassMemberRefFound.txt @@ -68,3 +68,10 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: class Public1 { prop!: string; } + --- DTS --- + export declare class Export { + prop: typeof Public1.prototype.prop; + } + declare class Public1 { + prop: string; + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_ClassProperties.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassProperties.txt index 73cb6a55b..310d49afc 100644 --- a/tests/specs/graph/JsrSpecifiers_FastCheck_ClassProperties.txt +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassProperties.txt @@ -114,3 +114,22 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: abstract foo: string; bar!: number; } + --- DTS --- + export declare class RedBlackNode extends BinarySearchNode { + parent: RedBlackNode | null; + left: RedBlackNode | null; + right: RedBlackNode | null; + red: boolean; + constructor(parent: RedBlackNode | null, value: T); + static override from(node: RedBlackNode): RedBlackNode; + } + declare const isSecure: Symbol; + declare const public2: Symbol; + export declare class CookieMapBase { + [isSecure]: boolean; + [public2](): number; + } + export declare class Bar { + abstract foo: string; + bar: number; + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_ClassPropertiesLeavable.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassPropertiesLeavable.txt index f33b93704..530434c83 100644 --- a/tests/specs/graph/JsrSpecifiers_FastCheck_ClassPropertiesLeavable.txt +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassPropertiesLeavable.txt @@ -94,6 +94,9 @@ Fast check https://jsr.io/@scope/a/1.0.0/b.ts: {} export type Foo = string; export type Bar = string; + --- DTS --- + export type Foo = string; + export type Bar = string; Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: { @@ -120,3 +123,11 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: bar = (b: Bar): number =>({} as any); bax = function(b: Bax): void {}; } + --- DTS --- + import type { Foo, Bar } from "./b.ts"; + export declare class Foo { + fizz: (a: string) => string; + foo: () => Foo; + bar: (b: Bar) => number; + bax: (b: Bax) => void; + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_ClassStaticMemberRefFound.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassStaticMemberRefFound.txt index 9bf14ef7c..931777193 100644 --- a/tests/specs/graph/JsrSpecifiers_FastCheck_ClassStaticMemberRefFound.txt +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassStaticMemberRefFound.txt @@ -68,3 +68,10 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: class Public1 { static prop: string; } + --- DTS --- + export declare class Export { + prop: typeof Public1.prop; + } + declare class Public1 { + static prop: string; + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_ClassSuper.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassSuper.txt index 58754e999..adb3ab7e8 100644 --- a/tests/specs/graph/JsrSpecifiers_FastCheck_ClassSuper.txt +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassSuper.txt @@ -90,3 +90,16 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: super(...({} as any)); } } + --- DTS --- + declare class Base { + constructor(a: number, b: number); + } + export declare class Child extends Base { + constructor(); + } + declare class SpreadBase { + constructor(...params: string[]); + } + export declare class SpreadChild extends SpreadBase { + constructor(); + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefAliased.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefAliased.txt index dc1c67f07..24841be02 100644 --- a/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefAliased.txt +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefAliased.txt @@ -140,6 +140,10 @@ Fast check https://jsr.io/@scope/a/1.0.0/a.ts: export class A { member!: string; } + --- DTS --- + export declare class A { + member: string; + } Fast check https://jsr.io/@scope/a/1.0.0/b.ts: { @@ -160,6 +164,8 @@ Fast check https://jsr.io/@scope/a/1.0.0/b.ts: } } export { A as B } from "./a.ts"; + --- DTS --- + export { A as B } from "./a.ts"; Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: { @@ -183,3 +189,8 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: export class Export { prop!: typeof B.prototype.member; } + --- DTS --- + import { B } from "./b.ts"; + export declare class Export { + prop: typeof B.prototype.member; + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefImportType.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefImportType.txt index 208d78738..a978bd4c0 100644 --- a/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefImportType.txt +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefImportType.txt @@ -89,6 +89,11 @@ Fast check https://jsr.io/@scope/a/1.0.0/a.ts: } export class A { } + --- DTS --- + export default class Test { + } + export declare class A { + } Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: { @@ -109,3 +114,5 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: } } export type Example = import("./a.ts"); + --- DTS --- + export type Example = import("./a.ts"); diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefImportTypeQualified.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefImportTypeQualified.txt index f9ac82eb9..b654277be 100644 --- a/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefImportTypeQualified.txt +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefImportTypeQualified.txt @@ -87,6 +87,9 @@ Fast check https://jsr.io/@scope/a/1.0.0/a.ts: {} export default class Test { } + --- DTS --- + export default class Test { + } Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: { @@ -107,3 +110,5 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: } } export type Example = import("./a.ts").default; + --- DTS --- + export type Example = import("./a.ts").default; diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefModule.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefModule.txt index b9e5a9cae..fcabfc635 100644 --- a/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefModule.txt +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefModule.txt @@ -143,6 +143,11 @@ Fast check https://jsr.io/@scope/a/1.0.0/a.ts: member!: string; member2!: string; } + --- DTS --- + export declare class A { + member: string; + member2: string; + } Fast check https://jsr.io/@scope/a/1.0.0/b.ts: { @@ -163,6 +168,8 @@ Fast check https://jsr.io/@scope/a/1.0.0/b.ts: } } export { A as B } from "./a.ts"; + --- DTS --- + export { A as B } from "./a.ts"; Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: { @@ -187,3 +194,9 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: prop!: typeof mod.B.prototype.member; prop2!: typeof mod.B.prototype.member2; } + --- DTS --- + import * as mod from "./b.ts"; + export declare class Export { + prop: typeof mod.B.prototype.member; + prop2: typeof mod.B.prototype.member2; + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_DefaultExportExprLeavable.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_DefaultExportExprLeavable.txt index c6b9a7899..af4c745b9 100644 --- a/tests/specs/graph/JsrSpecifiers_FastCheck_DefaultExportExprLeavable.txt +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_DefaultExportExprLeavable.txt @@ -77,3 +77,15 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: "compressible": true } } as const; + --- DTS --- + declare const _dts_1: { + readonly "application/1d-interleaved-parityfec": { + readonly "source": "iana"; + }; + readonly "application/3gpdash-qoe-report+xml": { + readonly "source": "iana"; + readonly "charset": "UTF-8"; + readonly "compressible": true; + }; + }; + export default _dts_1; diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_DefaultExportExprVar.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_DefaultExportExprVar.txt index 825b560ab..48810576e 100644 --- a/tests/specs/graph/JsrSpecifiers_FastCheck_DefaultExportExprVar.txt +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_DefaultExportExprVar.txt @@ -69,3 +69,9 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: } export const $: $Type = {} as any; export default $; + --- DTS --- + export interface $Type { + prop: boolean; + } + export declare const $: $Type; + export default $; diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_Enums.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_Enums.txt index 62e19a3c2..445d9a51c 100644 --- a/tests/specs/graph/JsrSpecifiers_FastCheck_Enums.txt +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_Enums.txt @@ -34,6 +34,18 @@ enum NonExportedEnum { Value } +const NUM = 1 as any; +export enum Foo1 { + A = 1, + B = "2", + C = 1 << 2, + D = 1 + 2, + F = Foo1.A | Foo1.C, + G = Foo1.A + Foo1.C, + H = new Public1().test + 1, + I = 1 + NUM, +} + # mod.ts import 'jsr:@scope/a' @@ -69,7 +81,7 @@ import 'jsr:@scope/a' }, { "kind": "esm", - "size": 421, + "size": 603, "mediaType": "TypeScript", "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" } @@ -108,3 +120,50 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: enum NonExportedEnum { Value } + const NUM: any = {} as any; + export enum Foo1 { + A = 1, + B = "2", + C = 1 << 2, + D = 1 + 2, + F = Foo1.A | Foo1.C, + G = Foo1.A + Foo1.C, + H = new Public1().test + 1, + I = 1 + NUM + } + --- DTS --- + export declare enum EnumWithNoInits { + Value1, + Value2 + } + export declare enum EnumWithNumInits { + Value1 = 1, + Value2 = 2 + } + export declare enum EnumWithStringInits { + Value1 = "a", + Value2 = "b" + } + declare const value: number; + export declare enum EnumWithNonConstInits { + Value1, + Value2 + } + declare class Public1 { + } + declare class Public2 { + } + declare enum NonExportedEnum { + Value + } + declare const NUM: any; + export declare enum Foo1 { + A = 1, + B = "2", + C = 1 << 2, + D = 1 + 2, + F = Foo1.A | Foo1.C, + G = Foo1.A + Foo1.C, + H, + I = 1 + NUM + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_Functions.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_Functions.txt index 536a6191c..f4c8f411a 100644 --- a/tests/specs/graph/JsrSpecifiers_FastCheck_Functions.txt +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_Functions.txt @@ -172,3 +172,32 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: } class PublicOther2 { } + --- DTS --- + export function test1(): void; + export function test2(): number; + export function test3(param: number): number; + export function test4(param?: number): number; + export function test5(param?: number): T; + export function test6(param?: number): T; + export function test7(param?: number): number; + export function test7(param?: number, param2?: PublicOther2): number; + function test8(param: number): number; + function test8(param: string): string; + function test8(param0?: any): any; + export { test8 }; + export default function test9(param: number): number; + export default function test9(param: string): string; + export function test10(...params: string[]): string[]; + export function test11(options?: { + copy: boolean; + }): void; + export interface GlobOptions { + } + export function test12({}?: GlobOptions): void; + export function test13([]?: number[]): void; + export function test14(): never; + export function test15(): Generator; + declare class PublicOther { + } + declare class PublicOther2 { + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_InferredUniqueSymbols.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_InferredUniqueSymbols.txt index 8b7fe1367..9062c4928 100644 --- a/tests/specs/graph/JsrSpecifiers_FastCheck_InferredUniqueSymbols.txt +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_InferredUniqueSymbols.txt @@ -69,3 +69,10 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: [key]!: number; [key2]!: string; } + --- DTS --- + declare const key: unique symbol; + declare const key2: unique symbol; + export declare class MyClass { + [key]: number; + [key2]: string; + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_InitIdentsAndMembers.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_InitIdentsAndMembers.txt index daaa37700..c982b6af2 100644 --- a/tests/specs/graph/JsrSpecifiers_FastCheck_InitIdentsAndMembers.txt +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_InitIdentsAndMembers.txt @@ -135,7 +135,22 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: Private: Public, C: Test.A }; - + --- DTS --- + declare class BaseHandler { + } + declare class Public { + } + declare module Test { + export declare class A { + } + } + export declare const handlers: { + readonly Private; + readonly C; + }; + --- DTS Diagnostics --- + unable to infer type from object property, skipping + at https://jsr.io/@scope/a/1.0.0/mod.ts@176 Fast check https://jsr.io/@scope/b/1.0.0/mod.ts: {} class BaseHandler { @@ -145,3 +160,16 @@ Fast check https://jsr.io/@scope/b/1.0.0/mod.ts: } } export function test(a = BaseHandler, C = Test.A): void {} + --- DTS --- + declare class BaseHandler { + } + declare module Test { + export declare class A { + } + } + export function test(a?: any, C?: any): void; + --- DTS Diagnostics --- + unable to infer type, falling back to any type + at https://jsr.io/@scope/b/1.0.0/mod.ts@140 + unable to infer type, falling back to any type + at https://jsr.io/@scope/b/1.0.0/mod.ts@159 diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_Methods.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_Methods.txt index aa1f0cb61..36bdb1ec3 100644 --- a/tests/specs/graph/JsrSpecifiers_FastCheck_Methods.txt +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_Methods.txt @@ -140,3 +140,25 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: } class PublicOther2 { } + --- DTS --- + export declare class Test { + test1(): void; + test2(): number; + test3(param: number): number; + test4(param?: number): number; + test5(param?: number): T; + test6(param?: number): T; + test7(param?: number): number; + test7(param?: number, param2?: PublicOther2): number; + test8(param: number): number; + test8(param: string): string; + } + export declare class Test2 { + public method1(): void; + protected method2(): void; + private tsPrivateMethod: unknown; + } + declare class PublicOther { + } + declare class PublicOther2 { + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_NestedJavaScriptDeclFile.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_NestedJavaScriptDeclFile.txt index 1fe1d9e24..59aaca442 100644 --- a/tests/specs/graph/JsrSpecifiers_FastCheck_NestedJavaScriptDeclFile.txt +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_NestedJavaScriptDeclFile.txt @@ -189,12 +189,24 @@ Fast check https://jsr.io/@scope/a/1.0.0/a.d.ts: } export class A3 { } + --- DTS --- + export declare class A1 { + method(): string; + } + export declare class A2 { + } + export declare class A3 { + } Fast check https://jsr.io/@scope/a/1.0.0/b.d.ts: {} export class B1 { prop!: string; } + --- DTS --- + export declare class B1 { + prop: string; + } Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: { @@ -237,3 +249,12 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: export { B1 as B2 } from "./b.d.ts"; import { B1 as B3 } from "./b.d.ts"; export { B3 }; + --- DTS --- + export * from "./a.d.ts"; + export { A1 as A4 } from "./a.d.ts"; + import { A3 } from "./a.d.ts"; + export { A3 as A5 }; + export * from "./b.d.ts"; + export { B1 as B2 } from "./b.d.ts"; + import { B1 as B3 } from "./b.d.ts"; + export { B3 }; diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_Properties.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_Properties.txt index b5111f9b5..9f5971419 100644 --- a/tests/specs/graph/JsrSpecifiers_FastCheck_Properties.txt +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_Properties.txt @@ -85,3 +85,15 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: } class PublicOther2 { } + --- DTS --- + export declare class Test { + prop1: PublicOther; + protected prop2?: PublicOther2; + private prop3: unknown; + prop4: number; + static myMember: string; + } + declare class PublicOther { + } + declare class PublicOther2 { + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_RefObjType.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_RefObjType.txt index d636905cc..f93ce7965 100644 --- a/tests/specs/graph/JsrSpecifiers_FastCheck_RefObjType.txt +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_RefObjType.txt @@ -75,3 +75,11 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: /** RFC 8297 **/ EarlyHints: 103 }; export type Status = typeof STATUS_CODE.Continue; + --- DTS --- + export declare const STATUS_CODE: { + readonly Continue: number; + readonly SwitchingProtocols: number; + readonly Processing: number; + readonly EarlyHints: number; + }; + export type Status = typeof STATUS_CODE.Continue; diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_TsAsConst.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_TsAsConst.txt new file mode 100644 index 000000000..c818b8a78 --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_TsAsConst.txt @@ -0,0 +1,108 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +export const foo = [1, 2] as const; +export const bar = [1, , 2] as const; +export const obj = { + str: "bar", + bool: true, + bool2: false, + num: 42, + nullish: null +} as const; + +export const spread = { + foo: 1, + ...obj, +} as const; + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 235, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + {} + export const foo = [ + 1, + 2 + ] as const; + export const bar = [ + 1, + , + 2 + ] as const; + export const obj = { + str: "bar", + bool: true, + bool2: false, + num: 42, + nullish: null + } as const; + export const spread = { + foo: 1, + ...obj + } as const; + --- DTS --- + export declare const foo: readonly [1, 2]; + export declare const bar: readonly [1, any, 2]; + export declare const obj: { + readonly str: "bar"; + readonly bool: true; + readonly bool2: false; + readonly num: 42; + readonly nullish: null; + }; + export declare const spread: { + readonly foo: 1; + }; + --- DTS Diagnostics --- + unable to infer type from spread, skipping + at https://jsr.io/@scope/a/1.0.0/mod.ts@215 diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_TsModule.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_TsModule.txt index 3a50fa7a3..0153591fa 100644 --- a/tests/specs/graph/JsrSpecifiers_FastCheck_TsModule.txt +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_TsModule.txt @@ -71,3 +71,11 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: } class Public1 { } + --- DTS --- + export declare module Test { + export declare class MyClass extends Public1 { + prop: string; + } + } + declare class Public1 { + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_TypeAssertion.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_TypeAssertion.txt index 6cc2fa2fe..bacd9e635 100644 --- a/tests/specs/graph/JsrSpecifiers_FastCheck_TypeAssertion.txt +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_TypeAssertion.txt @@ -111,3 +111,27 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: } interface Public6 { } + --- DTS --- + export declare class Application { + prop: S; + } + export function createMockApp = Record>(state?: S): Application; + export declare class A { + prop: Public1; + constructor(prop?: Public1); + handler: Public2; + secondProp: Public4; + } + export declare const var1: Public5; + interface Public1 { + } + interface Public2 { + } + interface Public3 { + } + interface Public4 { + } + interface Public5 { + } + interface Public6 { + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_Types.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_Types.txt index 175bd90f2..a7caf092b 100644 --- a/tests/specs/graph/JsrSpecifiers_FastCheck_Types.txt +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_Types.txt @@ -68,3 +68,10 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: prop: string; prop2?: number; }; + --- DTS --- + export type NewLocation = [lat: number, long: number]; + export type Value = [string, number]; + export type Value2 = { + prop: string; + prop2?: number; + }; diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_VarArrowLeavable.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_VarArrowLeavable.txt index da0fe6d5d..f1029422b 100644 --- a/tests/specs/graph/JsrSpecifiers_FastCheck_VarArrowLeavable.txt +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_VarArrowLeavable.txt @@ -91,6 +91,10 @@ Fast check https://jsr.io/@scope/a/1.0.0/b.ts: export type Foo = string; export class Bar { } + --- DTS --- + export type Foo = string; + export declare class Bar { + } Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: { @@ -113,3 +117,7 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: import type { Foo, Bar } from "./b.ts"; export const foo = (foo: Foo): string =>({} as any); export const bar = (): Bar =>({} as any); + --- DTS --- + import type { Foo, Bar } from "./b.ts"; + export declare const foo: (foo: Foo) => string; + export declare const bar: () => Bar; diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_VarFunctionLeavable.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_VarFunctionLeavable.txt index 944366ba3..cd17e14ba 100644 --- a/tests/specs/graph/JsrSpecifiers_FastCheck_VarFunctionLeavable.txt +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_VarFunctionLeavable.txt @@ -91,6 +91,10 @@ Fast check https://jsr.io/@scope/a/1.0.0/b.ts: export type Foo = string; export class Bar { } + --- DTS --- + export type Foo = string; + export declare class Bar { + } Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: { @@ -117,3 +121,7 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: export const bar = function(): Bar { return {} as any; }; + --- DTS --- + import type { Foo, Bar } from "./b.ts"; + export declare const foo: (foo: Foo) => string; + export declare const bar: () => Bar; diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_Vars.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_Vars.txt index e42aabcfe..770cd7e04 100644 --- a/tests/specs/graph/JsrSpecifiers_FastCheck_Vars.txt +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_Vars.txt @@ -170,3 +170,47 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: export const inferred10: (1 | 2n)[] = {} as any; export const inferred11: (keyof string)[] = {} as any; export const inferred12: number = {} as any; + --- DTS --- + declare const public1: Public1; + export { public1 }; + declare class Public1 { + } + declare const ERROR_STATUS_MAP: { + readonly "BadRequest": 400; + readonly "Unauthorized": 401; + readonly "PaymentRequired": 402; + readonly "Forbidden": 403; + }; + export type ErrorStatusKeys = keyof typeof ERROR_STATUS_MAP; + export declare const asIs1: readonly [number, number, number]; + export declare const asIs2: { + readonly a: readonly [number, number, number]; + readonly b: boolean; + readonly c: number; + readonly d: bigint; + readonly e; + readonly [1]: number; + readonly f; + readonly g; + readonly h: readonly [readonly [number, number], readonly [string]]; + }; + export declare const func1: () => string; + export declare const func2: () => string; + export declare const func3: () => string; + export declare const func4: () => any; + export declare const func5: () => Promise; + export declare const func6: () => Promise<1>; + export declare const func7: () => Promise<1>; + export declare const func8: () => any; + export declare const inferred1: 1; + export declare const inferred2: string; + export declare const inferred3: true; + export declare const inferred4: RegExp; + export declare const inferred5: boolean; + export declare const inferred6: boolean; + export declare const inferred7: 1 | 2 | 3 & 4; + export declare const inferred8: ["test", 1]; + export declare const inferred9: [...string]; + export declare const inferred10: (1 | 2n)[]; + export declare const inferred11: (keyof string)[]; + export declare const inferred12: number; diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_WorkspaceFastCheck.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_WorkspaceFastCheck.txt index a1d16d888..73af2a647 100644 --- a/tests/specs/graph/JsrSpecifiers_FastCheck_WorkspaceFastCheck.txt +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_WorkspaceFastCheck.txt @@ -130,8 +130,19 @@ Fast check file:///mod.ts: } class Public1 { } + --- DTS --- + import { C } from "jsr:@scope/c"; + export declare class MyClass { + prop: Public1; + c: C; + } + declare class Public1 { + } Fast check https://jsr.io/@scope/c/1.0.0/mod.ts: {} export class C { } + --- DTS --- + export declare class C { + } diff --git a/tests/specs/graph/JsrSpecifiers_SamePackageMultipleTimes.txt b/tests/specs/graph/JsrSpecifiers_SamePackageMultipleTimes.txt index 1b2b8541d..f48402d4d 100644 --- a/tests/specs/graph/JsrSpecifiers_SamePackageMultipleTimes.txt +++ b/tests/specs/graph/JsrSpecifiers_SamePackageMultipleTimes.txt @@ -159,3 +159,7 @@ Fast check https://jsr.io/@scope/b/1.1.0/mod.ts: export class Test { method(value: string): void {} } + --- DTS --- + export declare class Test { + method(value: string): void; + }