feat(parser): parse import attributes in TSImportType (#2436)

close: #2394 

64d2eeea7b/src/compiler/types.ts (L2177-L2185)

The corresponding test cases were skipped, so I manually added some
cases to misc

f5db48237f/tasks/coverage/src/typescript.rs (L118-L121)
This commit is contained in:
Dunqing 2024-02-19 12:26:42 +08:00 committed by GitHub
parent 5bd2ce6ecf
commit 60db720fa6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 157 additions and 7 deletions

View file

@ -778,9 +778,37 @@ pub struct TSImportType<'a> {
pub is_type_of: bool,
pub argument: TSType<'a>,
pub qualifier: Option<TSTypeName<'a>>,
pub attributes: Option<TSImportAttributes<'a>>,
pub type_parameters: Option<Box<'a, TSTypeParameterInstantiation<'a>>>,
}
#[derive(Debug, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type", rename_all = "camelCase"))]
#[cfg_attr(all(feature = "serde", feature = "wasm"), derive(tsify::Tsify))]
pub struct TSImportAttributes<'a> {
#[cfg_attr(feature = "serde", serde(flatten))]
pub span: Span,
pub elements: Vec<'a, TSImportAttribute<'a>>,
}
#[derive(Debug, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type", rename_all = "camelCase"))]
#[cfg_attr(all(feature = "serde", feature = "wasm"), derive(tsify::Tsify))]
pub struct TSImportAttribute<'a> {
#[cfg_attr(feature = "serde", serde(flatten))]
pub span: Span,
pub name: TSImportAttributeName,
pub value: Expression<'a>,
}
#[derive(Debug, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))]
#[cfg_attr(all(feature = "serde", feature = "wasm"), derive(tsify::Tsify))]
pub enum TSImportAttributeName {
Identifier(IdentifierName),
StringLiteral(StringLiteral),
}
#[derive(Debug, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type", rename_all = "camelCase"))]
#[cfg_attr(all(feature = "serde", feature = "wasm"), derive(tsify::Tsify))]

View file

@ -1652,6 +1652,7 @@ impl<'a> AstBuilder<'a> {
is_type_of: bool,
argument: TSType<'a>,
qualifier: Option<TSTypeName<'a>>,
attributes: Option<TSImportAttributes<'a>>,
type_parameters: Option<Box<'a, TSTypeParameterInstantiation<'a>>>,
) -> TSType<'a> {
TSType::TSImportType(self.alloc(TSImportType {
@ -1659,6 +1660,7 @@ impl<'a> AstBuilder<'a> {
is_type_of,
argument,
qualifier,
attributes,
type_parameters,
}))
}

View file

@ -159,3 +159,35 @@ impl<'a> SeparatedList<'a> for TSTypeArgumentList<'a> {
Ok(())
}
}
pub struct TSImportAttributeList<'a> {
pub elements: Vec<'a, TSImportAttribute<'a>>,
}
impl<'a> SeparatedList<'a> for TSImportAttributeList<'a> {
fn new(p: &ParserImpl<'a>) -> Self {
Self { elements: p.ast.new_vec() }
}
fn open(&self) -> Kind {
Kind::LCurly
}
fn close(&self) -> Kind {
Kind::RCurly
}
fn parse_element(&mut self, p: &mut ParserImpl<'a>) -> Result<()> {
let span = p.start_span();
let name = match p.cur_kind() {
Kind::Str => TSImportAttributeName::StringLiteral(p.parse_literal_string()?),
_ => TSImportAttributeName::Identifier(p.parse_identifier_name()?),
};
p.expect(Kind::Colon)?;
let value = p.parse_expression()?;
let element = TSImportAttribute { span: p.end_span(span), name, value };
self.elements.push(element);
Ok(())
}
}

View file

@ -12,6 +12,7 @@ use crate::{
js::list::{ArrayPatternList, ObjectPatternProperties},
lexer::Kind,
list::{NormalList, SeparatedList},
ts::list::TSImportAttributeList,
Context, ParserImpl,
};
@ -763,6 +764,8 @@ impl<'a> ParserImpl<'a> {
self.expect(Kind::Import)?;
self.expect(Kind::LParen)?;
let argument = self.parse_ts_type()?;
let attributes =
if self.eat(Kind::Comma) { Some(self.parse_ts_import_attributes()?) } else { None };
self.expect(Kind::RParen)?;
let qualifier = if self.eat(Kind::Dot) { Some(self.parse_ts_type_name()?) } else { None };
@ -774,10 +777,22 @@ impl<'a> ParserImpl<'a> {
is_type_of,
argument,
qualifier,
attributes,
type_parameters,
))
}
fn parse_ts_import_attributes(&mut self) -> Result<TSImportAttributes<'a>> {
let span = self.start_span();
// { with:
self.expect(Kind::LCurly)?;
self.expect(Kind::With)?;
self.expect(Kind::Colon)?;
let elements = TSImportAttributeList::parse(self)?.elements;
self.expect(Kind::RCurly)?;
Ok(TSImportAttributes { span, elements })
}
fn parse_ts_constructor_type(&mut self) -> Result<TSType<'a>> {
let span = self.start_span();
let r#abstract = self.eat(Kind::Abstract);

