Skip to content

Commit 0c107a0

Browse files
committed
implement destructuring of interfaces
1 parent 23b6089 commit 0c107a0

File tree

5 files changed

+165
-3
lines changed

5 files changed

+165
-3
lines changed

crates/biome_js_type_info/src/flattening.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,50 @@ fn flattened(mut ty: TypeData, resolver: &mut dyn TypeResolver, depth: usize) ->
216216
.collect(),
217217
);
218218
}
219+
(TypeData::Interface(interface), DestructureField::Name(name)) => {
220+
match interface.members.iter().find(|own_member| {
221+
own_member.is_static() && own_member.has_name(name.text())
222+
}) {
223+
Some(member) => {
224+
ty = flattened(
225+
resolver
226+
.resolve_and_get(
227+
&resolved
228+
.apply_module_id_to_reference(&member.ty),
229+
)
230+
.map(ResolvedTypeData::to_data)
231+
.unwrap_or_default(),
232+
resolver,
233+
depth,
234+
);
235+
}
236+
None => return TypeData::reference(GLOBAL_UNKNOWN_ID),
237+
}
238+
}
239+
(
240+
TypeData::Interface(interface),
241+
DestructureField::RestExcept(names),
242+
) => {
243+
return TypeData::object_with_members(
244+
interface
245+
.members
246+
.iter()
247+
.filter(|own_member| {
248+
own_member.is_static()
249+
&& !names
250+
.iter()
251+
.any(|name| own_member.has_name(name))
252+
})
253+
.map(|member| {
254+
ResolvedTypeMember::from((
255+
resolved.resolver_id(),
256+
member,
257+
))
258+
.to_member()
259+
})
260+
.collect(),
261+
);
262+
}
219263
(subject, DestructureField::Index(index)) => {
220264
return subject
221265
.clone()

crates/biome_js_type_info/src/helpers.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::borrow::Cow;
33
use biome_rowan::Text;
44

55
use crate::{
6-
BindingId, Class, GenericTypeParameter, Module, Namespace, Object, ResolvedTypeData,
6+
BindingId, Class, GenericTypeParameter, Interface, Module, Namespace, Object, ResolvedTypeData,
77
ResolvedTypeId, ResolvedTypeMember, ResolverId, TypeData, TypeInstance, TypeReference,
88
TypeResolver,
99
globals::{GLOBAL_ARRAY_ID, GLOBAL_PROMISE_ID, GLOBAL_TYPE_MEMBERS},
@@ -243,6 +243,18 @@ impl<'a> Iterator for TypeMemberIterator<'a> {
243243
}
244244
}
245245
Some(TypeMemberOwner::InstanceOf(instance_of)) => &instance_of.ty,
246+
Some(TypeMemberOwner::Interface(interface)) => {
247+
match interface.members.get(self.index) {
248+
Some(member) => {
249+
self.index += 1;
250+
return Some((self.resolver_id, member).into());
251+
}
252+
None => {
253+
self.owner = None;
254+
return None;
255+
}
256+
}
257+
}
246258
Some(TypeMemberOwner::Module(module)) => match module.members.get(self.index) {
247259
Some(member) => {
248260
self.index += 1;
@@ -322,6 +334,7 @@ enum TypeMemberOwner<'a> {
322334
Class(&'a Class),
323335
Global,
324336
InstanceOf(&'a TypeInstance),
337+
Interface(&'a Interface),
325338
Module(&'a Module),
326339
Namespace(&'a Namespace),
327340
Object(&'a Object),
@@ -333,6 +346,7 @@ impl<'a> TypeMemberOwner<'a> {
333346
TypeData::Class(class) => Some(Self::Class(class)),
334347
TypeData::Global => Some(Self::Global),
335348
TypeData::InstanceOf(type_instance) => Some(Self::InstanceOf(type_instance)),
349+
TypeData::Interface(interface) => Some(Self::Interface(interface)),
336350
TypeData::Module(module) => Some(Self::Module(module)),
337351
TypeData::Namespace(namespace) => Some(Self::Namespace(namespace)),
338352
TypeData::Object(object) => Some(Self::Object(object)),

crates/biome_js_type_info/tests/flattening.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ use biome_js_type_info::{GlobalsResolver, TypeData};
44

55
use utils::{
66
HardcodedSymbolResolver, assert_type_data_snapshot, assert_typed_bindings_snapshot,
7-
get_expression, get_function_declaration, get_variable_declaration, parse_ts,
7+
get_expression, get_function_declaration, get_interface_declaration, get_variable_declaration,
8+
parse_ts,
89
};
910

1011
#[test]
@@ -221,3 +222,34 @@ fn infer_flattened_type_of_destructured_array_element() {
221222
"infer_flattened_type_of_destructured_array_element",
222223
);
223224
}
225+
226+
#[test]
227+
fn infer_flattened_type_of_destructured_interface_field() {
228+
const CODE: &str = r#"interface Foo {
229+
foo(): string;
230+
}
231+
232+
function bar({ foo }: Foo) {
233+
}"#;
234+
235+
let root = parse_ts(CODE);
236+
let decl = get_interface_declaration(&root);
237+
let mut resolver = GlobalsResolver::default();
238+
let interface_ty = TypeData::from_ts_interface_declaration(&mut resolver, &decl)
239+
.expect("interface must be inferred");
240+
resolver.run_inference();
241+
242+
let function_decl = get_function_declaration(&root);
243+
let mut resolver = HardcodedSymbolResolver::new("Foo", interface_ty, resolver);
244+
let function_decl = TypeData::from_js_function_declaration(&mut resolver, &function_decl);
245+
resolver.run_inference();
246+
247+
let expr_ty = function_decl.inferred(&mut resolver);
248+
249+
assert_type_data_snapshot(
250+
CODE,
251+
expr_ty,
252+
&resolver,
253+
"infer_flattened_type_of_destructured_interface_field",
254+
)
255+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
---
2+
source: crates/biome_js_type_info/tests/utils.rs
3+
expression: content
4+
---
5+
## Input
6+
7+
```ts
8+
interface Foo {
9+
foo(): string;
10+
}
11+
12+
function bar({ foo }: Foo) {}
13+
14+
```
15+
16+
## Result
17+
18+
```
19+
sync Function "bar" {
20+
accepts: {
21+
params: [
22+
required (unnamed): Module(0) TypeId(1) (bindings: foo:Module(0) TypeId(1).foo)
23+
]
24+
type_args: []
25+
}
26+
returns: unknown reference
27+
}
28+
```
29+
30+
## Registered types
31+
32+
```
33+
Module TypeId(0) => interface "Foo" {
34+
extends: []
35+
type_args: []
36+
members: ["foo": Global TypeId(0)]
37+
}
38+
39+
Module TypeId(1) => instanceof Module(0) TypeId(0)
40+
41+
Module TypeId(2) => sync Function "foo" {
42+
accepts: {
43+
params: []
44+
type_args: []
45+
}
46+
returns: string
47+
}
48+
49+
Global TypeId(0) => sync Function "foo" {
50+
accepts: {
51+
params: []
52+
type_args: []
53+
}
54+
returns: string
55+
}
56+
```

crates/biome_js_type_info/tests/utils.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use biome_js_formatter::context::JsFormatOptions;
44
use biome_js_formatter::format_node;
55
use biome_js_parser::{JsParserOptions, parse};
6-
use biome_js_syntax::{AnyJsExpression, JsVariableDeclaration};
6+
use biome_js_syntax::{AnyJsExpression, JsVariableDeclaration, TsInterfaceDeclaration};
77
use biome_js_syntax::{
88
AnyJsModuleItem, AnyJsRoot, AnyJsStatement, JsFileSource, JsFunctionDeclaration,
99
};
@@ -233,6 +233,22 @@ pub fn get_function_declaration(root: &AnyJsRoot) -> JsFunctionDeclaration {
233233
.expect("cannot find function declaration")
234234
}
235235

236+
pub fn get_interface_declaration(root: &AnyJsRoot) -> TsInterfaceDeclaration {
237+
let module = root.as_js_module().unwrap();
238+
module
239+
.items()
240+
.into_iter()
241+
.filter_map(|item| match item {
242+
AnyJsModuleItem::AnyJsStatement(statement) => Some(statement),
243+
_ => None,
244+
})
245+
.find_map(|statement| match statement {
246+
AnyJsStatement::TsInterfaceDeclaration(decl) => Some(decl),
247+
_ => None,
248+
})
249+
.expect("cannot find interface declaration")
250+
}
251+
236252
pub fn get_variable_declaration(root: &AnyJsRoot) -> JsVariableDeclaration {
237253
let module = root.as_js_module().unwrap();
238254
module

0 commit comments

Comments
 (0)