Skip to content

Commit

Permalink
feat(parser): parse import attributes in TSImportType (#2436)
Browse files Browse the repository at this point in the history
  • Loading branch information
Dunqing authored Feb 19, 2024
1 parent 5bd2ce6 commit 60db720
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 7 deletions.
28 changes: 28 additions & 0 deletions crates/oxc_ast/src/ast/ts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))]
Expand Down
2 changes: 2 additions & 0 deletions crates/oxc_ast/src/ast_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1652,13 +1652,15 @@ 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 {
span,
is_type_of,
argument,
qualifier,
attributes,
type_parameters,
}))
}
Expand Down
32 changes: 32 additions & 0 deletions crates/oxc_parser/src/ts/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
}
15 changes: 15 additions & 0 deletions crates/oxc_parser/src/ts/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::{
js::list::{ArrayPatternList, ObjectPatternProperties},
lexer::Kind,
list::{NormalList, SeparatedList},
ts::list::TSImportAttributeList,
Context, ParserImpl,
};

Expand Down Expand Up @@ -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 };
Expand All @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions tasks/coverage/codegen_misc.snap
Original file line number Diff line number Diff line change
@@ -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%)
62 changes: 62 additions & 0 deletions tasks/coverage/misc/fail/oxc-2394.ts
Original file line number Diff line number Diff line change
@@ -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);
1 change: 1 addition & 0 deletions tasks/coverage/misc/pass/oxc-2394.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
type A = import("foo", {with: {type: "json"}})
15 changes: 12 additions & 3 deletions tasks/coverage/parser_misc.snap
Original file line number Diff line number Diff line change
@@ -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]
Expand Down Expand Up @@ -90,6 +90,15 @@ Negative Passed: 7/7 (100.00%)
· ─────────
╰────

× Expected `with` but found `string`
╭─[fail/oxc-2394.ts:20:22]
19export 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]
1let.a = 1;
Expand Down
5 changes: 3 additions & 2 deletions tasks/coverage/prettier_misc.snap
Original file line number Diff line number Diff line change
@@ -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"

0 comments on commit 60db720

Please sign in to comment.