View file

@ -1,3 +1,3 @@
codegen_misc Summary:
AST Parsed : 10/10 (100.00%)
Positive Passed: 10/10 (100.00%)
AST Parsed : 11/11 (100.00%)
Positive Passed: 11/11 (100.00%)

View file

@ -0,0 +1,62 @@
// copy from https://github.com/microsoft/TypeScript/blob/main/tests/cases/conformance/node/nodeModulesImportAttributesTypeModeDeclarationEmitErrors.ts#L72
// @filename: /node_modules/pkg/import.d.ts
export interface ImportInterface {}
// @filename: /node_modules/pkg/require.d.ts
export interface RequireInterface {}
// @filename: /index.ts
export type LocalInterface =
& import("pkg", { with: {"resolution-mode": "foobar"} }).RequireInterface
& import("pkg", { with: {"resolution-mode": "import"} }).ImportInterface;
export const a = (null as any as import("pkg", { with: {"resolution-mode": "foobar"} }).RequireInterface);
export const b = (null as any as import("pkg", { with: {"resolution-mode": "import"} }).ImportInterface);
// @filename: /other.ts
// missing with:
export type LocalInterface =
& import("pkg", {"resolution-mode": "require"}).RequireInterface
& import("pkg", {"resolution-mode": "import"}).ImportInterface;
export const a = (null as any as import("pkg", {"resolution-mode": "require"}).RequireInterface);
export const b = (null as any as import("pkg", {"resolution-mode": "import"}).ImportInterface);
// @filename: /other2.ts
// wrong attribute key
export type LocalInterface =
& import("pkg", { with: {"bad": "require"} }).RequireInterface
& import("pkg", { with: {"bad": "import"} }).ImportInterface;
export const a = (null as any as import("pkg", { with: {"bad": "require"} }).RequireInterface);
export const b = (null as any as import("pkg", { with: {"bad": "import"} }).ImportInterface);
// @filename: /other3.ts
// Array instead of object-y thing
export type LocalInterface =
& import("pkg", [ {"resolution-mode": "require"} ]).RequireInterface
& import("pkg", [ {"resolution-mode": "import"} ]).ImportInterface;
export const a = (null as any as import("pkg", [ {"resolution-mode": "require"} ]).RequireInterface);
export const b = (null as any as import("pkg", [ {"resolution-mode": "import"} ]).ImportInterface);
// @filename: /other4.ts
// Indirected attribute objecty-thing - not allowed
type Attribute1 = { with: {"resolution-mode": "require"} };
type Attribute2 = { with: {"resolution-mode": "import"} };
export type LocalInterface =
& import("pkg", Attribute1).RequireInterface
& import("pkg", Attribute2).ImportInterface;
export const a = (null as any as import("pkg", Attribute1).RequireInterface);
export const b = (null as any as import("pkg", Attribute2).ImportInterface);
// @filename: /other5.ts
export type LocalInterface =
& import("pkg", { with: {} }).RequireInterface
& import("pkg", { with: {} }).ImportInterface;
export const a = (null as any as import("pkg", { with: {} }).RequireInterface);
export const b = (null as any as import("pkg", { with: {} }).ImportInterface);

View file

@ -0,0 +1 @@
type A = import("foo", {with: {type: "json"}})

View file

@ -1,7 +1,7 @@
parser_misc Summary:
AST Parsed : 10/10 (100.00%)
Positive Passed: 10/10 (100.00%)
Negative Passed: 7/7 (100.00%)
AST Parsed : 11/11 (100.00%)
Positive Passed: 11/11 (100.00%)
Negative Passed: 8/8 (100.00%)
× Unexpected token
╭─[fail/oxc-169.js:1:1]
@ -90,6 +90,15 @@ Negative Passed: 7/7 (100.00%)
· ─────────
╰────
× Expected `with` but found `string`
╭─[fail/oxc-2394.ts:20:22]
19 │ export type LocalInterface =
20 │ & import("pkg", {"resolution-mode": "require"}).RequireInterface
· ────────┬────────
· ╰── `with` expected
21 │ & import("pkg", {"resolution-mode": "import"}).ImportInterface;
╰────
× The keyword 'let' is reserved
╭─[fail/oxc.js:1:1]
1 │ let.a = 1;

View file

@ -1,7 +1,8 @@
prettier_misc Summary:
AST Parsed : 10/10 (100.00%)
Positive Passed: 6/10 (60.00%)
AST Parsed : 11/11 (100.00%)
Positive Passed: 6/11 (54.55%)
Expect to Parse: "pass/oxc-1740.tsx"
Expect to Parse: "pass/oxc-2087.ts"
Expect to Parse: "pass/oxc-2394.ts"
Expect to Parse: "pass/swc-1627.js"
Expect to Parse: "pass/swc-8243.tsx"