diff --git a/Cargo.lock b/Cargo.lock index ac4bcca..9201149 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,19 @@ dependencies = [ "version_check", ] +[[package]] +name = "ahash" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -399,7 +412,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash", + "ahash 0.7.7", ] [[package]] @@ -1199,6 +1212,7 @@ dependencies = [ name = "swc-vue-jsx-visitor" version = "0.2.7" dependencies = [ + "ahash 0.8.7", "bitflags 2.4.2", "css_dataset", "indexmap", @@ -2026,3 +2040,23 @@ name = "yansi" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] diff --git a/visitor/Cargo.toml b/visitor/Cargo.toml index 6bd0c52..7b4696f 100644 --- a/visitor/Cargo.toml +++ b/visitor/Cargo.toml @@ -8,6 +8,7 @@ repository = "https://github.com/g-plane/swc-plugin-vue-jsx" edition = "2021" [dependencies] +ahash = "0.8" bitflags = "2.4" css_dataset = "0.3" indexmap = "2.1" diff --git a/visitor/src/lib.rs b/visitor/src/lib.rs index 2379b27..bf5f20e 100644 --- a/visitor/src/lib.rs +++ b/visitor/src/lib.rs @@ -1,3 +1,4 @@ +use ahash::AHashMap; use directive::{is_directive, parse_directive, Directive, NormalDirective}; use indexmap::IndexSet; pub use options::{Options, Regex}; @@ -18,6 +19,7 @@ use swc_core::{ mod directive; mod options; mod patch_flags; +mod resolve_type; mod slot_flag; mod util; @@ -38,7 +40,10 @@ where options: Options, vue_imports: BTreeMap<&'static str, Ident>, transform_on_helper: Option, + define_component: Option, + interfaces: AHashMap<(JsWord, SyntaxContext), TsInterfaceDecl>, + type_aliases: AHashMap<(JsWord, SyntaxContext), TsType>, unresolved_mark: Mark, comments: Option, @@ -62,7 +67,10 @@ where options, vue_imports: Default::default(), transform_on_helper: None, + define_component: None, + interfaces: Default::default(), + type_aliases: Default::default(), unresolved_mark, comments, @@ -1100,6 +1108,17 @@ where elems } } + + fn is_define_component_call(&self, CallExpr { callee, .. }: &CallExpr) -> bool { + callee + .as_expr() + .and_then(|expr| expr.as_ident()) + .and_then(|ident| { + self.define_component + .map(|ctxt| ctxt == ident.span.ctxt() && ident.sym == "defineComponent") + }) + .unwrap_or_default() + } } impl VisitMut for VueJsxTransformVisitor @@ -1356,4 +1375,93 @@ where self.define_component = Some(ctxt); } } + + fn visit_mut_ts_interface_decl(&mut self, ts_interface_decl: &mut TsInterfaceDecl) { + ts_interface_decl.visit_mut_children_with(self); + if self.options.resolve_type { + let key = ( + ts_interface_decl.id.sym.clone(), + ts_interface_decl.id.span.ctxt(), + ); + if let Some(interface) = self.interfaces.get_mut(&key) { + interface + .body + .body + .extend_from_slice(&ts_interface_decl.body.body); + } else { + self.interfaces.insert(key, ts_interface_decl.clone()); + } + } + } + + fn visit_mut_ts_type_alias_decl(&mut self, ts_type_alias_decl: &mut TsTypeAliasDecl) { + ts_type_alias_decl.visit_mut_children_with(self); + if self.options.resolve_type { + self.type_aliases.insert( + ( + ts_type_alias_decl.id.sym.clone(), + ts_type_alias_decl.id.span.ctxt(), + ), + (*ts_type_alias_decl.type_ann).clone(), + ); + } + } + + fn visit_mut_call_expr(&mut self, call_expr: &mut CallExpr) { + call_expr.visit_mut_children_with(self); + + if !self.options.resolve_type { + return; + } + + if !self.is_define_component_call(call_expr) { + return; + } + + let Some((maybe_setup, args)) = call_expr.args.split_first_mut() else { + return; + }; + + match args.first_mut().map(|arg| &mut *arg.expr) { + Some(Expr::Object(object)) => { + if !object.props.iter().any(|prop| { + prop.as_prop() + .and_then(|prop| prop.as_key_value()) + .and_then(|key_value| key_value.key.as_ident()) + .map(|ident| ident.sym == "props") + .unwrap_or_default() + }) { + if let Some(prop_types) = self.extract_props_type(maybe_setup) { + object + .props + .push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp { + key: PropName::Ident(quote_ident!("props")), + value: Box::new(Expr::Object(prop_types)), + })))); + } + } + } + None => { + let mut props = vec![]; + + if let Some(prop_types) = self.extract_props_type(maybe_setup) { + props.push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp { + key: PropName::Ident(quote_ident!("props")), + value: Box::new(Expr::Object(prop_types)), + })))); + } + + if !props.is_empty() { + call_expr.args.push(ExprOrSpread { + expr: Box::new(Expr::Object(ObjectLit { + props, + span: DUMMY_SP, + })), + spread: None, + }); + } + } + _ => {} + } + } } diff --git a/visitor/src/options.rs b/visitor/src/options.rs index cd14de3..8c67387 100644 --- a/visitor/src/options.rs +++ b/visitor/src/options.rs @@ -20,6 +20,8 @@ pub struct Options { pub enable_object_slots: bool, pub pragma: Option, + + pub resolve_type: bool, } impl Default for Options { @@ -31,6 +33,7 @@ impl Default for Options { merge_props: true, enable_object_slots: true, pragma: None, + resolve_type: false, } } } diff --git a/visitor/src/resolve_type.rs b/visitor/src/resolve_type.rs new file mode 100644 index 0000000..c725b0c --- /dev/null +++ b/visitor/src/resolve_type.rs @@ -0,0 +1,923 @@ +use crate::VueJsxTransformVisitor; +use indexmap::{IndexMap, IndexSet}; +use swc_core::{ + common::{comments::Comments, EqIgnoreSpan, Spanned, DUMMY_SP}, + ecma::{ + ast::*, + atoms::{js_word, JsWord}, + utils::quote_ident, + }, + plugin::errors::HANDLER, +}; + +enum RefinedTsTypeElement { + Property(TsPropertySignature), + GetterSignature(TsGetterSignature), + MethodSignature(TsMethodSignature), +} + +struct PropIr { + types: IndexSet>, + required: bool, +} + +impl VueJsxTransformVisitor +where + C: Comments, +{ + pub(crate) fn extract_props_type(&self, setup_fn: &ExprOrSpread) -> Option { + let Some(first_param_type) = (if let ExprOrSpread { expr, spread: None } = setup_fn { + match &**expr { + Expr::Arrow(arrow) => match arrow.params.first() { + Some(Pat::Ident(ident)) => ident.type_ann.as_deref(), + Some(Pat::Array(array)) => array.type_ann.as_deref(), + Some(Pat::Object(object)) => object.type_ann.as_deref(), + _ => return None, + }, + Expr::Fn(fn_expr) => { + match fn_expr.function.params.first().map(|param| ¶m.pat) { + Some(Pat::Ident(ident)) => ident.type_ann.as_deref(), + Some(Pat::Array(array)) => array.type_ann.as_deref(), + Some(Pat::Object(object)) => object.type_ann.as_deref(), + _ => return None, + } + } + _ => return None, + } + } else { + return None; + }) else { + return None; + }; + + Some(self.build_props_type(first_param_type)) + } + + fn build_props_type(&self, TsTypeAnn { type_ann, .. }: &TsTypeAnn) -> ObjectLit { + let mut props = Vec::with_capacity(3); + self.resolve_props(type_ann, &mut props); + + let cap = props.len(); + let irs = props.into_iter().fold( + IndexMap::::with_capacity(cap), + |mut irs, prop| { + match prop { + RefinedTsTypeElement::Property(TsPropertySignature { + key, + computed, + optional, + type_ann, + .. + }) + | RefinedTsTypeElement::GetterSignature(TsGetterSignature { + key, + computed, + optional, + type_ann, + .. + }) => { + let prop_name = extract_prop_name(*key, computed); + let types = if let Some(type_ann) = type_ann { + self.infer_runtime_type(&type_ann.type_ann) + } else { + let mut types = IndexSet::with_capacity(1); + types.insert(None); + types + }; + if let Some((_, ir)) = irs + .iter_mut() + .find(|(key, _)| prop_name.eq_ignore_span(key)) + { + if optional { + ir.required = false; + } + ir.types.extend(types); + } else { + irs.insert( + prop_name, + PropIr { + types, + required: !optional, + }, + ); + } + } + RefinedTsTypeElement::MethodSignature(TsMethodSignature { + key, + computed, + optional, + .. + }) => { + let prop_name = extract_prop_name(*key, computed); + let ty = Some(js_word!("Function")); + if let Some((_, ir)) = irs + .iter_mut() + .find(|(key, _)| prop_name.eq_ignore_span(key)) + { + if optional { + ir.required = false; + } + ir.types.insert(ty); + } else { + let mut types = IndexSet::with_capacity(1); + types.insert(ty); + irs.insert( + prop_name, + PropIr { + types, + required: !optional, + }, + ); + } + } + } + irs + }, + ); + + ObjectLit { + props: irs + .into_iter() + .map(|(prop_name, mut ir)| { + PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp { + key: prop_name, + value: Box::new(Expr::Object(ObjectLit { + props: vec![ + PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp { + key: PropName::Ident(quote_ident!("type")), + value: Box::new(if ir.types.len() == 1 { + if let Some(ty) = ir.types.pop().unwrap() { + Expr::Ident(quote_ident!(ty)) + } else { + Expr::Lit(Lit::Null(Null { span: DUMMY_SP })) + } + } else { + Expr::Array(ArrayLit { + elems: ir + .types + .into_iter() + .map(|ty| { + Some(ExprOrSpread { + expr: Box::new(if let Some(ty) = ty { + Expr::Ident(quote_ident!(ty)) + } else { + Expr::Lit(Lit::Null(Null { + span: DUMMY_SP, + })) + }), + spread: None, + }) + }) + .collect(), + span: DUMMY_SP, + }) + }), + }))), + PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp { + key: PropName::Ident(quote_ident!("required")), + value: Box::new(Expr::Lit(Lit::Bool(Bool { + value: ir.required, + span: DUMMY_SP, + }))), + }))), + ], + span: DUMMY_SP, + })), + }))) + }) + .collect(), + span: DUMMY_SP, + } + } + + fn resolve_props(&self, ty: &TsType, props: &mut Vec) { + match ty { + TsType::TsTypeLit(TsTypeLit { members, .. }) => { + props.extend(members.iter().filter_map(|member| match member { + TsTypeElement::TsPropertySignature(prop) => { + Some(RefinedTsTypeElement::Property(prop.clone())) + } + TsTypeElement::TsMethodSignature(method) => { + Some(RefinedTsTypeElement::MethodSignature(method.clone())) + } + TsTypeElement::TsGetterSignature(getter) => { + Some(RefinedTsTypeElement::GetterSignature(getter.clone())) + } + _ => None, + })); + } + TsType::TsUnionOrIntersectionType( + TsUnionOrIntersectionType::TsIntersectionType(TsIntersectionType { types, .. }) + | TsUnionOrIntersectionType::TsUnionType(TsUnionType { types, .. }), + ) => { + types.iter().for_each(|ty| self.resolve_props(ty, props)); + } + TsType::TsTypeRef(TsTypeRef { + type_name: TsEntityName::Ident(ident), + type_params, + span, + .. + }) => { + let key = (ident.sym.clone(), ident.span.ctxt()); + if let Some(aliased) = self.type_aliases.get(&key) { + self.resolve_props(aliased, props); + } else if let Some(TsInterfaceDecl { + extends, + body: TsInterfaceBody { body, .. }, + .. + }) = self.interfaces.get(&key) + { + props.extend(body.iter().filter_map(|element| match element { + TsTypeElement::TsPropertySignature(prop) => { + Some(RefinedTsTypeElement::Property(prop.clone())) + } + TsTypeElement::TsMethodSignature(method) => { + Some(RefinedTsTypeElement::MethodSignature(method.clone())) + } + TsTypeElement::TsGetterSignature(getter) => { + Some(RefinedTsTypeElement::GetterSignature(getter.clone())) + } + _ => None, + })); + extends + .iter() + .filter_map(|parent| parent.expr.as_ident()) + .for_each(|ident| { + self.resolve_props( + &TsType::TsTypeRef(TsTypeRef { + type_name: TsEntityName::Ident(ident.clone()), + type_params: None, + span: DUMMY_SP, + }), + props, + ) + }); + } else if ident.span.ctxt().has_mark(self.unresolved_mark) { + match &*ident.sym { + "Partial" => { + if let Some(param) = type_params + .as_deref() + .and_then(|params| params.params.first()) + { + let mut inner_props = vec![]; + self.resolve_props(param, &mut inner_props); + props.extend(inner_props.into_iter().map(|mut prop| { + match &mut prop { + RefinedTsTypeElement::Property(property) => { + property.optional = true; + } + RefinedTsTypeElement::MethodSignature(method) => { + method.optional = true; + } + RefinedTsTypeElement::GetterSignature(getter) => { + getter.optional = true; + } + } + prop + })); + } + } + "Required" => { + if let Some(param) = type_params + .as_deref() + .and_then(|params| params.params.first()) + { + let mut inner_props = vec![]; + self.resolve_props(param, &mut inner_props); + props.extend(inner_props.into_iter().map(|mut prop| { + match &mut prop { + RefinedTsTypeElement::Property(TsPropertySignature { + optional, + .. + }) + | RefinedTsTypeElement::MethodSignature( + TsMethodSignature { optional, .. }, + ) + | RefinedTsTypeElement::GetterSignature( + TsGetterSignature { optional, .. }, + ) => { + *optional = false; + } + } + prop + })); + } + } + "Pick" => { + if let Some((object, keys)) = type_params + .as_deref() + .and_then(|params| params.params.first().zip(params.params.get(1))) + { + let keys = self.resolve_index_keys(keys); + let mut inner_props = vec![]; + self.resolve_props(object, &mut inner_props); + props.extend(inner_props.into_iter().filter(|prop| match prop { + RefinedTsTypeElement::Property(TsPropertySignature { + key, + .. + }) + | RefinedTsTypeElement::MethodSignature(TsMethodSignature { + key, + .. + }) + | RefinedTsTypeElement::GetterSignature(TsGetterSignature { + key, + .. + }) => match &**key { + Expr::Ident(ident) => keys.contains(&ident.sym), + Expr::Lit(Lit::Str(str)) => keys.contains(&str.value), + _ => false, + }, + })); + } + } + "Omit" => { + if let Some((object, keys)) = type_params + .as_deref() + .and_then(|params| params.params.first().zip(params.params.get(1))) + { + let keys = self.resolve_index_keys(keys); + let mut inner_props = vec![]; + self.resolve_props(object, &mut inner_props); + props.extend(inner_props.into_iter().filter(|prop| match prop { + RefinedTsTypeElement::Property(TsPropertySignature { + key, + .. + }) + | RefinedTsTypeElement::MethodSignature(TsMethodSignature { + key, + .. + }) + | RefinedTsTypeElement::GetterSignature(TsGetterSignature { + key, + .. + }) => match &**key { + Expr::Ident(ident) => !keys.contains(&ident.sym), + Expr::Lit(Lit::Str(str)) => !keys.contains(&str.value), + _ => true, + }, + })); + } + } + _ => { + HANDLER.with(|handler| { + handler.span_err( + *span, + "Unresolvable type reference or unsupported built-in utility type.", + ); + }); + } + } + } else { + HANDLER.with(|handler| { + handler.span_err(*span, "Types from other modules can't be resolved."); + }); + } + } + TsType::TsIndexedAccessType(TsIndexedAccessType { + obj_type, + index_type, + .. + }) => { + if let Some(ty) = self.resolve_indexed_access(obj_type, index_type) { + self.resolve_props(&ty, props); + } else { + HANDLER.with(|handler| { + handler.span_err(ty.span(), "Unresolvable type."); + }); + } + } + TsType::TsParenthesizedType(TsParenthesizedType { type_ann, .. }) + | TsType::TsOptionalType(TsOptionalType { type_ann, .. }) => { + self.resolve_props(type_ann, props); + } + _ => HANDLER.with(|handler| { + handler.span_err(ty.span(), "Unresolvable type."); + }), + } + } + + fn resolve_index_keys(&self, ty: &TsType) -> Vec { + match ty { + TsType::TsLitType(TsLitType { + lit: TsLit::Str(key), + .. + }) => vec![key.value.clone()], + TsType::TsUnionOrIntersectionType(TsUnionOrIntersectionType::TsUnionType( + TsUnionType { types, .. }, + )) => types + .iter() + .filter_map(|ty| ty.as_ts_lit_type().and_then(|lit| lit.lit.as_str())) + .map(|str| str.value.clone()) + .collect(), + TsType::TsTypeRef(TsTypeRef { + type_name: TsEntityName::Ident(ident), + .. + }) => { + if let Some(aliased) = self + .type_aliases + .get(&(ident.sym.clone(), ident.span.ctxt())) + { + self.resolve_index_keys(aliased) + } else if ident.span.ctxt().has_mark(self.unresolved_mark) { + HANDLER.with(|handler| { + handler.span_err( + ty.span(), + "Unresolvable type reference or unsupported built-in utility type.", + ); + }); + vec![] + } else { + HANDLER.with(|handler| { + handler.span_err(ty.span(), "Types from other modules can't be resolved."); + }); + vec![] + } + } + _ => { + HANDLER + .with(|handler| handler.span_err(ty.span(), "Unsupported type as index key.")); + vec![] + } + } + } + + fn resolve_indexed_access(&self, obj: &TsType, index: &TsType) -> Option { + match obj { + TsType::TsTypeRef(TsTypeRef { + type_name: TsEntityName::Ident(ident), + type_params, + .. + }) => { + let key = (ident.sym.clone(), ident.span.ctxt()); + if let Some(aliased) = self.type_aliases.get(&key) { + self.resolve_indexed_access(aliased, index) + } else if let Some(interface) = self.interfaces.get(&key) { + let mut properties = match index { + TsType::TsKeywordType(TsKeywordType { + kind: TsKeywordTypeKind::TsStringKeyword, + .. + }) => interface + .body + .body + .iter() + .filter_map(|element| match element { + TsTypeElement::TsCallSignatureDecl(..) + | TsTypeElement::TsConstructSignatureDecl(..) + | TsTypeElement::TsSetterSignature(..) => None, + TsTypeElement::TsPropertySignature(TsPropertySignature { + key, + type_ann, + .. + }) + | TsTypeElement::TsGetterSignature(TsGetterSignature { + key, + type_ann, + .. + }) => { + if matches!(&**key, Expr::Ident(..) | Expr::Lit(Lit::Str(..))) { + type_ann.as_ref().map(|type_ann| type_ann.type_ann.clone()) + } else { + None + } + } + TsTypeElement::TsIndexSignature(TsIndexSignature { + type_ann, + .. + }) => type_ann.as_ref().map(|type_ann| type_ann.type_ann.clone()), + TsTypeElement::TsMethodSignature(..) => { + Some(Box::new(TsType::TsTypeRef(TsTypeRef { + type_name: TsEntityName::Ident(quote_ident!("Function")), + type_params: None, + span: DUMMY_SP, + }))) + } + }) + .collect(), + TsType::TsLitType(TsLitType { + lit: TsLit::Str(..), + .. + }) + | TsType::TsUnionOrIntersectionType( + TsUnionOrIntersectionType::TsUnionType(..), + ) + | TsType::TsTypeRef(..) => { + let keys = self.resolve_index_keys(index); + interface + .body + .body + .iter() + .filter_map(|element| match element { + TsTypeElement::TsPropertySignature(TsPropertySignature { + key, + type_ann, + .. + }) + | TsTypeElement::TsGetterSignature(TsGetterSignature { + key, + type_ann, + .. + }) => { + if let Expr::Ident(Ident { sym: key, .. }) + | Expr::Lit(Lit::Str(Str { value: key, .. })) = &**key + { + if keys.contains(key) { + type_ann + .as_ref() + .map(|type_ann| type_ann.type_ann.clone()) + } else { + None + } + } else { + None + } + } + TsTypeElement::TsMethodSignature(TsMethodSignature { + key, + .. + }) => { + if let Expr::Ident(Ident { sym: key, .. }) + | Expr::Lit(Lit::Str(Str { value: key, .. })) = &**key + { + if keys.contains(key) { + Some(Box::new(TsType::TsTypeRef(TsTypeRef { + type_name: TsEntityName::Ident(quote_ident!( + "Function" + )), + type_params: None, + span: DUMMY_SP, + }))) + } else { + None + } + } else { + None + } + } + TsTypeElement::TsCallSignatureDecl(..) + | TsTypeElement::TsConstructSignatureDecl(..) + | TsTypeElement::TsSetterSignature(..) + | TsTypeElement::TsIndexSignature(..) => None, + }) + .collect() + } + _ => vec![], + }; + if properties.len() == 1 { + Some((*properties.remove(0)).clone()) + } else { + Some(TsType::TsUnionOrIntersectionType( + TsUnionOrIntersectionType::TsUnionType(TsUnionType { + types: properties, + span: DUMMY_SP, + }), + )) + } + } else if ident.span.ctxt().has_mark(self.unresolved_mark) { + if ident.sym == "Array" { + type_params + .as_ref() + .and_then(|params| params.params.first()) + .map(|ty| (**ty).clone()) + } else { + None + } + } else { + None + } + } + TsType::TsTypeLit(TsTypeLit { members, .. }) => { + let mut properties = match index { + TsType::TsKeywordType(TsKeywordType { + kind: TsKeywordTypeKind::TsStringKeyword, + .. + }) => members + .iter() + .filter_map(|member| match member { + TsTypeElement::TsCallSignatureDecl(..) + | TsTypeElement::TsConstructSignatureDecl(..) + | TsTypeElement::TsSetterSignature(..) => None, + TsTypeElement::TsPropertySignature(TsPropertySignature { + key, + type_ann, + .. + }) + | TsTypeElement::TsGetterSignature(TsGetterSignature { + key, + type_ann, + .. + }) => { + if matches!(&**key, Expr::Ident(..) | Expr::Lit(Lit::Str(..))) { + type_ann.as_ref().map(|type_ann| type_ann.type_ann.clone()) + } else { + None + } + } + TsTypeElement::TsIndexSignature(TsIndexSignature { + type_ann, .. + }) => type_ann.as_ref().map(|type_ann| type_ann.type_ann.clone()), + TsTypeElement::TsMethodSignature(..) => { + Some(Box::new(TsType::TsTypeRef(TsTypeRef { + type_name: TsEntityName::Ident(quote_ident!("Function")), + type_params: None, + span: DUMMY_SP, + }))) + } + }) + .collect(), + TsType::TsLitType(TsLitType { + lit: TsLit::Str(..), + .. + }) + | TsType::TsTypeRef(..) + | TsType::TsUnionOrIntersectionType(TsUnionOrIntersectionType::TsUnionType( + .., + )) => { + let keys = self.resolve_index_keys(index); + members + .iter() + .filter_map(|member| match member { + TsTypeElement::TsPropertySignature(TsPropertySignature { + key, + type_ann, + .. + }) + | TsTypeElement::TsGetterSignature(TsGetterSignature { + key, + type_ann, + .. + }) => { + if let Expr::Ident(Ident { sym: key, .. }) + | Expr::Lit(Lit::Str(Str { value: key, .. })) = &**key + { + if keys.contains(key) { + type_ann + .as_ref() + .map(|type_ann| type_ann.type_ann.clone()) + } else { + None + } + } else { + None + } + } + TsTypeElement::TsMethodSignature(TsMethodSignature { + key, .. + }) => { + if let Expr::Ident(Ident { sym: key, .. }) + | Expr::Lit(Lit::Str(Str { value: key, .. })) = &**key + { + if keys.contains(key) { + Some(Box::new(TsType::TsTypeRef(TsTypeRef { + type_name: TsEntityName::Ident(quote_ident!( + "Function" + )), + type_params: None, + span: DUMMY_SP, + }))) + } else { + None + } + } else { + None + } + } + TsTypeElement::TsCallSignatureDecl(..) + | TsTypeElement::TsConstructSignatureDecl(..) + | TsTypeElement::TsSetterSignature(..) + | TsTypeElement::TsIndexSignature(..) => None, + }) + .collect() + } + _ => vec![], + }; + if properties.len() == 1 { + Some(*properties.remove(0)) + } else { + Some(TsType::TsUnionOrIntersectionType( + TsUnionOrIntersectionType::TsUnionType(TsUnionType { + types: properties, + span: DUMMY_SP, + }), + )) + } + } + TsType::TsArrayType(TsArrayType { elem_type, .. }) => { + if matches!( + index, + TsType::TsKeywordType(TsKeywordType { + kind: TsKeywordTypeKind::TsNumberKeyword, + .. + }) | TsType::TsLitType(TsLitType { + lit: TsLit::Number(..), + .. + }) + ) { + Some((**elem_type).clone()) + } else { + None + } + } + TsType::TsTupleType(TsTupleType { elem_types, .. }) => match index { + TsType::TsLitType(TsLitType { + lit: TsLit::Number(num), + .. + }) => elem_types + .get(num.value as usize) + .map(|element| (*element.ty).clone()), + TsType::TsKeywordType(TsKeywordType { + kind: TsKeywordTypeKind::TsNumberKeyword, + .. + }) => Some(TsType::TsUnionOrIntersectionType( + TsUnionOrIntersectionType::TsUnionType(TsUnionType { + types: elem_types + .iter() + .map(|TsTupleElement { ty, .. }| ty.clone()) + .collect(), + span: DUMMY_SP, + }), + )), + _ => None, + }, + _ => None, + } + } + + fn infer_runtime_type(&self, ty: &TsType) -> IndexSet> { + let mut runtime_types = IndexSet::with_capacity(1); + match ty { + TsType::TsKeywordType(keyword) => match keyword.kind { + TsKeywordTypeKind::TsStringKeyword => { + runtime_types.insert(Some(js_word!("String"))); + } + TsKeywordTypeKind::TsNumberKeyword => { + runtime_types.insert(Some(js_word!("Number"))); + } + TsKeywordTypeKind::TsBooleanKeyword => { + runtime_types.insert(Some(js_word!("Boolean"))); + } + TsKeywordTypeKind::TsObjectKeyword => { + runtime_types.insert(Some(js_word!("Object"))); + } + TsKeywordTypeKind::TsNullKeyword => { + runtime_types.insert(None); + } + TsKeywordTypeKind::TsBigIntKeyword => { + runtime_types.insert(Some(js_word!("BigInt"))); + } + TsKeywordTypeKind::TsSymbolKeyword => { + runtime_types.insert(Some(js_word!("Symbol"))); + } + _ => { + runtime_types.insert(None); + } + }, + TsType::TsTypeLit(TsTypeLit { members, .. }) => { + members.iter().for_each(|member| { + if let TsTypeElement::TsCallSignatureDecl(..) + | TsTypeElement::TsConstructSignatureDecl(..) = member + { + runtime_types.insert(Some(js_word!("Function"))); + } else { + runtime_types.insert(Some(js_word!("Object"))); + } + }); + } + TsType::TsFnOrConstructorType(..) => { + runtime_types.insert(Some(js_word!("Function"))); + } + TsType::TsArrayType(..) | TsType::TsTupleType(..) => { + runtime_types.insert(Some(js_word!("Array"))); + } + TsType::TsLitType(TsLitType { lit, .. }) => match lit { + TsLit::Str(..) | TsLit::Tpl(..) => { + runtime_types.insert(Some(js_word!("String"))); + } + TsLit::Bool(..) => { + runtime_types.insert(Some(js_word!("Boolean"))); + } + TsLit::Number(..) | TsLit::BigInt(..) => { + runtime_types.insert(Some(js_word!("Number"))); + } + }, + TsType::TsTypeRef(TsTypeRef { + type_name: TsEntityName::Ident(ident), + type_params, + .. + }) => { + let key = (ident.sym.clone(), ident.span.ctxt()); + if let Some(aliased) = self.type_aliases.get(&key) { + runtime_types.extend(self.infer_runtime_type(aliased)); + } else if let Some(TsInterfaceDecl { + body: TsInterfaceBody { body, .. }, + .. + }) = self.interfaces.get(&key) + { + body.iter().for_each(|element| { + if let TsTypeElement::TsCallSignatureDecl(..) + | TsTypeElement::TsConstructSignatureDecl(..) = element + { + runtime_types.insert(Some(js_word!("Function"))); + } else { + runtime_types.insert(Some(js_word!("Object"))); + } + }); + } else { + match &*ident.sym { + "Array" | "Function" | "Object" | "Set" | "Map" | "WeakSet" | "WeakMap" + | "Date" | "Promise" | "Error" | "RegExp" => { + runtime_types.insert(Some(ident.sym.clone())); + } + "Partial" | "Required" | "Readonly" | "Record" | "Pick" | "Omit" + | "InstanceType" => { + runtime_types.insert(Some(js_word!("Object"))); + } + "Uppercase" | "Lowercase" | "Capitalize" | "Uncapitalize" => { + runtime_types.insert(Some(js_word!("String"))); + } + "Parameters" | "ConstructorParameters" => { + runtime_types.insert(Some(js_word!("Array"))); + } + "NonNullable" => { + if let Some(ty) = type_params + .as_ref() + .and_then(|type_params| type_params.params.first()) + { + let types = self.infer_runtime_type(ty); + runtime_types.extend(types.into_iter().filter(|ty| ty.is_some())); + } else { + runtime_types.insert(Some(js_word!("Object"))); + } + } + "Exclude" | "OmitThisParameter" => { + if let Some(ty) = type_params + .as_ref() + .and_then(|type_params| type_params.params.first()) + { + runtime_types.extend(self.infer_runtime_type(ty)); + } else { + runtime_types.insert(Some(js_word!("Object"))); + } + } + "Extract" => { + if let Some(ty) = type_params + .as_ref() + .and_then(|type_params| type_params.params.get(1)) + { + runtime_types.extend(self.infer_runtime_type(ty)); + } else { + runtime_types.insert(Some(js_word!("Object"))); + } + } + _ => { + runtime_types.insert(Some(js_word!("Object"))); + } + } + } + } + TsType::TsParenthesizedType(TsParenthesizedType { type_ann, .. }) => { + runtime_types.extend(self.infer_runtime_type(type_ann)); + } + TsType::TsUnionOrIntersectionType( + TsUnionOrIntersectionType::TsUnionType(TsUnionType { types, .. }) + | TsUnionOrIntersectionType::TsIntersectionType(TsIntersectionType { types, .. }), + ) => runtime_types.extend(types.iter().flat_map(|ty| self.infer_runtime_type(ty))), + TsType::TsIndexedAccessType(TsIndexedAccessType { + obj_type, + index_type, + .. + }) => { + if let Some(ty) = self.resolve_indexed_access(obj_type, index_type) { + runtime_types.extend(self.infer_runtime_type(&ty)); + } + } + TsType::TsOptionalType(TsOptionalType { type_ann, .. }) => { + runtime_types.extend(self.infer_runtime_type(type_ann)); + } + _ => { + runtime_types.insert(Some(js_word!("Object"))); + } + }; + runtime_types + } +} + +fn extract_prop_name(expr: Expr, computed: bool) -> PropName { + if computed { + PropName::Computed(ComputedPropName { + expr: Box::new(expr), + span: DUMMY_SP, + }) + } else { + match expr { + Expr::Ident(ident) => PropName::Ident(ident), + Expr::Lit(Lit::Str(str)) => PropName::Str(str), + Expr::Lit(Lit::Num(num)) => PropName::Num(num), + Expr::Lit(Lit::BigInt(bigint)) => PropName::BigInt(bigint), + _ => { + HANDLER.with(|handler| handler.span_err(expr.span(), "Unsupported prop key.")); + PropName::Ident(quote_ident!("")) + } + } + } +} diff --git a/visitor/tests/fixture.rs b/visitor/tests/fixture.rs index 609ee96..2150138 100644 --- a/visitor/tests/fixture.rs +++ b/visitor/tests/fixture.rs @@ -2,7 +2,7 @@ use std::{fs, io::ErrorKind, path::PathBuf}; use swc_core::{ common::{chain, Mark}, ecma::{ - parser::{EsConfig, Syntax}, + parser::{EsConfig, Syntax, TsConfig}, transforms::{base::resolver, testing::test_fixture}, visit::as_folder, }, @@ -10,6 +10,7 @@ use swc_core::{ use swc_vue_jsx_visitor::{Options, VueJsxTransformVisitor}; #[testing::fixture("tests/fixture/**/input.jsx")] +#[testing::fixture("tests/fixture/**/input.tsx")] fn test(input: PathBuf) { let config = match fs::read_to_string(input.with_file_name("config.json")) { Ok(json) => serde_json::from_str(&json).unwrap(), @@ -21,15 +22,28 @@ fn test(input: PathBuf) { }; let output = input.with_file_name("output.js"); + let is_ts = input + .extension() + .map(|ext| ext.to_string_lossy()) + .map(|ext| &*ext == "tsx") + .unwrap_or_default(); + test_fixture( - Syntax::Es(EsConfig { - jsx: true, - ..Default::default() - }), + if is_ts { + Syntax::Typescript(TsConfig { + tsx: true, + ..Default::default() + }) + } else { + Syntax::Es(EsConfig { + jsx: true, + ..Default::default() + }) + }, &|tester| { let unresolved_mark = Mark::new(); chain!( - resolver(unresolved_mark, Mark::new(), false), + resolver(unresolved_mark, Mark::new(), is_ts), as_folder(VueJsxTransformVisitor::new( config.clone(), unresolved_mark, diff --git a/visitor/tests/fixture/resolve-props-types/disabled/input.tsx b/visitor/tests/fixture/resolve-props-types/disabled/input.tsx new file mode 100644 index 0000000..3888274 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/disabled/input.tsx @@ -0,0 +1,7 @@ +import { defineComponent } from 'vue' + +type Props = { + foo: string +} + +defineComponent((props: Props) => {}) diff --git a/visitor/tests/fixture/resolve-props-types/disabled/output.js b/visitor/tests/fixture/resolve-props-types/disabled/output.js new file mode 100644 index 0000000..3999007 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/disabled/output.js @@ -0,0 +1,5 @@ +import { defineComponent } from 'vue'; +type Props = { + foo: string; +}; +defineComponent((props: Props)=>{}); diff --git a/visitor/tests/fixture/resolve-props-types/function/config.json b/visitor/tests/fixture/resolve-props-types/function/config.json new file mode 100644 index 0000000..46eaaca --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/function/config.json @@ -0,0 +1,3 @@ +{ + "resolveType": true +} diff --git a/visitor/tests/fixture/resolve-props-types/function/input.tsx b/visitor/tests/fixture/resolve-props-types/function/input.tsx new file mode 100644 index 0000000..b54da35 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/function/input.tsx @@ -0,0 +1,21 @@ +import { defineComponent } from 'vue' + +defineComponent(({}: { + foo: number, +}) => { }) + +defineComponent(([]: { + foo: number, +}) => { }) + +defineComponent(function (props: { + foo: number, +}) { }) + +defineComponent(function ({}: { + foo: number, +}) { }) + +defineComponent(function ([]: { + foo: number, +}) { }) diff --git a/visitor/tests/fixture/resolve-props-types/function/output.js b/visitor/tests/fixture/resolve-props-types/function/output.js new file mode 100644 index 0000000..8f01b1b --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/function/output.js @@ -0,0 +1,51 @@ +import { defineComponent } from 'vue'; +defineComponent(({}: { + foo: number; +})=>{}, { + props: { + foo: { + type: Number, + required: true + } + } +}); +defineComponent(([]: { + foo: number; +})=>{}, { + props: { + foo: { + type: Number, + required: true + } + } +}); +defineComponent(function(props: { + foo: number; +}) {}, { + props: { + foo: { + type: Number, + required: true + } + } +}); +defineComponent(function({}: { + foo: number; +}) {}, { + props: { + foo: { + type: Number, + required: true + } + } +}); +defineComponent(function([]: { + foo: number; +}) {}, { + props: { + foo: { + type: Number, + required: true + } + } +}); diff --git a/visitor/tests/fixture/resolve-props-types/indexed-access-interface-advanced/config.json b/visitor/tests/fixture/resolve-props-types/indexed-access-interface-advanced/config.json new file mode 100644 index 0000000..46eaaca --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/indexed-access-interface-advanced/config.json @@ -0,0 +1,3 @@ +{ + "resolveType": true +} diff --git a/visitor/tests/fixture/resolve-props-types/indexed-access-interface-advanced/input.tsx b/visitor/tests/fixture/resolve-props-types/indexed-access-interface-advanced/input.tsx new file mode 100644 index 0000000..030fbf8 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/indexed-access-interface-advanced/input.tsx @@ -0,0 +1,9 @@ +import { defineComponent } from 'vue' + +type K = 'foo' | 'bar' +interface T { foo: string, bar: number } +interface Foo { foo: T[string] } +interface Bar { bar: string } +interface S { foo: Foo, bar: Bar } + +defineComponent((props: S[K]) => { }) diff --git a/visitor/tests/fixture/resolve-props-types/indexed-access-interface-advanced/output.js b/visitor/tests/fixture/resolve-props-types/indexed-access-interface-advanced/output.js new file mode 100644 index 0000000..7906d0a --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/indexed-access-interface-advanced/output.js @@ -0,0 +1,31 @@ +import { defineComponent } from 'vue'; +type K = 'foo' | 'bar'; +interface T { + foo: string; + bar: number; +} +interface Foo { + foo: T[string]; +} +interface Bar { + bar: string; +} +interface S { + foo: Foo; + bar: Bar; +} +defineComponent((props: S[K])=>{}, { + props: { + foo: { + type: [ + String, + Number + ], + required: true + }, + bar: { + type: String, + required: true + } + } +}); diff --git a/visitor/tests/fixture/resolve-props-types/indexed-access-interface-literal/config.json b/visitor/tests/fixture/resolve-props-types/indexed-access-interface-literal/config.json new file mode 100644 index 0000000..46eaaca --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/indexed-access-interface-literal/config.json @@ -0,0 +1,3 @@ +{ + "resolveType": true +} diff --git a/visitor/tests/fixture/resolve-props-types/indexed-access-interface-literal/input.tsx b/visitor/tests/fixture/resolve-props-types/indexed-access-interface-literal/input.tsx new file mode 100644 index 0000000..5defe97 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/indexed-access-interface-literal/input.tsx @@ -0,0 +1,6 @@ +import { defineComponent } from 'vue' + +interface T { bar: number } +interface S { nested: { foo: T['bar'] }} + +defineComponent((props: S['nested']) => { }) diff --git a/visitor/tests/fixture/resolve-props-types/indexed-access-interface-literal/output.js b/visitor/tests/fixture/resolve-props-types/indexed-access-interface-literal/output.js new file mode 100644 index 0000000..d00023b --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/indexed-access-interface-literal/output.js @@ -0,0 +1,17 @@ +import { defineComponent } from 'vue'; +interface T { + bar: number; +} +interface S { + nested: { + foo: T['bar']; + }; +} +defineComponent((props: S['nested'])=>{}, { + props: { + foo: { + type: Number, + required: true + } + } +}); diff --git a/visitor/tests/fixture/resolve-props-types/indexed-access-type-advanced/config.json b/visitor/tests/fixture/resolve-props-types/indexed-access-type-advanced/config.json new file mode 100644 index 0000000..46eaaca --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/indexed-access-type-advanced/config.json @@ -0,0 +1,3 @@ +{ + "resolveType": true +} diff --git a/visitor/tests/fixture/resolve-props-types/indexed-access-type-advanced/input.tsx b/visitor/tests/fixture/resolve-props-types/indexed-access-type-advanced/input.tsx new file mode 100644 index 0000000..e5076f0 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/indexed-access-type-advanced/input.tsx @@ -0,0 +1,7 @@ +import { defineComponent } from 'vue' + +type K = 'foo' | 'bar' +type T = { foo: string, bar: number } +type S = { foo: { foo: T[string] }, bar: { bar: string } } + +defineComponent((props: S[K]) => { }) diff --git a/visitor/tests/fixture/resolve-props-types/indexed-access-type-advanced/output.js b/visitor/tests/fixture/resolve-props-types/indexed-access-type-advanced/output.js new file mode 100644 index 0000000..4a8b699 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/indexed-access-type-advanced/output.js @@ -0,0 +1,29 @@ +import { defineComponent } from 'vue'; +type K = 'foo' | 'bar'; +type T = { + foo: string; + bar: number; +}; +type S = { + foo: { + foo: T[string]; + }; + bar: { + bar: string; + }; +}; +defineComponent((props: S[K])=>{}, { + props: { + foo: { + type: [ + String, + Number + ], + required: true + }, + bar: { + type: String, + required: true + } + } +}); diff --git a/visitor/tests/fixture/resolve-props-types/indexed-access-type-literal/config.json b/visitor/tests/fixture/resolve-props-types/indexed-access-type-literal/config.json new file mode 100644 index 0000000..46eaaca --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/indexed-access-type-literal/config.json @@ -0,0 +1,3 @@ +{ + "resolveType": true +} diff --git a/visitor/tests/fixture/resolve-props-types/indexed-access-type-literal/input.tsx b/visitor/tests/fixture/resolve-props-types/indexed-access-type-literal/input.tsx new file mode 100644 index 0000000..84c54f0 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/indexed-access-type-literal/input.tsx @@ -0,0 +1,6 @@ +import { defineComponent } from 'vue' + +type T = { bar: number } +type S = { nested: { foo: T['bar'] }} + +defineComponent((props: S['nested']) => { }) diff --git a/visitor/tests/fixture/resolve-props-types/indexed-access-type-literal/output.js b/visitor/tests/fixture/resolve-props-types/indexed-access-type-literal/output.js new file mode 100644 index 0000000..6d3b11e --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/indexed-access-type-literal/output.js @@ -0,0 +1,17 @@ +import { defineComponent } from 'vue'; +type T = { + bar: number; +}; +type S = { + nested: { + foo: T['bar']; + }; +}; +defineComponent((props: S['nested'])=>{}, { + props: { + foo: { + type: Number, + required: true + } + } +}); diff --git a/visitor/tests/fixture/resolve-props-types/indexed-access-type-number/config.json b/visitor/tests/fixture/resolve-props-types/indexed-access-type-number/config.json new file mode 100644 index 0000000..46eaaca --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/indexed-access-type-number/config.json @@ -0,0 +1,3 @@ +{ + "resolveType": true +} diff --git a/visitor/tests/fixture/resolve-props-types/indexed-access-type-number/input.tsx b/visitor/tests/fixture/resolve-props-types/indexed-access-type-number/input.tsx new file mode 100644 index 0000000..7723534 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/indexed-access-type-number/input.tsx @@ -0,0 +1,17 @@ +import { defineComponent } from 'vue' + +type A = (string | number)[] +type AA = Array +type T = [1, 'foo'] +type TT = [foo: 1, bar: 'foo'] +type Optional = [1?] + +defineComponent((props: { + foo: A[number], + bar: AA[number], + tuple: T[number], + tuple0: T[0], + tuple1: T[1], + namedTuple: TT[number], + optional: Optional[0], +}) => { }) diff --git a/visitor/tests/fixture/resolve-props-types/indexed-access-type-number/output.js b/visitor/tests/fixture/resolve-props-types/indexed-access-type-number/output.js new file mode 100644 index 0000000..59cec8d --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/indexed-access-type-number/output.js @@ -0,0 +1,55 @@ +import { defineComponent } from 'vue'; +type A = (string | number)[]; +type AA = Array; +type T = [1, 'foo']; +type TT = [foo: 1, bar: 'foo']; +type Optional = [1?]; +defineComponent((props: { + foo: A[number]; + bar: AA[number]; + tuple: T[number]; + tuple0: T[0]; + tuple1: T[1]; + namedTuple: TT[number]; + optional: Optional[0]; +})=>{}, { + props: { + foo: { + type: [ + String, + Number + ], + required: true + }, + bar: { + type: String, + required: true + }, + tuple: { + type: [ + Number, + String + ], + required: true + }, + tuple0: { + type: Number, + required: true + }, + tuple1: { + type: String, + required: true + }, + namedTuple: { + type: [ + Number, + String + ], + required: true + }, + optional: { + type: Number, + required: true + } + } +}); diff --git a/visitor/tests/fixture/resolve-props-types/interface-merging/config.json b/visitor/tests/fixture/resolve-props-types/interface-merging/config.json new file mode 100644 index 0000000..46eaaca --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/interface-merging/config.json @@ -0,0 +1,3 @@ +{ + "resolveType": true +} diff --git a/visitor/tests/fixture/resolve-props-types/interface-merging/input.tsx b/visitor/tests/fixture/resolve-props-types/interface-merging/input.tsx new file mode 100644 index 0000000..4ecbc59 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/interface-merging/input.tsx @@ -0,0 +1,13 @@ +import { defineComponent } from 'vue' + +interface Foo { + a: string +} +interface Foo { + b: number +} + +defineComponent((props: { + foo: Foo['a'], + bar: Foo['b'], +}) => { }) diff --git a/visitor/tests/fixture/resolve-props-types/interface-merging/output.js b/visitor/tests/fixture/resolve-props-types/interface-merging/output.js new file mode 100644 index 0000000..6653b13 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/interface-merging/output.js @@ -0,0 +1,22 @@ +import { defineComponent } from 'vue'; +interface Foo { + a: string; +} +interface Foo { + b: number; +} +defineComponent((props: { + foo: Foo['a']; + bar: Foo['b']; +})=>{}, { + props: { + foo: { + type: String, + required: true + }, + bar: { + type: Number, + required: true + } + } +}); diff --git a/visitor/tests/fixture/resolve-props-types/intersection-type/config.json b/visitor/tests/fixture/resolve-props-types/intersection-type/config.json new file mode 100644 index 0000000..46eaaca --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/intersection-type/config.json @@ -0,0 +1,3 @@ +{ + "resolveType": true +} diff --git a/visitor/tests/fixture/resolve-props-types/intersection-type/input.tsx b/visitor/tests/fixture/resolve-props-types/intersection-type/input.tsx new file mode 100644 index 0000000..14617b3 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/intersection-type/input.tsx @@ -0,0 +1,7 @@ +import { defineComponent } from 'vue' + +type Foo = { foo: number } +type Bar = { bar: string } +type Baz = { bar: string | boolean } + +defineComponent((props: { self: any } & Foo & Bar & Baz) => { }) diff --git a/visitor/tests/fixture/resolve-props-types/intersection-type/output.js b/visitor/tests/fixture/resolve-props-types/intersection-type/output.js new file mode 100644 index 0000000..05627ee --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/intersection-type/output.js @@ -0,0 +1,31 @@ +import { defineComponent } from 'vue'; +type Foo = { + foo: number; +}; +type Bar = { + bar: string; +}; +type Baz = { + bar: string | boolean; +}; +defineComponent((props: { + self: any; +} & Foo & Bar & Baz)=>{}, { + props: { + self: { + type: null, + required: true + }, + foo: { + type: Number, + required: true + }, + bar: { + type: [ + String, + Boolean + ], + required: true + } + } +}); diff --git a/visitor/tests/fixture/resolve-props-types/optional/config.json b/visitor/tests/fixture/resolve-props-types/optional/config.json new file mode 100644 index 0000000..46eaaca --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/optional/config.json @@ -0,0 +1,3 @@ +{ + "resolveType": true +} diff --git a/visitor/tests/fixture/resolve-props-types/optional/input.tsx b/visitor/tests/fixture/resolve-props-types/optional/input.tsx new file mode 100644 index 0000000..8608b9a --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/optional/input.tsx @@ -0,0 +1,10 @@ +import { defineComponent } from 'vue' + +defineComponent((props: { + foo?: number, // property + bar?(): void, // method + 'baz'?: string, // string literal key + + untyped1?, + untyped2?(), +}) => { }) diff --git a/visitor/tests/fixture/resolve-props-types/optional/output.js b/visitor/tests/fixture/resolve-props-types/optional/output.js new file mode 100644 index 0000000..1ac9003 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/optional/output.js @@ -0,0 +1,31 @@ +import { defineComponent } from 'vue'; +defineComponent((props: { + foo?: number; + bar?(): void; + 'baz'?: string; + untyped1?; + untyped2?(); +})=>{}, { + props: { + foo: { + type: Number, + required: false + }, + bar: { + type: Function, + required: false + }, + 'baz': { + type: String, + required: false + }, + untyped1: { + type: null, + required: false + }, + untyped2: { + type: Function, + required: false + } + } +}); diff --git a/visitor/tests/fixture/resolve-props-types/options-object/config.json b/visitor/tests/fixture/resolve-props-types/options-object/config.json new file mode 100644 index 0000000..46eaaca --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/options-object/config.json @@ -0,0 +1,3 @@ +{ + "resolveType": true +} diff --git a/visitor/tests/fixture/resolve-props-types/options-object/input.tsx b/visitor/tests/fixture/resolve-props-types/options-object/input.tsx new file mode 100644 index 0000000..032ed83 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/options-object/input.tsx @@ -0,0 +1,16 @@ +import { defineComponent } from 'vue' + +defineComponent((props: { + foo: number, +}) => { }, {}) + +// shouldn't be resolved +defineComponent((props: { + foo: number, +}) => { }, { + props: { + bar: { + type: String, + }, + }, +}) diff --git a/visitor/tests/fixture/resolve-props-types/options-object/output.js b/visitor/tests/fixture/resolve-props-types/options-object/output.js new file mode 100644 index 0000000..0c2e335 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/options-object/output.js @@ -0,0 +1,21 @@ +import { defineComponent } from 'vue'; +defineComponent((props: { + foo: number; +})=>{}, { + props: { + foo: { + type: Number, + required: true + } + } +}); +// shouldn't be resolved +defineComponent((props: { + foo: number; +})=>{}, { + props: { + bar: { + type: String + } + } +}); diff --git a/visitor/tests/fixture/resolve-props-types/reference-class/config.json b/visitor/tests/fixture/resolve-props-types/reference-class/config.json new file mode 100644 index 0000000..46eaaca --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/reference-class/config.json @@ -0,0 +1,3 @@ +{ + "resolveType": true +} diff --git a/visitor/tests/fixture/resolve-props-types/reference-class/input.tsx b/visitor/tests/fixture/resolve-props-types/reference-class/input.tsx new file mode 100644 index 0000000..1659a68 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/reference-class/input.tsx @@ -0,0 +1,5 @@ +import { defineComponent } from 'vue' + +class Foo {} + +defineComponent((props: { foo: Foo }) => { }) diff --git a/visitor/tests/fixture/resolve-props-types/reference-class/output.js b/visitor/tests/fixture/resolve-props-types/reference-class/output.js new file mode 100644 index 0000000..e0038fc --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/reference-class/output.js @@ -0,0 +1,13 @@ +import { defineComponent } from 'vue'; +class Foo { +} +defineComponent((props: { + foo: Foo; +})=>{}, { + props: { + foo: { + type: Object, + required: true + } + } +}); diff --git a/visitor/tests/fixture/resolve-props-types/reference-exported-interface/config.json b/visitor/tests/fixture/resolve-props-types/reference-exported-interface/config.json new file mode 100644 index 0000000..46eaaca --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/reference-exported-interface/config.json @@ -0,0 +1,3 @@ +{ + "resolveType": true +} diff --git a/visitor/tests/fixture/resolve-props-types/reference-exported-interface/input.tsx b/visitor/tests/fixture/resolve-props-types/reference-exported-interface/input.tsx new file mode 100644 index 0000000..11dd98a --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/reference-exported-interface/input.tsx @@ -0,0 +1,5 @@ +import { defineComponent } from 'vue' + +export interface Aliased { foo: number } + +defineComponent((props: Aliased) => { }) diff --git a/visitor/tests/fixture/resolve-props-types/reference-exported-interface/output.js b/visitor/tests/fixture/resolve-props-types/reference-exported-interface/output.js new file mode 100644 index 0000000..116b527 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/reference-exported-interface/output.js @@ -0,0 +1,12 @@ +import { defineComponent } from 'vue'; +export interface Aliased { + foo: number; +} +defineComponent((props: Aliased)=>{}, { + props: { + foo: { + type: Number, + required: true + } + } +}); diff --git a/visitor/tests/fixture/resolve-props-types/reference-exported-type/config.json b/visitor/tests/fixture/resolve-props-types/reference-exported-type/config.json new file mode 100644 index 0000000..46eaaca --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/reference-exported-type/config.json @@ -0,0 +1,3 @@ +{ + "resolveType": true +} diff --git a/visitor/tests/fixture/resolve-props-types/reference-exported-type/input.tsx b/visitor/tests/fixture/resolve-props-types/reference-exported-type/input.tsx new file mode 100644 index 0000000..ec6287c --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/reference-exported-type/input.tsx @@ -0,0 +1,5 @@ +import { defineComponent } from 'vue' + +export type Aliased = { foo: number } + +defineComponent((props: Aliased) => { }) diff --git a/visitor/tests/fixture/resolve-props-types/reference-exported-type/output.js b/visitor/tests/fixture/resolve-props-types/reference-exported-type/output.js new file mode 100644 index 0000000..efa7dd5 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/reference-exported-type/output.js @@ -0,0 +1,12 @@ +import { defineComponent } from 'vue'; +export type Aliased = { + foo: number; +}; +defineComponent((props: Aliased)=>{}, { + props: { + foo: { + type: Number, + required: true + } + } +}); diff --git a/visitor/tests/fixture/resolve-props-types/reference-interface-extends/config.json b/visitor/tests/fixture/resolve-props-types/reference-interface-extends/config.json new file mode 100644 index 0000000..46eaaca --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/reference-interface-extends/config.json @@ -0,0 +1,3 @@ +{ + "resolveType": true +} diff --git a/visitor/tests/fixture/resolve-props-types/reference-interface-extends/input.tsx b/visitor/tests/fixture/resolve-props-types/reference-interface-extends/input.tsx new file mode 100644 index 0000000..e5ec12b --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/reference-interface-extends/input.tsx @@ -0,0 +1,8 @@ +import { defineComponent } from 'vue' + +export interface A { a(): void } +export interface B extends A { b: boolean } +interface C { c: string } +interface Aliased extends B, C { foo: number } + +defineComponent((props: Aliased) => { }) diff --git a/visitor/tests/fixture/resolve-props-types/reference-interface-extends/output.js b/visitor/tests/fixture/resolve-props-types/reference-interface-extends/output.js new file mode 100644 index 0000000..4d3bf16 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/reference-interface-extends/output.js @@ -0,0 +1,33 @@ +import { defineComponent } from 'vue'; +export interface A { + a(): void; +} +export interface B extends A { + b: boolean; +} +interface C { + c: string; +} +interface Aliased extends B, C { + foo: number; +} +defineComponent((props: Aliased)=>{}, { + props: { + foo: { + type: Number, + required: true + }, + b: { + type: Boolean, + required: true + }, + a: { + type: Function, + required: true + }, + c: { + type: String, + required: true + } + } +}); diff --git a/visitor/tests/fixture/resolve-props-types/reference-interface/config.json b/visitor/tests/fixture/resolve-props-types/reference-interface/config.json new file mode 100644 index 0000000..46eaaca --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/reference-interface/config.json @@ -0,0 +1,3 @@ +{ + "resolveType": true +} diff --git a/visitor/tests/fixture/resolve-props-types/reference-interface/input.tsx b/visitor/tests/fixture/resolve-props-types/reference-interface/input.tsx new file mode 100644 index 0000000..a95a816 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/reference-interface/input.tsx @@ -0,0 +1,7 @@ +import { defineComponent } from 'vue' + +interface Aliased { foo: number } + +defineComponent((props: Aliased) => { }) + +defineComponent((props: (Aliased)) => { }) diff --git a/visitor/tests/fixture/resolve-props-types/reference-interface/output.js b/visitor/tests/fixture/resolve-props-types/reference-interface/output.js new file mode 100644 index 0000000..b9b8e7f --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/reference-interface/output.js @@ -0,0 +1,20 @@ +import { defineComponent } from 'vue'; +interface Aliased { + foo: number; +} +defineComponent((props: Aliased)=>{}, { + props: { + foo: { + type: Number, + required: true + } + } +}); +defineComponent((props: (Aliased))=>{}, { + props: { + foo: { + type: Number, + required: true + } + } +}); diff --git a/visitor/tests/fixture/resolve-props-types/reference-type/config.json b/visitor/tests/fixture/resolve-props-types/reference-type/config.json new file mode 100644 index 0000000..46eaaca --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/reference-type/config.json @@ -0,0 +1,3 @@ +{ + "resolveType": true +} diff --git a/visitor/tests/fixture/resolve-props-types/reference-type/input.tsx b/visitor/tests/fixture/resolve-props-types/reference-type/input.tsx new file mode 100644 index 0000000..da9e0c8 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/reference-type/input.tsx @@ -0,0 +1,5 @@ +import { defineComponent } from 'vue' + +type Aliased = { foo: number } + +defineComponent((props: Aliased) => { }) diff --git a/visitor/tests/fixture/resolve-props-types/reference-type/output.js b/visitor/tests/fixture/resolve-props-types/reference-type/output.js new file mode 100644 index 0000000..cd7a921 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/reference-type/output.js @@ -0,0 +1,12 @@ +import { defineComponent } from 'vue'; +type Aliased = { + foo: number; +}; +defineComponent((props: Aliased)=>{}, { + props: { + foo: { + type: Number, + required: true + } + } +}); diff --git a/visitor/tests/fixture/resolve-props-types/runtime-types/config.json b/visitor/tests/fixture/resolve-props-types/runtime-types/config.json new file mode 100644 index 0000000..46eaaca --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/runtime-types/config.json @@ -0,0 +1,3 @@ +{ + "resolveType": true +} diff --git a/visitor/tests/fixture/resolve-props-types/runtime-types/input.tsx b/visitor/tests/fixture/resolve-props-types/runtime-types/input.tsx new file mode 100644 index 0000000..df342ef --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/runtime-types/input.tsx @@ -0,0 +1,91 @@ +import { defineComponent } from 'vue' + +type FunctionType = { + (): void +} +type ObjectType = { + foo: string +} +type MixedType = { + foo: string + (): void +} + +interface FunctionInterface { + (): void +} +interface ObjectInterface { + foo: string +} +interface MixedInterface { + foo: string + (): void +} + +defineComponent((props: { + a: string + b: number + c: boolean + d: object + e: null + f: bigint + g: symbol + h: any + i: () => void + j: new () => object + k: string[] + l: Array + m: [string, number] + n: 'literal' + o: 123 + p: 123n + q: true + r: FunctionType + s: ObjectType + t: MixedType + u: FunctionInterface + v: ObjectInterface + w: MixedInterface +}) => { }) + +defineComponent((props: { + function: Function, + object: Object, + set: Set, + map: Map, + weakSet: WeakSet, + weakMap: WeakMap, + date: Date, + promise: Promise, + error: Error, + regexp: RegExp, +}) => { }) + +defineComponent((props: { + partial: Partial<{ foo: string }>, + required: Required<{ foo?: string }>, + readonly: Readonly<{ foo: string }>, + record: Record, + pick: Pick<{ foo: string, bar: number }, 'foo'>, + omit: Omit<{ foo: string, bar: number }, 'foo'>, + instance: InstanceType, +}) => { }) + +defineComponent((props: { + uppercase: Uppercase<'foo'>, + lowercase: Lowercase<'FOO'>, + capitalize: Capitalize<'foo'>, + uncapitalize: Uncapitalize<'FOO'>, +}) => { }) + +defineComponent((props: { + parameters: Parameters<() => void>, + constructorParameters: ConstructorParameters, +}) => { }) + +defineComponent((props: { + nonNullable: NonNullable, + exclude: Exclude, + extract: Extract, + omitThisParameter: OmitThisParameter<(this: string) => void>, +}) => { }) diff --git a/visitor/tests/fixture/resolve-props-types/runtime-types/output.js b/visitor/tests/fixture/resolve-props-types/runtime-types/output.js new file mode 100644 index 0000000..ed8d7ab --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/runtime-types/output.js @@ -0,0 +1,323 @@ +import { defineComponent } from 'vue'; +type FunctionType = { + () : void; +}; +type ObjectType = { + foo: string; +}; +type MixedType = { + foo: string; + () : void; +}; +interface FunctionInterface { + () : void; +} +interface ObjectInterface { + foo: string; +} +interface MixedInterface { + foo: string; + () : void; +} +defineComponent((props: { + a: string; + b: number; + c: boolean; + d: object; + e: null; + f: bigint; + g: symbol; + h: any; + i: () => void; + j: new() => object; + k: string[]; + l: Array; + m: [string, number]; + n: 'literal'; + o: 123; + p: 123n; + q: true; + r: FunctionType; + s: ObjectType; + t: MixedType; + u: FunctionInterface; + v: ObjectInterface; + w: MixedInterface; +})=>{}, { + props: { + a: { + type: String, + required: true + }, + b: { + type: Number, + required: true + }, + c: { + type: Boolean, + required: true + }, + d: { + type: Object, + required: true + }, + e: { + type: null, + required: true + }, + f: { + type: BigInt, + required: true + }, + g: { + type: Symbol, + required: true + }, + h: { + type: null, + required: true + }, + i: { + type: Function, + required: true + }, + j: { + type: Function, + required: true + }, + k: { + type: Array, + required: true + }, + l: { + type: Array, + required: true + }, + m: { + type: Array, + required: true + }, + n: { + type: String, + required: true + }, + o: { + type: Number, + required: true + }, + p: { + type: Number, + required: true + }, + q: { + type: Boolean, + required: true + }, + r: { + type: Function, + required: true + }, + s: { + type: Object, + required: true + }, + t: { + type: [ + Object, + Function + ], + required: true + }, + u: { + type: Function, + required: true + }, + v: { + type: Object, + required: true + }, + w: { + type: [ + Object, + Function + ], + required: true + } + } +}); +defineComponent((props: { + function: Function; + object: Object; + set: Set; + map: Map; + weakSet: WeakSet; + weakMap: WeakMap; + date: Date; + promise: Promise; + error: Error; + regexp: RegExp; +})=>{}, { + props: { + function: { + type: Function, + required: true + }, + object: { + type: Object, + required: true + }, + set: { + type: Set, + required: true + }, + map: { + type: Map, + required: true + }, + weakSet: { + type: WeakSet, + required: true + }, + weakMap: { + type: WeakMap, + required: true + }, + date: { + type: Date, + required: true + }, + promise: { + type: Promise, + required: true + }, + error: { + type: Error, + required: true + }, + regexp: { + type: RegExp, + required: true + } + } +}); +defineComponent((props: { + partial: Partial<{ + foo: string; + }>; + required: Required<{ + foo?: string; + }>; + readonly: Readonly<{ + foo: string; + }>; + record: Record; + pick: Pick<{ + foo: string; + bar: number; + }, 'foo'>; + omit: Omit<{ + foo: string; + bar: number; + }, 'foo'>; + instance: InstanceType; +})=>{}, { + props: { + partial: { + type: Object, + required: true + }, + required: { + type: Object, + required: true + }, + readonly: { + type: Object, + required: true + }, + record: { + type: Object, + required: true + }, + pick: { + type: Object, + required: true + }, + omit: { + type: Object, + required: true + }, + instance: { + type: Object, + required: true + } + } +}); +defineComponent((props: { + uppercase: Uppercase<'foo'>; + lowercase: Lowercase<'FOO'>; + capitalize: Capitalize<'foo'>; + uncapitalize: Uncapitalize<'FOO'>; +})=>{}, { + props: { + uppercase: { + type: String, + required: true + }, + lowercase: { + type: String, + required: true + }, + capitalize: { + type: String, + required: true + }, + uncapitalize: { + type: String, + required: true + } + } +}); +defineComponent((props: { + parameters: Parameters<() => void>; + constructorParameters: ConstructorParameters; +})=>{}, { + props: { + parameters: { + type: Array, + required: true + }, + constructorParameters: { + type: Array, + required: true + } + } +}); +defineComponent((props: { + nonNullable: NonNullable; + exclude: Exclude; + extract: Extract; + omitThisParameter: OmitThisParameter<(this: string) => void>; +})=>{}, { + props: { + nonNullable: { + type: String, + required: true + }, + exclude: { + type: [ + String, + Number, + Boolean + ], + required: true + }, + extract: { + type: Boolean, + required: true + }, + omitThisParameter: { + type: Function, + required: true + } + } +}); diff --git a/visitor/tests/fixture/resolve-props-types/scope/config.json b/visitor/tests/fixture/resolve-props-types/scope/config.json new file mode 100644 index 0000000..46eaaca --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/scope/config.json @@ -0,0 +1,3 @@ +{ + "resolveType": true +} diff --git a/visitor/tests/fixture/resolve-props-types/scope/input.tsx b/visitor/tests/fixture/resolve-props-types/scope/input.tsx new file mode 100644 index 0000000..90b8c45 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/scope/input.tsx @@ -0,0 +1,15 @@ +import { defineComponent } from 'vue' + +type Props = { + foo: string +} + +function scope() { + type Props = { + bar: number + } + + defineComponent((props: Props) => {}) +} + +defineComponent((props: Props) => {}) diff --git a/visitor/tests/fixture/resolve-props-types/scope/output.js b/visitor/tests/fixture/resolve-props-types/scope/output.js new file mode 100644 index 0000000..d885dfa --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/scope/output.js @@ -0,0 +1,25 @@ +import { defineComponent } from 'vue'; +type Props = { + foo: string; +}; +function scope() { + type Props = { + bar: number; + }; + defineComponent((props: Props)=>{}, { + props: { + bar: { + type: Number, + required: true + } + } + }); +} +defineComponent((props: Props)=>{}, { + props: { + foo: { + type: String, + required: true + } + } +}); diff --git a/visitor/tests/fixture/resolve-props-types/type-literal/config.json b/visitor/tests/fixture/resolve-props-types/type-literal/config.json new file mode 100644 index 0000000..46eaaca --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/type-literal/config.json @@ -0,0 +1,3 @@ +{ + "resolveType": true +} diff --git a/visitor/tests/fixture/resolve-props-types/type-literal/input.tsx b/visitor/tests/fixture/resolve-props-types/type-literal/input.tsx new file mode 100644 index 0000000..cfde591 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/type-literal/input.tsx @@ -0,0 +1,14 @@ +import { defineComponent } from 'vue' + +defineComponent((props: { + foo: number, // property + bar(): void, // method + 'baz': string, // string literal key + get qux(): number, // getter + (e: 'foo'): void, // call signature + (e: 'bar'): void, + + untyped1, + get untyped2(), + untyped3(), +}) => { }) diff --git a/visitor/tests/fixture/resolve-props-types/type-literal/output.js b/visitor/tests/fixture/resolve-props-types/type-literal/output.js new file mode 100644 index 0000000..aeaf609 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/type-literal/output.js @@ -0,0 +1,43 @@ +import { defineComponent } from 'vue'; +defineComponent((props: { + foo: number; + bar(): void; + 'baz': string; + get qux(): number; + (e: 'foo') : void; + (e: 'bar') : void; + untyped1; + get untyped2(); + untyped3(); +})=>{}, { + props: { + foo: { + type: Number, + required: true + }, + bar: { + type: Function, + required: true + }, + 'baz': { + type: String, + required: true + }, + qux: { + type: Number, + required: true + }, + untyped1: { + type: null, + required: true + }, + untyped2: { + type: null, + required: true + }, + untyped3: { + type: Function, + required: true + } + } +}); diff --git a/visitor/tests/fixture/resolve-props-types/union-type/config.json b/visitor/tests/fixture/resolve-props-types/union-type/config.json new file mode 100644 index 0000000..46eaaca --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/union-type/config.json @@ -0,0 +1,3 @@ +{ + "resolveType": true +} diff --git a/visitor/tests/fixture/resolve-props-types/union-type/input.tsx b/visitor/tests/fixture/resolve-props-types/union-type/input.tsx new file mode 100644 index 0000000..c96de52 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/union-type/input.tsx @@ -0,0 +1,18 @@ +import { defineComponent } from 'vue' + +interface CommonProps { + size?: 'xl' | 'l' | 'm' | 's' | 'xs' +} + +type ConditionalProps = + | { + color: 'normal' | 'primary' | 'secondary' + appearance: 'normal' | 'outline' | 'text' + } + | { + color: number + appearance: 'outline' + note: string + } + +defineComponent((props: CommonProps & ConditionalProps) => { }) diff --git a/visitor/tests/fixture/resolve-props-types/union-type/output.js b/visitor/tests/fixture/resolve-props-types/union-type/output.js new file mode 100644 index 0000000..e12f597 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/union-type/output.js @@ -0,0 +1,35 @@ +import { defineComponent } from 'vue'; +interface CommonProps { + size?: 'xl' | 'l' | 'm' | 's' | 'xs'; +} +type ConditionalProps = { + color: 'normal' | 'primary' | 'secondary'; + appearance: 'normal' | 'outline' | 'text'; +} | { + color: number; + appearance: 'outline'; + note: string; +}; +defineComponent((props: CommonProps & ConditionalProps)=>{}, { + props: { + size: { + type: String, + required: false + }, + color: { + type: [ + String, + Number + ], + required: true + }, + appearance: { + type: String, + required: true + }, + note: { + type: String, + required: true + } + } +}); diff --git a/visitor/tests/fixture/resolve-props-types/utility-type-omit/config.json b/visitor/tests/fixture/resolve-props-types/utility-type-omit/config.json new file mode 100644 index 0000000..46eaaca --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/utility-type-omit/config.json @@ -0,0 +1,3 @@ +{ + "resolveType": true +} diff --git a/visitor/tests/fixture/resolve-props-types/utility-type-omit/input.tsx b/visitor/tests/fixture/resolve-props-types/utility-type-omit/input.tsx new file mode 100644 index 0000000..5531f66 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/utility-type-omit/input.tsx @@ -0,0 +1,6 @@ +import { defineComponent } from 'vue' + +type T = { foo: number, bar: string, baz: boolean } +type K = 'foo' | 'bar' + +defineComponent((props: Omit) => { }) diff --git a/visitor/tests/fixture/resolve-props-types/utility-type-omit/output.js b/visitor/tests/fixture/resolve-props-types/utility-type-omit/output.js new file mode 100644 index 0000000..90b2a43 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/utility-type-omit/output.js @@ -0,0 +1,15 @@ +import { defineComponent } from 'vue'; +type T = { + foo: number; + bar: string; + baz: boolean; +}; +type K = 'foo' | 'bar'; +defineComponent((props: Omit)=>{}, { + props: { + baz: { + type: Boolean, + required: true + } + } +}); diff --git a/visitor/tests/fixture/resolve-props-types/utility-type-partial/config.json b/visitor/tests/fixture/resolve-props-types/utility-type-partial/config.json new file mode 100644 index 0000000..46eaaca --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/utility-type-partial/config.json @@ -0,0 +1,3 @@ +{ + "resolveType": true +} diff --git a/visitor/tests/fixture/resolve-props-types/utility-type-partial/input.tsx b/visitor/tests/fixture/resolve-props-types/utility-type-partial/input.tsx new file mode 100644 index 0000000..96d28fa --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/utility-type-partial/input.tsx @@ -0,0 +1,5 @@ +import { defineComponent } from 'vue' + +type T = { foo: number, bar: string } + +defineComponent((props: Partial) => { }) diff --git a/visitor/tests/fixture/resolve-props-types/utility-type-partial/output.js b/visitor/tests/fixture/resolve-props-types/utility-type-partial/output.js new file mode 100644 index 0000000..8740bb1 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/utility-type-partial/output.js @@ -0,0 +1,17 @@ +import { defineComponent } from 'vue'; +type T = { + foo: number; + bar: string; +}; +defineComponent((props: Partial)=>{}, { + props: { + foo: { + type: Number, + required: false + }, + bar: { + type: String, + required: false + } + } +}); diff --git a/visitor/tests/fixture/resolve-props-types/utility-type-pick/config.json b/visitor/tests/fixture/resolve-props-types/utility-type-pick/config.json new file mode 100644 index 0000000..46eaaca --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/utility-type-pick/config.json @@ -0,0 +1,3 @@ +{ + "resolveType": true +} diff --git a/visitor/tests/fixture/resolve-props-types/utility-type-pick/input.tsx b/visitor/tests/fixture/resolve-props-types/utility-type-pick/input.tsx new file mode 100644 index 0000000..308a27d --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/utility-type-pick/input.tsx @@ -0,0 +1,6 @@ +import { defineComponent } from 'vue' + +type T = { foo: number, bar: string, baz: boolean } +type K = 'foo' | 'bar' + +defineComponent((props: Pick) => { }) diff --git a/visitor/tests/fixture/resolve-props-types/utility-type-pick/output.js b/visitor/tests/fixture/resolve-props-types/utility-type-pick/output.js new file mode 100644 index 0000000..750fd8e --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/utility-type-pick/output.js @@ -0,0 +1,19 @@ +import { defineComponent } from 'vue'; +type T = { + foo: number; + bar: string; + baz: boolean; +}; +type K = 'foo' | 'bar'; +defineComponent((props: Pick)=>{}, { + props: { + foo: { + type: Number, + required: true + }, + bar: { + type: String, + required: true + } + } +}); diff --git a/visitor/tests/fixture/resolve-props-types/utility-type-required/config.json b/visitor/tests/fixture/resolve-props-types/utility-type-required/config.json new file mode 100644 index 0000000..46eaaca --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/utility-type-required/config.json @@ -0,0 +1,3 @@ +{ + "resolveType": true +} diff --git a/visitor/tests/fixture/resolve-props-types/utility-type-required/input.tsx b/visitor/tests/fixture/resolve-props-types/utility-type-required/input.tsx new file mode 100644 index 0000000..40d6ba1 --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/utility-type-required/input.tsx @@ -0,0 +1,5 @@ +import { defineComponent } from 'vue' + +type T = { foo?: number, bar?: string } + +defineComponent((props: Required) => { }) diff --git a/visitor/tests/fixture/resolve-props-types/utility-type-required/output.js b/visitor/tests/fixture/resolve-props-types/utility-type-required/output.js new file mode 100644 index 0000000..7529eec --- /dev/null +++ b/visitor/tests/fixture/resolve-props-types/utility-type-required/output.js @@ -0,0 +1,17 @@ +import { defineComponent } from 'vue'; +type T = { + foo?: number; + bar?: string; +}; +defineComponent((props: Required)=>{}, { + props: { + foo: { + type: Number, + required: true + }, + bar: { + type: String, + required: true + } + } +});