Skip to content

Commit 30a016b

Browse files
authored
feat(biome_js_parser): support ts resolution-mode import (#3462)
1 parent 2a05cd4 commit 30a016b

File tree

19 files changed

+964
-155
lines changed

19 files changed

+964
-155
lines changed

CHANGELOG.md

+12
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,18 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b
194194

195195
Contributed by @fireairforce
196196

197+
- Add support for parsing typescript's `resolution-mode` in Import Types([#2115](https://github.com/biomejs/biome/issues/2115))
198+
199+
```ts
200+
export type Fs = typeof import('fs', { with: { 'resolution-mode': 'import' } });
201+
export type TypeFromRequire =
202+
import("pkg", { with: { "resolution-mode": "require" } }).TypeFromRequire;
203+
export type TypeFromImport =
204+
import("pkg", { with: { "resolution-mode": "import" } }).TypeFromImport;
205+
```
206+
207+
Contributed by @fireairforce
208+
197209
#### Bug Fixes
198210

199211
- The CSS parser now accepts more emoji in identifiers ([#3627](https://github.com/biomejs/biome/issues/3627#issuecomment-2392388022)).

crates/biome_js_factory/src/generated/node_factory.rs

+4-12
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/biome_js_factory/src/generated/syntax_factory.rs

+2-16
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/biome_js_formatter/src/ts/module/import_type.rs

+2-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
use crate::prelude::*;
2-
use crate::utils::{FormatLiteralStringToken, StringLiteralParentKind};
3-
42
use biome_formatter::write;
53
use biome_js_syntax::TsImportType;
64
use biome_js_syntax::TsImportTypeFields;
@@ -13,9 +11,7 @@ impl FormatNodeRule<TsImportType> for FormatTsImportType {
1311
let TsImportTypeFields {
1412
typeof_token,
1513
import_token,
16-
l_paren_token,
17-
argument_token,
18-
r_paren_token,
14+
arguments,
1915
qualifier_clause,
2016
type_arguments,
2117
} = node.as_fields();
@@ -28,12 +24,7 @@ impl FormatNodeRule<TsImportType> for FormatTsImportType {
2824
f,
2925
[
3026
import_token.format(),
31-
l_paren_token.format(),
32-
FormatLiteralStringToken::new(
33-
&argument_token?,
34-
StringLiteralParentKind::Expression
35-
),
36-
r_paren_token.format(),
27+
arguments.format(),
3728
qualifier_clause.format(),
3829
type_arguments.format(),
3930
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export type Fs = typeof import('fs', { with: { 'resolution-mode': 'import' } });
2+
export type TypeFromRequire =
3+
import("pkg", { with: { "resolution-mode": "require" } }).TypeFromRequire;
4+
export type TypeFromImport =
5+
import("pkg", { with: { "resolution-mode": "import" } }).TypeFromImport;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
---
2+
source: crates/biome_formatter_test/src/snapshot_builder.rs
3+
info: ts/type/import_type_with_resolution_mode.ts
4+
---
5+
# Input
6+
7+
```ts
8+
export type Fs = typeof import('fs', { with: { 'resolution-mode': 'import' } });
9+
export type TypeFromRequire =
10+
import("pkg", { with: { "resolution-mode": "require" } }).TypeFromRequire;
11+
export type TypeFromImport =
12+
import("pkg", { with: { "resolution-mode": "import" } }).TypeFromImport;
13+
```
14+
15+
16+
=============================
17+
18+
# Outputs
19+
20+
## Output 1
21+
22+
-----
23+
Indent style: Tab
24+
Indent width: 2
25+
Line ending: LF
26+
Line width: 80
27+
Quote style: Double Quotes
28+
JSX quote style: Double Quotes
29+
Quote properties: As needed
30+
Trailing commas: All
31+
Semicolons: Always
32+
Arrow parentheses: Always
33+
Bracket spacing: true
34+
Bracket same line: false
35+
Attribute Position: Auto
36+
-----
37+
38+
```ts
39+
export type Fs = typeof import("fs", { with: { "resolution-mode": "import" } });
40+
export type TypeFromRequire = import("pkg", {
41+
with: { "resolution-mode": "require" },
42+
}).TypeFromRequire;
43+
export type TypeFromImport = import("pkg", {
44+
with: { "resolution-mode": "import" },
45+
}).TypeFromImport;
46+
```

crates/biome_js_parser/src/syntax/expr.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1450,12 +1450,13 @@ fn parse_primary_expression(p: &mut JsParser, context: ExpressionContext) -> Par
14501450
// test js import_call
14511451
// import("foo")
14521452
// import("foo", { assert: { type: 'json' } })
1453+
// import("foo", { with: { 'resolution-mode': 'import' } })
14531454

14541455
// test_err js import_invalid_args
14551456
// import()
14561457
// import(...["foo"])
14571458
// import("foo", { assert: { type: 'json' } }, "bar")
1458-
1459+
// import("foo", { with: { type: 'json' } }, "bar")
14591460
let args = p.start();
14601461
p.bump(T!['(']);
14611462
let args_list = p.start();
@@ -1834,7 +1835,7 @@ fn parse_array_expr(p: &mut JsParser) -> ParsedSyntax {
18341835
// test_err js spread
18351836
// [...]
18361837
/// A spread element consisting of three dots and an assignment expression such as `...foo`
1837-
fn parse_spread_element(p: &mut JsParser, context: ExpressionContext) -> ParsedSyntax {
1838+
pub(crate) fn parse_spread_element(p: &mut JsParser, context: ExpressionContext) -> ParsedSyntax {
18381839
if !p.at(T![...]) {
18391840
return Absent;
18401841
}

crates/biome_js_parser/src/syntax/typescript/types.rs

+45-4
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ use crate::prelude::*;
55
use crate::state::{EnterType, SignatureFlags};
66
use crate::syntax::expr::{
77
is_at_binary_operator, is_at_expression, is_at_identifier, is_nth_at_identifier,
8-
is_nth_at_identifier_or_keyword, parse_big_int_literal_expression, parse_identifier,
9-
parse_literal_expression, parse_name, parse_number_literal_expression,
10-
parse_reference_identifier, parse_template_elements, ExpressionContext,
8+
is_nth_at_identifier_or_keyword, parse_assignment_expression_or_higher,
9+
parse_big_int_literal_expression, parse_identifier, parse_literal_expression, parse_name,
10+
parse_number_literal_expression, parse_reference_identifier, parse_template_elements,
11+
ExpressionContext,
1112
};
1213
use crate::syntax::function::{
1314
parse_formal_parameter, parse_parameter_list, skip_parameter_start, ParameterContext,
1415
};
16+
use crate::syntax::js_parse_error;
1517
use crate::syntax::js_parse_error::{
1618
decorators_not_allowed, expected_identifier, expected_object_member_name, expected_parameter,
1719
expected_parameters, expected_property_or_signature, modifier_already_seen,
@@ -29,6 +31,7 @@ use crate::syntax::typescript::ts_parse_error::{
2931
ts_in_out_modifier_cannot_appear_on_a_type_parameter,
3032
};
3133
use biome_parser::parse_lists::{ParseNodeList, ParseSeparatedList};
34+
use biome_parser::ParserProgress;
3235
use enumflags2::{bitflags, make_bitflags, BitFlags};
3336
use smallvec::SmallVec;
3437

@@ -1150,6 +1153,11 @@ fn parse_ts_mapped_type_optional_modifier_clause(p: &mut JsParser) -> ParsedSynt
11501153
// type C = typeof import("test").a.b.c.d.e.f;
11511154
// type D = import("test")<string>;
11521155
// type E = import("test").C<string>;
1156+
// type F = typeof import("test", { with: { "resolution-mode": "import" } });
1157+
// type G = import("test", { with: { "resolution-mode": "import" } }).TypeFromImport;
1158+
// type H = import("test", { with: { "resolution-mode": "import" } })<string>;
1159+
// type I = import("test", { with: { "resolution-mode": "require" } }).C<string>;
1160+
// type J = typeof import("test", { with: { "resolution-mode": "require" } }).a.b.c.d.e.f;
11531161
fn parse_ts_import_type(p: &mut JsParser, context: TypeContext) -> ParsedSyntax {
11541162
if !p.at(T![typeof]) && !p.at(T![import]) {
11551163
return Absent;
@@ -1158,9 +1166,42 @@ fn parse_ts_import_type(p: &mut JsParser, context: TypeContext) -> ParsedSyntax
11581166
let m = p.start();
11591167
p.eat(T![typeof]);
11601168
p.expect(T![import]);
1169+
let args = p.start();
11611170
p.expect(T!['(']);
1162-
p.expect(JS_STRING_LITERAL);
1171+
let args_list = p.start();
1172+
1173+
let mut progress = ParserProgress::default();
1174+
let mut error_range_start = p.cur_range().start();
1175+
let mut args_count = 0;
1176+
1177+
while !p.at(EOF) && !p.at(T![')']) {
1178+
progress.assert_progressing(p);
1179+
args_count += 1;
1180+
1181+
if args_count == 3 {
1182+
error_range_start = p.cur_range().start();
1183+
}
1184+
1185+
parse_assignment_expression_or_higher(p, ExpressionContext::default())
1186+
.or_add_diagnostic(p, js_parse_error::expected_expression_assignment);
1187+
1188+
if p.at(T![,]) {
1189+
p.bump_any();
1190+
} else {
1191+
break;
1192+
}
1193+
}
1194+
args_list.complete(p, JS_CALL_ARGUMENT_LIST);
1195+
1196+
if args_count == 0 || args_count > 2 {
1197+
let err = p.err_builder(
1198+
"`typeof import()` requires exactly one or two arguments. ",
1199+
error_range_start..p.cur_range().end(),
1200+
);
1201+
p.error(err);
1202+
}
11631203
p.expect(T![')']);
1204+
args.complete(p, JS_CALL_ARGUMENTS);
11641205

11651206
if p.at(T![.]) {
11661207
let qualifier = p.start();

crates/biome_js_parser/src/tests.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ fn diagnostics_print_correctly() {
408408
#[test]
409409
pub fn quick_test() {
410410
let code = r#"
411-
import defer * as yNamespace from "y";
411+
type A = typeof import("test");
412412
"#;
413413
let root = parse(
414414
code,
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
import()
22
import(...["foo"])
33
import("foo", { assert: { type: 'json' } }, "bar")
4+
import("foo", { with: { type: 'json' } }, "bar")

0 commit comments

Comments
 (0)