Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 37 additions & 10 deletions crates/biome_js_type_info/src/format_type_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -788,23 +788,50 @@ impl Format<FormatTypeContext> for Interface {

impl Format<FormatTypeContext> for Literal {
fn fmt(&self, f: &mut Formatter<FormatTypeContext>) -> FormatResult<()> {
write!(f, [&format_args![token("value:"), space()]])?;
match self {
Self::BigInt(bigint_text) => write!(f, [text(bigint_text, TextSize::default())]),
Self::BigInt(bigint_text) => write!(
f,
[
token("bigint:"),
space(),
text(bigint_text, TextSize::default())
]
),
Self::Boolean(lit) => write!(
f,
[text(
lit.as_bool().to_string().as_str(),
TextSize::default()
)]
[
token("bool:"),
space(),
text(lit.as_bool().to_string().as_str(), TextSize::default())
]
),
Self::Number(lit) => {
write!(f, [text(lit.as_str(), TextSize::default())])
write!(
f,
[
token("number:"),
space(),
text(lit.as_str(), TextSize::default())
]
)
}
Self::Object(obj) => write!(f, [&obj]),
Self::RegExp(regexp_text) => write!(f, [text(regexp_text, TextSize::default())]),
Self::String(lit) => write!(f, [text(lit.as_str(), TextSize::default())]),
Self::Template(template_text) => write!(f, [text(template_text, TextSize::default())]),
Self::RegExp(regex) => write!(
f,
[token("regex:"), space(), text(regex, TextSize::default())]
),
Self::String(lit) => write!(
f,
[
token("string:"),
space(),
text(lit.as_str(), TextSize::default())
]
),
Self::Template(tmpl) => write!(
f,
[token("string:"), space(), text(tmpl, TextSize::default())]
),
}
}
}
Expand Down
94 changes: 62 additions & 32 deletions crates/biome_js_type_info/src/globals.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Hardcoded global definitions.

// FIXME: Implement inference from type definitions.
// FIXME: Implement inference from type definitions: https://github.com/biomejs/biome/issues/5977

use std::{
borrow::Cow,
Expand Down Expand Up @@ -59,22 +59,25 @@ pub const PROMISE_RACE_ID: TypeId = TypeId::new(22);
pub const PROMISE_REJECT_ID: TypeId = TypeId::new(23);
pub const PROMISE_RESOLVE_ID: TypeId = TypeId::new(24);
pub const PROMISE_TRY_ID: TypeId = TypeId::new(25);
pub const BIGINT_STRING_LITERAL_ID: TypeId = TypeId::new(26);
pub const BOOLEAN_STRING_LITERAL_ID: TypeId = TypeId::new(27);
pub const FUNCTION_STRING_LITERAL_ID: TypeId = TypeId::new(28);
pub const NUMBER_STRING_LITERAL_ID: TypeId = TypeId::new(29);
pub const OBJECT_STRING_LITERAL_ID: TypeId = TypeId::new(30);
pub const STRING_STRING_LITERAL_ID: TypeId = TypeId::new(31);
pub const SYMBOL_STRING_LITERAL_ID: TypeId = TypeId::new(32);
pub const UNDEFINED_STRING_LITERAL_ID: TypeId = TypeId::new(33);
pub const TYPEOF_OPERATOR_RETURN_UNION_ID: TypeId = TypeId::new(34);
pub const T_ID: TypeId = TypeId::new(35);
pub const U_ID: TypeId = TypeId::new(36);
pub const CONDITIONAL_CALLBACK_ID: TypeId = TypeId::new(37);
pub const MAP_CALLBACK_ID: TypeId = TypeId::new(38);
pub const VOID_CALLBACK_ID: TypeId = TypeId::new(39);
pub const FETCH_ID: TypeId = TypeId::new(40);
pub const NUM_PREDEFINED_TYPES: usize = 41; // Must be one more than the highest `TypeId` above.
pub const INSTANCEOF_REGEXP_ID: TypeId = TypeId::new(26);
pub const REGEXP_ID: TypeId = TypeId::new(27);
pub const REGEXP_EXEC_ID: TypeId = TypeId::new(28);
pub const BIGINT_STRING_LITERAL_ID: TypeId = TypeId::new(29);
pub const BOOLEAN_STRING_LITERAL_ID: TypeId = TypeId::new(30);
pub const FUNCTION_STRING_LITERAL_ID: TypeId = TypeId::new(31);
pub const NUMBER_STRING_LITERAL_ID: TypeId = TypeId::new(32);
pub const OBJECT_STRING_LITERAL_ID: TypeId = TypeId::new(33);
pub const STRING_STRING_LITERAL_ID: TypeId = TypeId::new(34);
pub const SYMBOL_STRING_LITERAL_ID: TypeId = TypeId::new(35);
pub const UNDEFINED_STRING_LITERAL_ID: TypeId = TypeId::new(36);
pub const TYPEOF_OPERATOR_RETURN_UNION_ID: TypeId = TypeId::new(37);
pub const T_ID: TypeId = TypeId::new(38);
pub const U_ID: TypeId = TypeId::new(39);
pub const CONDITIONAL_CALLBACK_ID: TypeId = TypeId::new(40);
pub const MAP_CALLBACK_ID: TypeId = TypeId::new(41);
pub const VOID_CALLBACK_ID: TypeId = TypeId::new(42);
pub const FETCH_ID: TypeId = TypeId::new(43);
pub const NUM_PREDEFINED_TYPES: usize = 44; // Must be one more than the highest `TypeId` above.

pub const GLOBAL_UNKNOWN_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, UNKNOWN_ID);
pub const GLOBAL_UNDEFINED_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, UNDEFINED_ID);
Expand All @@ -83,6 +86,9 @@ pub const GLOBAL_CONDITIONAL_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEV
pub const GLOBAL_NUMBER_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, NUMBER_ID);
pub const GLOBAL_STRING_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, STRING_ID);
pub const GLOBAL_ARRAY_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, ARRAY_ID);
pub const GLOBAL_INSTANCEOF_REGEXP_ID: ResolvedTypeId =
ResolvedTypeId::new(GLOBAL_LEVEL, INSTANCEOF_REGEXP_ID);
pub const GLOBAL_REGEXP_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, REGEXP_ID);
pub const GLOBAL_GLOBAL_ID /* :smirk: */: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, GLOBAL_ID);
pub const GLOBAL_INSTANCEOF_PROMISE_ID: ResolvedTypeId =
ResolvedTypeId::new(GLOBAL_LEVEL, INSTANCEOF_PROMISE_ID);
Expand Down Expand Up @@ -140,24 +146,27 @@ pub fn global_type_name(id: TypeId) -> &'static str {
23 => "Promise.reject",
24 => "Promise.resolve",
25 => "Promise.try",
26 => "\"bigint\"",
27 => "\"boolean\"",
28 => "\"function\"",
29 => "\"number\"",
30 => "\"object\"",
31 => "\"string\"",
32 => "\"symbol\"",
33 => "\"undefined\"",
34 => {
26 => "instanceof RegExp",
27 => "RegExp",
28 => "RegExp.exec",
29 => "\"bigint\"",
30 => "\"boolean\"",
31 => "\"function\"",
32 => "\"number\"",
33 => "\"object\"",
34 => "\"string\"",
35 => "\"symbol\"",
36 => "\"undefined\"",
37 => {
"\"bigint\" | \"boolean\" | \"function\" | \"number\" | \"object\" \
| \"string\" | \"symbol\" | \"undefined\""
}
35 => "T",
36 => "U",
37 => "() => conditional",
38 => "<U>(item: T) => U",
39 => "() => void",
40 => "fetch",
38 => "T",
39 => "U",
40 => "() => conditional",
41 => "<U>(item: T) => U",
42 => "() => void",
43 => "fetch",
_ => "inferred type",
}
}
Expand Down Expand Up @@ -215,6 +224,16 @@ impl Default for GlobalsResolver {
})
};

