diff --git a/src/html/symbols/function.rs b/src/html/symbols/function.rs index 334da4c8..689e60c0 100644 --- a/src/html/symbols/function.rs +++ b/src/html/symbols/function.rs @@ -62,7 +62,6 @@ pub(crate) fn render_function( ctx: &RenderContext, doc_nodes: Vec<&DocNodeWithContext>, ) -> FunctionCtx { - // TODO: this needs to be handled more gracefully on the frontend let mut overloads_ctx = Vec::with_capacity(doc_nodes.len()); let mut functions_content = Vec::with_capacity(doc_nodes.len()); diff --git a/src/html/symbols/mod.rs b/src/html/symbols/mod.rs index c15aef99..35d91b7a 100644 --- a/src/html/symbols/mod.rs +++ b/src/html/symbols/mod.rs @@ -58,8 +58,6 @@ impl SymbolGroupCtx { split_nodes.sort_keys(); - // TODO: property drilldown - let symbols = split_nodes .values() .map(|doc_nodes| { diff --git a/src/html/types.rs b/src/html/types.rs index bd82d47b..6a3f97a8 100644 --- a/src/html/types.rs +++ b/src/html/types.rs @@ -56,8 +56,25 @@ pub(crate) fn render_type_def( format!("{:?}", html_escape::encode_safe(&def.repr)) } LiteralDefKind::Template => { - // TODO(@kitsonk) do this properly and escape properly - format!("`{}`", html_escape::encode_safe(&def.repr)) + if let Some(types) = &lit.ts_types { + let mut out = String::new(); + + for ts_type in types { + out.push_str(&if ts_type + .literal + .as_ref() + .is_some_and(|literal| literal.string.is_some()) + { + html_escape::encode_safe(&ts_type.repr).into_owned() + } else { + format!("${{{}}}", render_type_def(ctx, ts_type)) + }); + } + + format!("`{out}`") + } else { + format!("`{}`", html_escape::encode_safe(&def.repr)) + } } } } diff --git a/src/parser.rs b/src/parser.rs index 0a2ef152..55ed8b30 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -393,14 +393,14 @@ impl<'a> DocParser<'a> { var_declarator, ) .into_iter() - .find(|(name, _, _)| name.as_str() == &*ident.sym) - .map(|(name, var_def, _)| { + .find(|(name, _)| name.as_str() == &*ident.sym) + .map(|(name, var_def)| { let location = get_location(module_info.source(), ident.start()); DocNode::variable( name, location, DeclarationKind::Declare, - js_doc.clone(), + js_doc, var_def, ) }) diff --git a/src/printer.rs b/src/printer.rs index 598e4da1..ed91626d 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -1,15 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -// TODO(ry) This module builds up output by appending to a string. Instead it -// should either use a formatting trait -// https://doc.rust-lang.org/std/fmt/index.html#formatting-traits -// Or perhaps implement a Serializer for serde -// https://docs.serde.rs/serde/ser/trait.Serializer.html - -// TODO(ry) The methods in this module take ownership of the DocNodes, this is -// unnecessary and can result in unnecessary copying. Instead they should take -// references. - use crate::colors; use crate::display::display_abstract; use crate::display::display_async; diff --git a/src/ts_type.rs b/src/ts_type.rs index c6192a43..35e9c5c1 100644 --- a/src/ts_type.rs +++ b/src/ts_type.rs @@ -17,8 +17,6 @@ use crate::ParamDef; use deno_ast::swc::ast::*; use deno_ast::ParsedSource; -use deno_ast::SourceRange; -use deno_ast::SourceRangedForSpanned; use serde::Deserialize; use serde::Serialize; use std::fmt::Display; @@ -30,9 +28,15 @@ impl TsTypeDef { match &other.lit { TsLit::Number(num) => TsTypeDef::number_literal(num), TsLit::Str(str_) => TsTypeDef::string_literal(str_), - TsLit::Tpl(tpl) => { - TsTypeDef::tpl_literal(parsed_source, &tpl.types, &tpl.quasis) - } + TsLit::Tpl(tpl) => TsTypeDef::tpl_literal( + parsed_source, + tpl + .types + .iter() + .map(|types| TsTypeDef::new(parsed_source, types)) + .collect::>(), + &tpl.quasis, + ), TsLit::Bool(bool_) => TsTypeDef::bool_literal(bool_), TsLit::BigInt(bigint_) => TsTypeDef::bigint_literal(bigint_), } @@ -1128,49 +1132,6 @@ impl Display for TsTypePredicateDef { } } -fn get_range_from_type(ts_type: &TsType) -> SourceRange { - use deno_ast::swc::ast::TsType::*; - - match ts_type { - TsArrayType(ref t) => get_range_from_type(t.elem_type.as_ref()), - TsConditionalType(ref t) => get_range_from_type(t.check_type.as_ref()), - TsFnOrConstructorType(ref t) => { - if let Some(t) = t.clone().ts_constructor_type() { - t.range() - } else if let Some(t) = t.clone().ts_fn_type() { - t.range() - } else { - unreachable!("no type found") - } - } - TsImportType(ref t) => t.range(), - TsIndexedAccessType(ref t) => get_range_from_type(&t.index_type), - TsInferType(t) => t.range(), - TsKeywordType(t) => t.range(), - TsLitType(t) => t.range(), - TsMappedType(t) => t.range(), - TsOptionalType(t) => t.range(), - TsParenthesizedType(t) => t.range(), - TsRestType(t) => t.range(), - TsThisType(t) => t.range(), - TsTupleType(t) => t.range(), - TsTypeLit(t) => t.range(), - TsTypeOperator(t) => t.range(), - TsTypePredicate(t) => t.range(), - TsTypeQuery(t) => t.range(), - TsTypeRef(t) => t.range(), - TsUnionOrIntersectionType(t) => { - if let Some(t) = t.clone().ts_intersection_type() { - t.range() - } else if let Some(t) = t.clone().ts_union_type() { - t.range() - } else { - unreachable!("no type found") - } - } - } -} - impl TsTypeDef { pub fn number_literal(num: &Number) -> Self { Self::number_value(num.value) @@ -1205,16 +1166,16 @@ impl TsTypeDef { } pub fn tpl_literal( - parsed_source: &ParsedSource, - types: &[Box], + _parsed_source: &ParsedSource, + types: Vec, quasis: &[TplElement], ) -> Self { - let mut ts_types: Vec<(SourceRange, Self, String)> = Vec::new(); + let mut types_out: Vec<(Self, String)> = Vec::new(); for ts_type in types { - let t = TsTypeDef::new(parsed_source, ts_type); - let repr = format!("${{{}}}", t); - ts_types.push((get_range_from_type(ts_type), t, repr)) + let repr = format!("${{{}}}", ts_type); + types_out.push((ts_type, repr)) } + let mut qasis_out: Vec<(Self, String)> = Vec::new(); for quasi in quasis { let repr = quasi.raw.to_string(); let lit = LiteralDef { @@ -1224,15 +1185,22 @@ impl TsTypeDef { ts_types: None, boolean: None, }; - ts_types.push((quasi.range(), Self::literal(repr.clone(), lit), repr)); + qasis_out.push((Self::literal(repr.clone(), lit), repr)); } - ts_types.sort_by(|(a, _, _), (b, _, _)| a.cmp(b)); - let repr = ts_types - .iter() - .map(|(_, _, s)| s.as_str()) - .collect::>() - .join(""); - let ts_types = Some(ts_types.into_iter().map(|(_, t, _)| t).collect()); + + let mut out = vec![]; + + let mut types_iter = types_out.into_iter(); + + for quasi in qasis_out { + out.push(quasi); + if let Some(ts_type) = types_iter.next() { + out.push(ts_type); + } + } + + let repr = out.iter().map(|(_, s)| s.as_str()).collect::(); + let ts_types = Some(out.into_iter().map(|(t, _)| t).collect()); let lit = LiteralDef { kind: LiteralDefKind::Template, number: None, @@ -1815,11 +1783,14 @@ fn infer_ts_type_from_tpl( tpl: &Tpl, is_const: bool, ) -> TsTypeDef { - // TODO(@kitsonk) we should iterate over the expr and if each expr has a - // ts_type or can be trivially inferred, it should be passed to the - // tp_literal - if tpl.quasis.len() == 1 && is_const { - TsTypeDef::tpl_literal(parsed_source, &[], &tpl.quasis) + let exprs = tpl + .exprs + .iter() + .map(|expr| infer_ts_type_from_expr(parsed_source, expr, is_const)) + .collect::>>(); + + if let Some(exprs) = exprs { + TsTypeDef::tpl_literal(parsed_source, exprs, &tpl.quasis) } else { TsTypeDef::string_with_repr("string") } diff --git a/src/variable.rs b/src/variable.rs index 2764664d..bf2b95cf 100644 --- a/src/variable.rs +++ b/src/variable.rs @@ -1,8 +1,8 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use deno_ast::swc::ast::Pat; -use deno_ast::SourceRange; -use deno_ast::SourceRangedForSpanned; +use deno_ast::swc::ast::VarDeclKind; +use deno_ast::ParsedSource; use deno_graph::symbols::EsModuleInfo; use deno_graph::symbols::SymbolNodeRef; use serde::Deserialize; @@ -10,20 +10,21 @@ use serde::Serialize; use crate::ts_type::infer_simple_ts_type_from_var_decl; use crate::ts_type::TsTypeDef; +use crate::ts_type::TsTypeDefKind; #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] pub struct VariableDef { pub ts_type: Option, - pub kind: deno_ast::swc::ast::VarDeclKind, + pub kind: VarDeclKind, } pub fn get_docs_for_var_declarator( module_info: &EsModuleInfo, var_decl: &deno_ast::swc::ast::VarDecl, var_declarator: &deno_ast::swc::ast::VarDeclarator, -) -> Vec<(String, VariableDef, Option)> { - let mut items = Vec::<(String, VariableDef, Option)>::new(); +) -> Vec<(String, VariableDef)> { + let mut items = Vec::<(String, VariableDef)>::new(); let ref_name: Option = var_declarator.init.as_ref().and_then(|init| { if let deno_ast::swc::ast::Expr::Ident(ident) = &**init { @@ -36,7 +37,8 @@ pub fn get_docs_for_var_declarator( let maybe_ts_type_ann = match &var_declarator.name { Pat::Ident(ident) => ident.type_ann.as_ref(), Pat::Object(pat) => pat.type_ann.as_ref(), - _ => None, + Pat::Array(pat) => pat.type_ann.as_ref(), + Pat::Invalid(_) | Pat::Expr(_) | Pat::Rest(_) | Pat::Assign(_) => None, }; let maybe_ts_type = maybe_ts_type_ann .map(|def| TsTypeDef::new(module_info.source(), &def.type_ann)) @@ -61,7 +63,7 @@ pub fn get_docs_for_var_declarator( let maybe_type_ann = infer_simple_ts_type_from_var_decl( module_info.source(), var_declarator, - var_decl.kind == deno_ast::swc::ast::VarDeclKind::Const, + var_decl.kind == VarDeclKind::Const, ); if let Some(type_ann) = maybe_type_ann { return Some(type_ann); @@ -77,7 +79,7 @@ pub fn get_docs_for_var_declarator( infer_simple_ts_type_from_var_decl( module_info.source(), var_declarator, - var_decl.kind == deno_ast::swc::ast::VarDeclKind::Const, + var_decl.kind == VarDeclKind::Const, ) }); @@ -88,47 +90,178 @@ pub fn get_docs_for_var_declarator( ts_type: maybe_ts_type, kind: var_decl.kind, }; - items.push((var_name, variable_def, Some(var_declarator.range()))); + items.push((var_name, variable_def)); } - Pat::Object(pat) => { - for prop in &pat.props { - let (name, reassign_name, maybe_range) = match prop { - deno_ast::swc::ast::ObjectPatProp::KeyValue(kv) => ( - crate::params::prop_name_to_string(module_info.source(), &kv.key), - match &*kv.value { - Pat::Ident(ident) => Some(ident.sym.to_string()), - _ => None, // TODO: properly implement - }, - None, - ), - deno_ast::swc::ast::ObjectPatProp::Assign(assign) => { - (assign.key.sym.to_string(), None, Some(assign.range())) - } - deno_ast::swc::ast::ObjectPatProp::Rest(_) => { - ("".to_string(), None, None) - } // TODO: properly implement - }; + Pat::Object(obj) => get_vars_from_obj_destructuring( + obj, + var_decl.kind, + maybe_ts_type.as_ref(), + &mut items, + module_info.source(), + ), + Pat::Array(arr) => get_vars_from_array_destructuring( + arr, + var_decl.kind, + maybe_ts_type.as_ref(), + &mut items, + module_info.source(), + ), + Pat::Expr(_) | Pat::Invalid(_) | Pat::Assign(_) | Pat::Rest(_) => {} + } + items +} - let ts_type = maybe_ts_type.as_ref().and_then(|ts_type| { - ts_type.type_literal.as_ref().and_then(|type_literal| { - type_literal.properties.iter().find_map(|property| { - if property.name == name { - property.ts_type.clone() - } else { - None +fn get_vars_from_obj_destructuring( + obj: &deno_ast::swc::ast::ObjectPat, + kind: VarDeclKind, + maybe_ts_type: Option<&TsTypeDef>, + items: &mut Vec<(String, VariableDef)>, + source: &ParsedSource, +) { + let mut reached_rest = false; + for prop in &obj.props { + assert!(!reached_rest, "object rest is always last"); + let (name, reassign_name, rest_type_ann) = match prop { + deno_ast::swc::ast::ObjectPatProp::KeyValue(kv) => ( + crate::params::prop_name_to_string(source, &kv.key), + match &*kv.value { + Pat::Ident(ident) => Some(ident.sym.to_string()), + Pat::Assign(assign) => { + let name = match &*assign.left { + Pat::Ident(ident) => ident.sym.to_string(), + Pat::Rest(_) => unreachable!("assign cannot have rest"), + Pat::Assign(_) => unreachable!("rest cannot have assign"), + Pat::Array(_) | Pat::Object(_) => { + continue; // TODO(@crowlKats): implement recursive destructuring } - }) + Pat::Invalid(_) | Pat::Expr(_) => continue, + }; + + Some(name) + } + Pat::Array(_) | Pat::Object(_) => { + continue; // TODO(@crowlKats): implement recursive destructuring + } + Pat::Rest(_) | Pat::Invalid(_) | Pat::Expr(_) => { + continue; + } + }, + None, + ), + deno_ast::swc::ast::ObjectPatProp::Assign(assign) => { + (assign.key.sym.to_string(), None, None) + } + deno_ast::swc::ast::ObjectPatProp::Rest(rest) => { + reached_rest = true; + + ( + match &*rest.arg { + Pat::Ident(ident) => ident.sym.to_string(), + Pat::Rest(_) => unreachable!("rest cannot have rest"), + Pat::Assign(_) => unreachable!("rest cannot have assign"), + Pat::Array(_) | Pat::Object(_) => { + continue; // TODO(@crowlKats): implement recursive destructuring + } + Pat::Invalid(_) | Pat::Expr(_) => continue, + }, + None, + rest.type_ann.as_ref(), + ) + } + }; + + let ts_type = if !reached_rest { + maybe_ts_type.as_ref().and_then(|ts_type| { + ts_type.type_literal.as_ref().and_then(|type_literal| { + type_literal.properties.iter().find_map(|property| { + if property.name == name { + property.ts_type.clone() + } else { + None + } }) - }); + }) + }) + } else { + rest_type_ann.map(|type_ann| TsTypeDef::new(source, &type_ann.type_ann)) + }; - let variable_def = VariableDef { - ts_type, - kind: var_decl.kind, + let variable_def = VariableDef { ts_type, kind }; + items.push((reassign_name.unwrap_or(name), variable_def)); + } +} + +fn get_vars_from_array_destructuring( + arr: &deno_ast::swc::ast::ArrayPat, + kind: VarDeclKind, + maybe_ts_type: Option<&TsTypeDef>, + items: &mut Vec<(String, VariableDef)>, + source: &ParsedSource, +) { + let mut reached_rest = false; + for (i, elem) in arr.elems.iter().enumerate() { + assert!(!reached_rest, "object rest is always last"); + let Some(elem) = elem else { + continue; + }; + + let (name, rest_type_ann) = match elem { + Pat::Ident(ident) => (ident.sym.to_string(), None), + Pat::Rest(rest) => { + reached_rest = true; + ( + match &*rest.arg { + Pat::Ident(ident) => ident.sym.to_string(), + Pat::Rest(_) => unreachable!("rest cannot have rest"), + Pat::Assign(_) => unreachable!("rest cannot have assign"), + Pat::Array(_) | Pat::Object(_) => { + continue; // TODO(@crowlKats): implement recursive destructuring + } + Pat::Invalid(_) | Pat::Expr(_) => continue, + }, + rest.type_ann.as_ref(), + ) + } + Pat::Assign(assign) => { + let name = match &*assign.left { + Pat::Ident(ident) => ident.sym.to_string(), + Pat::Rest(_) => unreachable!("assign cannot have rest"), + Pat::Assign(_) => unreachable!("rest cannot have assign"), + Pat::Array(_) | Pat::Object(_) => { + continue; // TODO(@crowlKats): implement recursive destructuring + } + Pat::Invalid(_) | Pat::Expr(_) => continue, }; - items.push((reassign_name.unwrap_or(name), variable_def, maybe_range)); + + (name, None) } - } - _ => (), + Pat::Array(_) | Pat::Object(_) => { + continue; // TODO(@crowlKats): implement recursive destructuring + } + Pat::Invalid(_) | Pat::Expr(_) => continue, + }; + + let ts_type = if !reached_rest { + maybe_ts_type.and_then(|ts_type| match ts_type.kind.as_ref()? { + TsTypeDefKind::Array => Some(*ts_type.array.clone().unwrap()), + TsTypeDefKind::Tuple => ts_type.tuple.as_ref().unwrap().get(i).cloned(), + _ => None, + }) + } else { + rest_type_ann + .map(|type_ann| TsTypeDef::new(source, &type_ann.type_ann)) + .or_else(|| { + maybe_ts_type.and_then(|ts_type| { + if ts_type.kind == Some(TsTypeDefKind::Array) { + Some(ts_type.clone()) + } else { + None + } + }) + }) + }; + + let variable_def = VariableDef { ts_type, kind }; + items.push((name, variable_def)); } - items } diff --git a/tests/specs/DestructuringAssignmentArray.txt b/tests/specs/DestructuringAssignmentArray.txt new file mode 100644 index 00000000..d45db482 --- /dev/null +++ b/tests/specs/DestructuringAssignmentArray.txt @@ -0,0 +1,269 @@ +# mod.ts +const array = ["a", "b"]; + +const [a1, b1] = array; +const [a2, , b2] = array; +const [a3 = "foo", b3] = array; +const [a4, b4, ...rest4] = array; +const [a5, , b5, ...rest5] = array; + +# output.txt +Defined in file:///mod.ts:3:8 + +private const a1 + +Defined in file:///mod.ts:4:8 + +private const a2 + +Defined in file:///mod.ts:5:8 + +private const a3 + +Defined in file:///mod.ts:6:8 + +private const a4 + +Defined in file:///mod.ts:7:8 + +private const a5 + +Defined in file:///mod.ts:1:7 + +private const array: string[] + +Defined in file:///mod.ts:3:12 + +private const b1 + +Defined in file:///mod.ts:4:14 + +private const b2 + +Defined in file:///mod.ts:5:20 + +private const b3 + +Defined in file:///mod.ts:6:12 + +private const b4 + +Defined in file:///mod.ts:7:14 + +private const b5 + +Defined in file:///mod.ts:6:19 + +private const rest4 + +Defined in file:///mod.ts:7:21 + +private const rest5 + + +# output.json +[ + { + "kind": "variable", + "name": "array", + "location": { + "filename": "file:///mod.ts", + "line": 1, + "col": 6, + "byteIndex": 6 + }, + "declarationKind": "private", + "variableDef": { + "tsType": { + "repr": "", + "kind": "array", + "array": { + "repr": "string", + "kind": "keyword", + "keyword": "string" + } + }, + "kind": "const" + } + }, + { + "kind": "variable", + "name": "a1", + "location": { + "filename": "file:///mod.ts", + "line": 3, + "col": 7, + "byteIndex": 34 + }, + "declarationKind": "private", + "variableDef": { + "tsType": null, + "kind": "const" + } + }, + { + "kind": "variable", + "name": "b1", + "location": { + "filename": "file:///mod.ts", + "line": 3, + "col": 11, + "byteIndex": 38 + }, + "declarationKind": "private", + "variableDef": { + "tsType": null, + "kind": "const" + } + }, + { + "kind": "variable", + "name": "a2", + "location": { + "filename": "file:///mod.ts", + "line": 4, + "col": 7, + "byteIndex": 58 + }, + "declarationKind": "private", + "variableDef": { + "tsType": null, + "kind": "const" + } + }, + { + "kind": "variable", + "name": "b2", + "location": { + "filename": "file:///mod.ts", + "line": 4, + "col": 13, + "byteIndex": 64 + }, + "declarationKind": "private", + "variableDef": { + "tsType": null, + "kind": "const" + } + }, + { + "kind": "variable", + "name": "a3", + "location": { + "filename": "file:///mod.ts", + "line": 5, + "col": 7, + "byteIndex": 84 + }, + "declarationKind": "private", + "variableDef": { + "tsType": null, + "kind": "const" + } + }, + { + "kind": "variable", + "name": "b3", + "location": { + "filename": "file:///mod.ts", + "line": 5, + "col": 19, + "byteIndex": 96 + }, + "declarationKind": "private", + "variableDef": { + "tsType": null, + "kind": "const" + } + }, + { + "kind": "variable", + "name": "a4", + "location": { + "filename": "file:///mod.ts", + "line": 6, + "col": 7, + "byteIndex": 116 + }, + "declarationKind": "private", + "variableDef": { + "tsType": null, + "kind": "const" + } + }, + { + "kind": "variable", + "name": "b4", + "location": { + "filename": "file:///mod.ts", + "line": 6, + "col": 11, + "byteIndex": 120 + }, + "declarationKind": "private", + "variableDef": { + "tsType": null, + "kind": "const" + } + }, + { + "kind": "variable", + "name": "rest4", + "location": { + "filename": "file:///mod.ts", + "line": 6, + "col": 18, + "byteIndex": 127 + }, + "declarationKind": "private", + "variableDef": { + "tsType": null, + "kind": "const" + } + }, + { + "kind": "variable", + "name": "a5", + "location": { + "filename": "file:///mod.ts", + "line": 7, + "col": 7, + "byteIndex": 150 + }, + "declarationKind": "private", + "variableDef": { + "tsType": null, + "kind": "const" + } + }, + { + "kind": "variable", + "name": "b5", + "location": { + "filename": "file:///mod.ts", + "line": 7, + "col": 13, + "byteIndex": 156 + }, + "declarationKind": "private", + "variableDef": { + "tsType": null, + "kind": "const" + } + }, + { + "kind": "variable", + "name": "rest5", + "location": { + "filename": "file:///mod.ts", + "line": 7, + "col": 20, + "byteIndex": 163 + }, + "declarationKind": "private", + "variableDef": { + "tsType": null, + "kind": "const" + } + } +] diff --git a/tests/specs/DestructuringAssignmentObject.txt b/tests/specs/DestructuringAssignmentObject.txt new file mode 100644 index 00000000..8cce0f4b --- /dev/null +++ b/tests/specs/DestructuringAssignmentObject.txt @@ -0,0 +1,178 @@ +# mod.ts +const obj = { a: "foo", b: "bar" }; +const { a, b, ...rest } = obj; +const { a: a1, b: b1 } = obj; +const { ["a"]: a2 } = obj; + +# output.txt +Defined in file:///mod.ts:2:9 + +private const a + +Defined in file:///mod.ts:3:12 + +private const a1 + +Defined in file:///mod.ts:4:16 + +private const a2 + +Defined in file:///mod.ts:2:12 + +private const b + +Defined in file:///mod.ts:3:19 + +private const b1 + +Defined in file:///mod.ts:1:7 + +private const obj: { a: string; b: string; } + +Defined in file:///mod.ts:2:18 + +private const rest + + +# output.json +[ + { + "kind": "variable", + "name": "obj", + "location": { + "filename": "file:///mod.ts", + "line": 1, + "col": 6, + "byteIndex": 6 + }, + "declarationKind": "private", + "variableDef": { + "tsType": { + "repr": "", + "kind": "typeLiteral", + "typeLiteral": { + "methods": [], + "properties": [ + { + "name": "a", + "params": [], + "computed": false, + "optional": false, + "tsType": { + "repr": "string", + "kind": "keyword", + "keyword": "string" + }, + "typeParams": [] + }, + { + "name": "b", + "params": [], + "computed": false, + "optional": false, + "tsType": { + "repr": "string", + "kind": "keyword", + "keyword": "string" + }, + "typeParams": [] + } + ], + "callSignatures": [], + "indexSignatures": [] + } + }, + "kind": "const" + } + }, + { + "kind": "variable", + "name": "a", + "location": { + "filename": "file:///mod.ts", + "line": 2, + "col": 8, + "byteIndex": 44 + }, + "declarationKind": "private", + "variableDef": { + "tsType": null, + "kind": "const" + } + }, + { + "kind": "variable", + "name": "b", + "location": { + "filename": "file:///mod.ts", + "line": 2, + "col": 11, + "byteIndex": 47 + }, + "declarationKind": "private", + "variableDef": { + "tsType": null, + "kind": "const" + } + }, + { + "kind": "variable", + "name": "rest", + "location": { + "filename": "file:///mod.ts", + "line": 2, + "col": 17, + "byteIndex": 53 + }, + "declarationKind": "private", + "variableDef": { + "tsType": null, + "kind": "const" + } + }, + { + "kind": "variable", + "name": "a1", + "location": { + "filename": "file:///mod.ts", + "line": 3, + "col": 11, + "byteIndex": 78 + }, + "declarationKind": "private", + "variableDef": { + "tsType": null, + "kind": "const" + } + }, + { + "kind": "variable", + "name": "b1", + "location": { + "filename": "file:///mod.ts", + "line": 3, + "col": 18, + "byteIndex": 85 + }, + "declarationKind": "private", + "variableDef": { + "tsType": null, + "kind": "const" + } + }, + { + "kind": "variable", + "name": "a2", + "location": { + "filename": "file:///mod.ts", + "line": 4, + "col": 15, + "byteIndex": 112 + }, + "declarationKind": "private", + "variableDef": { + "tsType": null, + "kind": "const" + } + } +] diff --git a/tests/specs/DestructuringAssignmentObjectAssignment.txt b/tests/specs/DestructuringAssignmentObjectAssignment.txt new file mode 100644 index 00000000..f6a376f6 --- /dev/null +++ b/tests/specs/DestructuringAssignmentObjectAssignment.txt @@ -0,0 +1,100 @@ +# mod.ts +const obj = { a: "foo", b: "bar" }; +const { a: a1 = "a", b = "b" } = obj; + +# output.txt +Defined in file:///mod.ts:2:12 + +private const a1 + +Defined in file:///mod.ts:2:22 + +private const b + +Defined in file:///mod.ts:1:7 + +private const obj: { a: string; b: string; } + + +# output.json +[ + { + "kind": "variable", + "name": "obj", + "location": { + "filename": "file:///mod.ts", + "line": 1, + "col": 6, + "byteIndex": 6 + }, + "declarationKind": "private", + "variableDef": { + "tsType": { + "repr": "", + "kind": "typeLiteral", + "typeLiteral": { + "methods": [], + "properties": [ + { + "name": "a", + "params": [], + "computed": false, + "optional": false, + "tsType": { + "repr": "string", + "kind": "keyword", + "keyword": "string" + }, + "typeParams": [] + }, + { + "name": "b", + "params": [], + "computed": false, + "optional": false, + "tsType": { + "repr": "string", + "kind": "keyword", + "keyword": "string" + }, + "typeParams": [] + } + ], + "callSignatures": [], + "indexSignatures": [] + } + }, + "kind": "const" + } + }, + { + "kind": "variable", + "name": "a1", + "location": { + "filename": "file:///mod.ts", + "line": 2, + "col": 11, + "byteIndex": 47 + }, + "declarationKind": "private", + "variableDef": { + "tsType": null, + "kind": "const" + } + }, + { + "kind": "variable", + "name": "b", + "location": { + "filename": "file:///mod.ts", + "line": 2, + "col": 21, + "byteIndex": 57 + }, + "declarationKind": "private", + "variableDef": { + "tsType": null, + "kind": "const" + } + } +] diff --git a/tests/specs/ExportLet.txt b/tests/specs/ExportLet.txt index 0fe88a36..b949daa2 100644 --- a/tests/specs/ExportLet.txt +++ b/tests/specs/ExportLet.txt @@ -84,7 +84,7 @@ let sym: symbol Defined in file:///mod.ts:7:12 -let tpl: string +let tpl: `foobarbaz` # output.json @@ -218,9 +218,21 @@ let tpl: string "declarationKind": "export", "variableDef": { "tsType": { - "repr": "string", - "kind": "keyword", - "keyword": "string" + "repr": "foobarbaz", + "kind": "literal", + "literal": { + "kind": "template", + "tsTypes": [ + { + "repr": "foobarbaz", + "kind": "literal", + "literal": { + "kind": "string", + "string": "foobarbaz" + } + } + ] + } }, "kind": "let" } diff --git a/tests/specs/InferTsTypes.txt b/tests/specs/InferTsTypes.txt index f126ab4c..d9ef3c45 100644 --- a/tests/specs/InferTsTypes.txt +++ b/tests/specs/InferTsTypes.txt @@ -5,6 +5,7 @@ export let b = false; export let bi = 100n; export let re = /hello/; export let tpl = `foobar`; +export let complexTpl = `f${true ? "a" : 1}${true ? "b" : null}o${"c"}${1}o`; export function test() { console.log("hello"); @@ -98,163 +99,170 @@ error[missing-jsdoc]: exported symbol is missing JSDoc documentation error[missing-jsdoc]: exported symbol is missing JSDoc documentation - --> /mod.ts:8:1 + --> /mod.ts:7:12 | -8 | export function test() { +7 | export let complexTpl = `f${true ? "a" : 1}${true ? "b" : null}o${"c"}${1}o`; + | ^ + + +error[missing-jsdoc]: exported symbol is missing JSDoc documentation + --> /mod.ts:9:1 + | +9 | export function test() { | ^ error[missing-jsdoc]: exported symbol is missing JSDoc documentation - --> /mod.ts:12:1 + --> /mod.ts:13:1 | -12 | export async function testAsync() { +13 | export async function testAsync() { | ^ error[missing-jsdoc]: exported symbol is missing JSDoc documentation - --> /mod.ts:16:1 + --> /mod.ts:17:1 | -16 | export function cannotInfer() { +17 | export function cannotInfer() { | ^ error[missing-return-type]: exported function is missing an explicit return type annotation - --> /mod.ts:16:1 + --> /mod.ts:17:1 | -16 | export function cannotInfer() { +17 | export function cannotInfer() { | ^ error[missing-jsdoc]: exported symbol is missing JSDoc documentation - --> /mod.ts:22:1 + --> /mod.ts:23:1 | -22 | export async function cannotInferAsync() { +23 | export async function cannotInferAsync() { | ^ error[missing-return-type]: exported function is missing an explicit return type annotation - --> /mod.ts:22:1 + --> /mod.ts:23:1 | -22 | export async function cannotInferAsync() { +23 | export async function cannotInferAsync() { | ^ error[missing-jsdoc]: exported symbol is missing JSDoc documentation - --> /mod.ts:28:1 + --> /mod.ts:29:1 | -28 | export function* cannotInferGenerator() { +29 | export function* cannotInferGenerator() { | ^ error[missing-return-type]: exported function is missing an explicit return type annotation - --> /mod.ts:28:1 + --> /mod.ts:29:1 | -28 | export function* cannotInferGenerator() { +29 | export function* cannotInferGenerator() { | ^ error[missing-jsdoc]: exported symbol is missing JSDoc documentation - --> /mod.ts:31:1 + --> /mod.ts:32:1 | -31 | export class MyClass { +32 | export class MyClass { | ^ error[missing-jsdoc]: exported symbol is missing JSDoc documentation - --> /mod.ts:32:3 + --> /mod.ts:33:3 | -32 | infer() { +33 | infer() { | ^ error[missing-jsdoc]: exported symbol is missing JSDoc documentation - --> /mod.ts:34:3 + --> /mod.ts:35:3 | -34 | async inferAync() { +35 | async inferAync() { | ^ error[missing-jsdoc]: exported symbol is missing JSDoc documentation - --> /mod.ts:37:3 + --> /mod.ts:38:3 | -37 | cannotInfer() { +38 | cannotInfer() { | ^ error[missing-return-type]: exported function is missing an explicit return type annotation - --> /mod.ts:37:3 + --> /mod.ts:38:3 | -37 | cannotInfer() { +38 | cannotInfer() { | ^ error[missing-jsdoc]: exported symbol is missing JSDoc documentation - --> /mod.ts:43:3 + --> /mod.ts:44:3 | -43 | async cannotInferAsync() { +44 | async cannotInferAsync() { | ^ error[missing-return-type]: exported function is missing an explicit return type annotation - --> /mod.ts:43:3 + --> /mod.ts:44:3 | -43 | async cannotInferAsync() { +44 | async cannotInferAsync() { | ^ error[missing-jsdoc]: exported symbol is missing JSDoc documentation - --> /mod.ts:47:3 + --> /mod.ts:48:3 | -47 | *cannotInferGenerator() { +48 | *cannotInferGenerator() { | ^ error[missing-return-type]: exported function is missing an explicit return type annotation - --> /mod.ts:47:3 + --> /mod.ts:48:3 | -47 | *cannotInferGenerator() { +48 | *cannotInferGenerator() { | ^ error[missing-jsdoc]: exported symbol is missing JSDoc documentation - --> /mod.ts:51:1 + --> /mod.ts:52:1 | -51 | export namespace MyNamespace { +52 | export namespace MyNamespace { | ^ error[missing-jsdoc]: exported symbol is missing JSDoc documentation - --> /mod.ts:53:3 + --> /mod.ts:54:3 | -53 | declare function cannotInfer(); +54 | declare function cannotInfer(); | ^ error[missing-return-type]: exported function is missing an explicit return type annotation - --> /mod.ts:53:3 + --> /mod.ts:54:3 | -53 | declare function cannotInfer(); +54 | declare function cannotInfer(); | ^ # output.txt -Defined in file:///mod.ts:16:1 +Defined in file:///mod.ts:17:1 function cannotInfer() -Defined in file:///mod.ts:22:1 +Defined in file:///mod.ts:23:1 async function cannotInferAsync() -Defined in file:///mod.ts:28:1 +Defined in file:///mod.ts:29:1 function* cannotInferGenerator() -Defined in file:///mod.ts:8:1 +Defined in file:///mod.ts:9:1 function test(): void -Defined in file:///mod.ts:12:1 +Defined in file:///mod.ts:13:1 async function testAsync(): Promise @@ -266,6 +274,10 @@ Defined in file:///mod.ts:4:12 let bi: bigint +Defined in file:///mod.ts:7:12 + +let complexTpl: `f${string | number}${string | null}o${string}${number}o` + Defined in file:///mod.ts:2:12 let n: number @@ -280,9 +292,9 @@ let s: string Defined in file:///mod.ts:6:12 -let tpl: string +let tpl: `foobar` -Defined in file:///mod.ts:31:1 +Defined in file:///mod.ts:32:1 class MyClass @@ -292,7 +304,7 @@ class MyClass async cannotInferAsync() *cannotInferGenerator() -Defined in file:///mod.ts:51:1 +Defined in file:///mod.ts:52:1 namespace MyNamespace @@ -411,9 +423,126 @@ namespace MyNamespace "declarationKind": "export", "variableDef": { "tsType": { - "repr": "string", - "kind": "keyword", - "keyword": "string" + "repr": "foobar", + "kind": "literal", + "literal": { + "kind": "template", + "tsTypes": [ + { + "repr": "foobar", + "kind": "literal", + "literal": { + "kind": "string", + "string": "foobar" + } + } + ] + } + }, + "kind": "let" + } + }, + { + "kind": "variable", + "name": "complexTpl", + "location": { + "filename": "file:///mod.ts", + "line": 7, + "col": 11, + "byteIndex": 151 + }, + "declarationKind": "export", + "variableDef": { + "tsType": { + "repr": "f${string | number}${string | null}o${string}${number}o", + "kind": "literal", + "literal": { + "kind": "template", + "tsTypes": [ + { + "repr": "f", + "kind": "literal", + "literal": { + "kind": "string", + "string": "f" + } + }, + { + "repr": "", + "kind": "union", + "union": [ + { + "repr": "string", + "kind": "keyword", + "keyword": "string" + }, + { + "repr": "number", + "kind": "keyword", + "keyword": "number" + } + ] + }, + { + "repr": "", + "kind": "literal", + "literal": { + "kind": "string", + "string": "" + } + }, + { + "repr": "", + "kind": "union", + "union": [ + { + "repr": "string", + "kind": "keyword", + "keyword": "string" + }, + { + "repr": "null", + "kind": "keyword", + "keyword": "null" + } + ] + }, + { + "repr": "o", + "kind": "literal", + "literal": { + "kind": "string", + "string": "o" + } + }, + { + "repr": "string", + "kind": "keyword", + "keyword": "string" + }, + { + "repr": "", + "kind": "literal", + "literal": { + "kind": "string", + "string": "" + } + }, + { + "repr": "number", + "kind": "keyword", + "keyword": "number" + }, + { + "repr": "o", + "kind": "literal", + "literal": { + "kind": "string", + "string": "o" + } + } + ] + } }, "kind": "let" } @@ -423,9 +552,9 @@ namespace MyNamespace "name": "test", "location": { "filename": "file:///mod.ts", - "line": 8, + "line": 9, "col": 0, - "byteIndex": 141 + "byteIndex": 219 }, "declarationKind": "export", "functionDef": { @@ -446,9 +575,9 @@ namespace MyNamespace "name": "testAsync", "location": { "filename": "file:///mod.ts", - "line": 12, + "line": 13, "col": 0, - "byteIndex": 193 + "byteIndex": 271 }, "declarationKind": "export", "functionDef": { @@ -478,9 +607,9 @@ namespace MyNamespace "name": "cannotInfer", "location": { "filename": "file:///mod.ts", - "line": 16, + "line": 17, "col": 0, - "byteIndex": 260 + "byteIndex": 338 }, "declarationKind": "export", "functionDef": { @@ -497,9 +626,9 @@ namespace MyNamespace "name": "cannotInferAsync", "location": { "filename": "file:///mod.ts", - "line": 22, + "line": 23, "col": 0, - "byteIndex": 337 + "byteIndex": 415 }, "declarationKind": "export", "functionDef": { @@ -516,9 +645,9 @@ namespace MyNamespace "name": "cannotInferGenerator", "location": { "filename": "file:///mod.ts", - "line": 28, + "line": 29, "col": 0, - "byteIndex": 447 + "byteIndex": 525 }, "declarationKind": "export", "functionDef": { @@ -535,9 +664,9 @@ namespace MyNamespace "name": "MyClass", "location": { "filename": "file:///mod.ts", - "line": 31, + "line": 32, "col": 0, - "byteIndex": 492 + "byteIndex": 570 }, "declarationKind": "export", "classDef": { @@ -567,9 +696,9 @@ namespace MyNamespace }, "location": { "filename": "file:///mod.ts", - "line": 32, + "line": 33, "col": 2, - "byteIndex": 517 + "byteIndex": 595 } }, { @@ -602,9 +731,9 @@ namespace MyNamespace }, "location": { "filename": "file:///mod.ts", - "line": 34, + "line": 35, "col": 2, - "byteIndex": 533 + "byteIndex": 611 } }, { @@ -624,9 +753,9 @@ namespace MyNamespace }, "location": { "filename": "file:///mod.ts", - "line": 37, + "line": 38, "col": 2, - "byteIndex": 560 + "byteIndex": 638 } }, { @@ -646,9 +775,9 @@ namespace MyNamespace }, "location": { "filename": "file:///mod.ts", - "line": 43, + "line": 44, "col": 2, - "byteIndex": 650 + "byteIndex": 728 } }, { @@ -668,9 +797,9 @@ namespace MyNamespace }, "location": { "filename": "file:///mod.ts", - "line": 47, + "line": 48, "col": 2, - "byteIndex": 714 + "byteIndex": 792 } } ], @@ -685,9 +814,9 @@ namespace MyNamespace "name": "MyNamespace", "location": { "filename": "file:///mod.ts", - "line": 51, + "line": 52, "col": 0, - "byteIndex": 747 + "byteIndex": 825 }, "declarationKind": "export", "namespaceDef": { @@ -697,9 +826,9 @@ namespace MyNamespace "name": "cannotInfer", "location": { "filename": "file:///mod.ts", - "line": 53, + "line": 54, "col": 2, - "byteIndex": 831 + "byteIndex": 909 }, "declarationKind": "declare", "functionDef": {