Skip to content

Commit

Permalink
feat: Add fast-check based TypeScript type definition generation (#379)
Browse files Browse the repository at this point in the history
Co-authored-by: David Sherret <dsherret@users.noreply.github.com>
  • Loading branch information
marvinhagemeister and dsherret committed Feb 16, 2024
1 parent 8b9a8ee commit 34681b3
Show file tree
Hide file tree
Showing 39 changed files with 2,340 additions and 34 deletions.
1 change: 1 addition & 0 deletions lib/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ pub async fn js_create_graph(
reporter: None,
workspace_fast_check: false,
workspace_members: Vec::new(),
fast_check_dts: false,
},
)
.await;
Expand Down
4 changes: 4 additions & 0 deletions src/fast_check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
80 changes: 80 additions & 0 deletions src/fast_check/swc_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -187,3 +195,75 @@ fn is_keyword_type(return_type: &TsType, kind: TsKeywordTypeKind) -> bool {
_ => false,
}
}

pub fn any_type_ann() -> Box<TsTypeAnn> {
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<TsTypeAnn> {
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<TsType> {
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<TsType> {
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,
}
}
74 changes: 42 additions & 32 deletions src/fast_check/transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down Expand Up @@ -139,15 +143,23 @@ impl CommentsMut {
}
}

#[derive(Debug, Clone)]
pub struct FastCheckDtsModule {
pub text: String,
pub diagnostics: Vec<FastCheckDtsDiagnostic>,
}

pub struct FastCheckModule {
pub module_info: ModuleInfo,
pub text: String,
pub dts: Option<FastCheckDtsModule>,
pub source_map: Vec<u8>,
}

pub struct TransformOptions<'a> {
pub workspace_members: &'a [WorkspaceMember],
pub should_error_on_first_diagnostic: bool,
pub dts: bool,
}

pub fn transform(
Expand Down Expand Up @@ -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| {
Expand All @@ -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,
})
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -1497,7 +1514,7 @@ fn void_or_promise_void(is_async: bool) -> Box<TsType> {
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],
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -1786,13 +1803,6 @@ fn paren_expr(expr: Box<Expr>) -> Expr {
})
}

fn any_type_ann() -> Box<TsTypeAnn> {
Box::new(TsTypeAnn {
span: DUMMY_SP,
type_ann: Box::new(ts_keyword_type(TsKeywordTypeKind::TsAnyKeyword)),
})
}

fn unknown_type_ann() -> Box<TsTypeAnn> {
Box::new(TsTypeAnn {
span: DUMMY_SP,
Expand Down

0 comments on commit 34681b3

Please sign in to comment.