let regexp_method_definition = |id: TypeId| {
TypeData::from(Function {
is_async: false,
type_parameters: Default::default(),
name: Some(Text::new_static(global_type_name(id))),
parameters: Default::default(),
return_type: ReturnType::Type(GLOBAL_INSTANCEOF_REGEXP_ID.into()),
})
};

let string_literal = |value: &'static str| -> TypeData {
TypeData::from(Literal::String(Text::new_static(value).into()))
};
Expand Down Expand Up @@ -311,6 +330,15 @@ impl Default for GlobalsResolver {
promise_method_definition(PROMISE_REJECT_ID),
promise_method_definition(PROMISE_RESOLVE_ID),
promise_method_definition(PROMISE_TRY_ID),
TypeData::instance_of(TypeReference::from(GLOBAL_REGEXP_ID)),
TypeData::Class(Box::new(Class {
name: Some(Text::new_static("RegExp")),
type_parameters: Box::default(),
extends: None,
implements: [].into(),
members: Box::new([method("exec", REGEXP_EXEC_ID)]),
})),
regexp_method_definition(REGEXP_EXEC_ID),
string_literal("bigint"),
string_literal("boolean"),
string_literal("function"),
Expand Down Expand Up @@ -454,6 +482,8 @@ impl TypeResolver for GlobalsResolver {
Some(GLOBAL_ARRAY_ID)
} else if qualifier.is_promise() && !qualifier.has_known_type_parameters() {
Some(GLOBAL_PROMISE_ID)
} else if qualifier.is_regex() && !qualifier.has_known_type_parameters() {
Some(GLOBAL_REGEXP_ID)
} else if !qualifier.type_only
&& let Some(ident) = qualifier.path.identifier()
{
Expand Down
11 changes: 11 additions & 0 deletions crates/biome_js_type_info/src/type_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1402,6 +1402,17 @@ impl TypeReferenceQualifier {
self.path.is_identifier("Promise")
}

/// Checks whether this type qualifier references the `RegExp` type.
///
/// This method simply checks whether the reference is for a literal
/// `RegExp`, without considering whether another symbol named `RegExp` is
/// in scope. It can be used _after_ type resolution has failed to find a
/// `RegExp` symbol in scope, but should not be used _instead of_ such type
/// resolution.
pub fn is_regex(&self) -> bool {
self.path.is_identifier("RegExp")
}

pub fn with_excluded_binding_id(mut self, binding_id: BindingId) -> Self {
self.excluded_binding_id = Some(binding_id);
self
Expand Down
11 changes: 11 additions & 0 deletions crates/biome_js_type_info/tests/local_inference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,17 @@ fn infer_type_of_object_member_expression() {
);
}

#[test]
fn infer_type_of_regex() {
const CODE: &str = r#"/ab+c/"#;

let root = parse_ts(CODE);
let expr = get_expression(&root);
let mut resolver = GlobalsResolver::default();
let ty = TypeData::from_any_js_expression(&mut resolver, ScopeId::GLOBAL, &expr);
assert_type_data_snapshot(CODE, &ty, &resolver, "infer_type_of_regex");
}

#[test]
fn infer_type_of_typeof_expression() {
const CODE: &str = r#"typeof foo"#;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ instanceof Promise
## Registered types

```
Global TypeId(0) => value: value
Global TypeId(0) => string: value

Global TypeId(1) => Call unresolved reference "resolve" (scope ID: 0)(Global TypeId(0))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ instanceof Promise
```
Global TypeId(0) => Promise.resolve

Global TypeId(1) => value: value
Global TypeId(1) => string: value
```
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ typeof foo;
## Registered types

```
Thin TypeId(0) => value: foo
Thin TypeId(0) => string: foo

Global TypeId(0) => value: foo
Global TypeId(0) => string: foo
```
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ new Promise
## Registered types

```
Global TypeId(0) => value: value
Global TypeId(0) => string: value

Global TypeId(1) => Call unresolved reference "resolve" (scope ID: 0)(Global TypeId(0))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ Call Global TypeId(0)(Global TypeId(1))
```
Global TypeId(0) => Promise.resolve

Global TypeId(1) => value: value
Global TypeId(1) => string: value
```
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ const a = 1 === 1;
## Result

```
a => value: true
a => bool: true

```

## Registered types

```
Global TypeId(0) => value: true
Global TypeId(0) => bool: true
```
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ const a = 0 !== 1;
## Result

```
a => value: true
a => bool: true

```

## Registered types

```
Global TypeId(0) => value: true
Global TypeId(0) => bool: true
```
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ const a = 123.45;
## Result

```
a => value: 123.45
a => number: 123.45

```

## Registered types

```
Global TypeId(0) => value: 123.45
Global TypeId(0) => number: 123.45
```
16 changes: 16 additions & 0 deletions crates/biome_js_type_info/tests/snapshots/infer_type_of_regex.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
source: crates/biome_js_type_info/tests/utils.rs
expression: content
---
## Input

```ts
/ab+c/;
```

## Result

```
regex: /ab+c/
```
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Global TypeId(1) => undefined | Global TypeId(0) | number

Global TypeId(2) => undefined | Global TypeId(0) | number

Global TypeId(3) => value: 0
Global TypeId(3) => number: 0

Global TypeId(4) => undefined | Global TypeId(0) | Global TypeId(3)
```
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Module TypeId(0) => Promise

Module TypeId(1) => Module(0) TypeId(7)

Module TypeId(2) => value: true
Module TypeId(2) => bool: true

Module TypeId(3) => Object {
prototype: No prototype
Expand Down Expand Up @@ -114,9 +114,9 @@ BindingId(0) => JsBindingData {
## Registered types

```
Module TypeId(0) => value: true
Module TypeId(0) => bool: true

Module TypeId(1) => value: false
Module TypeId(1) => bool: false

Module TypeId(2) => Module(0) TypeId(0) | Module(0) TypeId(1)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,13 @@ BindingId(3) => JsBindingData {
## Registered types

```
Module TypeId(0) => value: 42
Module TypeId(0) => number: 42

Module TypeId(1) => Module(0) TypeId(0)

Module TypeId(2) => number

Module TypeId(3) => value: Life, The Universe, and Everything
Module TypeId(3) => string: Life, The Universe, and Everything

Module TypeId(4) => Module(0) TypeId(10)

Expand Down
Loading