mirror of
https://github.com/danbulant/oxc
synced 2026-05-23 06:08:47 +00:00
feat(ast,codegen): add TSParenthesizedType and print type parentheses correctly (#3979)
closes #3916
This commit is contained in:
parent
5845057bff
commit
dc6d45e2e6
18 changed files with 233 additions and 119 deletions
|
|
@ -616,14 +616,16 @@ macro_rules! inherit_variants {
|
||||||
TSTypeReference(Box<'a, TSTypeReference<'a>>) = 32,
|
TSTypeReference(Box<'a, TSTypeReference<'a>>) = 32,
|
||||||
/// Inherited from [`TSType`]
|
/// Inherited from [`TSType`]
|
||||||
TSUnionType(Box<'a, TSUnionType<'a>>) = 33,
|
TSUnionType(Box<'a, TSUnionType<'a>>) = 33,
|
||||||
|
/// Inherited from [`TSType`]
|
||||||
|
TSParenthesizedType(Box<'a, TSParenthesizedType<'a>>) = 34,
|
||||||
|
|
||||||
// JSDoc
|
// JSDoc
|
||||||
/// Inherited from [`TSType`]
|
/// Inherited from [`TSType`]
|
||||||
JSDocNullableType(Box<'a, JSDocNullableType<'a>>) = 34,
|
JSDocNullableType(Box<'a, JSDocNullableType<'a>>) = 35,
|
||||||
/// Inherited from [`TSType`]
|
/// Inherited from [`TSType`]
|
||||||
JSDocNonNullableType(Box<'a, JSDocNonNullableType<'a>>) = 35,
|
JSDocNonNullableType(Box<'a, JSDocNonNullableType<'a>>) = 36,
|
||||||
/// Inherited from [`TSType`]
|
/// Inherited from [`TSType`]
|
||||||
JSDocUnknownType(Box<'a, JSDocUnknownType>) = 36,
|
JSDocUnknownType(Box<'a, JSDocUnknownType>) = 37,
|
||||||
|
|
||||||
$($rest)*
|
$($rest)*
|
||||||
}
|
}
|
||||||
|
|
@ -672,6 +674,7 @@ macro_rules! inherit_variants {
|
||||||
TSTypeQuery,
|
TSTypeQuery,
|
||||||
TSTypeReference,
|
TSTypeReference,
|
||||||
TSUnionType,
|
TSUnionType,
|
||||||
|
TSParenthesizedType,
|
||||||
JSDocNullableType,
|
JSDocNullableType,
|
||||||
JSDocNonNullableType,
|
JSDocNonNullableType,
|
||||||
JSDocUnknownType,
|
JSDocUnknownType,
|
||||||
|
|
|
||||||
|
|
@ -172,10 +172,11 @@ pub enum TSType<'a> {
|
||||||
TSTypeQuery(Box<'a, TSTypeQuery<'a>>) = 31,
|
TSTypeQuery(Box<'a, TSTypeQuery<'a>>) = 31,
|
||||||
TSTypeReference(Box<'a, TSTypeReference<'a>>) = 32,
|
TSTypeReference(Box<'a, TSTypeReference<'a>>) = 32,
|
||||||
TSUnionType(Box<'a, TSUnionType<'a>>) = 33,
|
TSUnionType(Box<'a, TSUnionType<'a>>) = 33,
|
||||||
|
TSParenthesizedType(Box<'a, TSParenthesizedType<'a>>) = 34,
|
||||||
// JSDoc
|
// JSDoc
|
||||||
JSDocNullableType(Box<'a, JSDocNullableType<'a>>) = 34,
|
JSDocNullableType(Box<'a, JSDocNullableType<'a>>) = 35,
|
||||||
JSDocNonNullableType(Box<'a, JSDocNonNullableType<'a>>) = 35,
|
JSDocNonNullableType(Box<'a, JSDocNonNullableType<'a>>) = 36,
|
||||||
JSDocUnknownType(Box<'a, JSDocUnknownType>) = 36,
|
JSDocUnknownType(Box<'a, JSDocUnknownType>) = 37,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Macro for matching `TSType`'s variants.
|
/// Macro for matching `TSType`'s variants.
|
||||||
|
|
@ -216,6 +217,7 @@ macro_rules! match_ts_type {
|
||||||
| $ty::TSTypeQuery(_)
|
| $ty::TSTypeQuery(_)
|
||||||
| $ty::TSTypeReference(_)
|
| $ty::TSTypeReference(_)
|
||||||
| $ty::TSUnionType(_)
|
| $ty::TSUnionType(_)
|
||||||
|
| $ty::TSParenthesizedType(_)
|
||||||
| $ty::JSDocNullableType(_)
|
| $ty::JSDocNullableType(_)
|
||||||
| $ty::JSDocNonNullableType(_)
|
| $ty::JSDocNonNullableType(_)
|
||||||
| $ty::JSDocUnknownType(_)
|
| $ty::JSDocUnknownType(_)
|
||||||
|
|
@ -265,6 +267,16 @@ pub struct TSIntersectionType<'a> {
|
||||||
pub types: Vec<'a, TSType<'a>>,
|
pub types: Vec<'a, TSType<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[visited_node]
|
||||||
|
#[derive(Debug, Hash)]
|
||||||
|
#[cfg_attr(feature = "serialize", derive(Serialize, Tsify))]
|
||||||
|
#[cfg_attr(feature = "serialize", serde(tag = "type"))]
|
||||||
|
pub struct TSParenthesizedType<'a> {
|
||||||
|
#[cfg_attr(feature = "serialize", serde(flatten))]
|
||||||
|
pub span: Span,
|
||||||
|
pub type_annotation: TSType<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
/// keyof unique readonly
|
/// keyof unique readonly
|
||||||
///
|
///
|
||||||
/// <https://www.typescriptlang.org/docs/handbook/2/keyof-types.html>
|
/// <https://www.typescriptlang.org/docs/handbook/2/keyof-types.html>
|
||||||
|
|
|
||||||
|
|
@ -1539,6 +1539,11 @@ impl<'a> AstBuilder<'a> {
|
||||||
TSType::TSUnionType(self.alloc(TSUnionType { span, types }))
|
TSType::TSUnionType(self.alloc(TSUnionType { span, types }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn ts_parenthesized_type(self, span: Span, ty: TSType<'a>) -> TSType<'a> {
|
||||||
|
TSType::TSParenthesizedType(self.alloc(TSParenthesizedType { span, type_annotation: ty }))
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn ts_intersection_type(self, span: Span, types: Vec<'a, TSType<'a>>) -> TSType<'a> {
|
pub fn ts_intersection_type(self, span: Span, types: Vec<'a, TSType<'a>>) -> TSType<'a> {
|
||||||
TSType::TSIntersectionType(self.alloc(TSIntersectionType { span, types }))
|
TSType::TSIntersectionType(self.alloc(TSIntersectionType { span, types }))
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,16 @@ impl<'a> Hash for TSTypeParameter<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> TSType<'a> {
|
||||||
|
/// Remove nested parentheses from this type.
|
||||||
|
pub fn without_parenthesized(&self) -> &Self {
|
||||||
|
match self {
|
||||||
|
Self::TSParenthesizedType(expr) => expr.type_annotation.without_parenthesized(),
|
||||||
|
_ => self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TSAccessibility {
|
impl TSAccessibility {
|
||||||
pub fn is_private(&self) -> bool {
|
pub fn is_private(&self) -> bool {
|
||||||
matches!(self, TSAccessibility::Private)
|
matches!(self, TSAccessibility::Private)
|
||||||
|
|
|
||||||
|
|
@ -299,6 +299,7 @@ impl<'a> AstKind<'a> {
|
||||||
Self::TSTypeLiteral(_) => "TSTypeLiteral".into(),
|
Self::TSTypeLiteral(_) => "TSTypeLiteral".into(),
|
||||||
Self::TSTypeReference(_) => "TSTypeReference".into(),
|
Self::TSTypeReference(_) => "TSTypeReference".into(),
|
||||||
Self::TSUnionType(_) => "TSUnionType".into(),
|
Self::TSUnionType(_) => "TSUnionType".into(),
|
||||||
|
Self::TSParenthesizedType(_) => "TSParenthesizedType".into(),
|
||||||
Self::TSVoidKeyword(_) => "TSVoidKeyword".into(),
|
Self::TSVoidKeyword(_) => "TSVoidKeyword".into(),
|
||||||
Self::TSBigIntKeyword(_) => "TSBigIntKeyword".into(),
|
Self::TSBigIntKeyword(_) => "TSBigIntKeyword".into(),
|
||||||
Self::TSBooleanKeyword(_) => "TSBooleanKeyword".into(),
|
Self::TSBooleanKeyword(_) => "TSBooleanKeyword".into(),
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,7 @@ pub enum AstType {
|
||||||
TSLiteralType,
|
TSLiteralType,
|
||||||
TSUnionType,
|
TSUnionType,
|
||||||
TSIntersectionType,
|
TSIntersectionType,
|
||||||
|
TSParenthesizedType,
|
||||||
TSIndexedAccessType,
|
TSIndexedAccessType,
|
||||||
TSNamedTupleMember,
|
TSNamedTupleMember,
|
||||||
TSAnyKeyword,
|
TSAnyKeyword,
|
||||||
|
|
@ -272,6 +273,7 @@ pub enum AstKind<'a> {
|
||||||
TSLiteralType(&'a TSLiteralType<'a>),
|
TSLiteralType(&'a TSLiteralType<'a>),
|
||||||
TSUnionType(&'a TSUnionType<'a>),
|
TSUnionType(&'a TSUnionType<'a>),
|
||||||
TSIntersectionType(&'a TSIntersectionType<'a>),
|
TSIntersectionType(&'a TSIntersectionType<'a>),
|
||||||
|
TSParenthesizedType(&'a TSParenthesizedType<'a>),
|
||||||
TSIndexedAccessType(&'a TSIndexedAccessType<'a>),
|
TSIndexedAccessType(&'a TSIndexedAccessType<'a>),
|
||||||
TSNamedTupleMember(&'a TSNamedTupleMember<'a>),
|
TSNamedTupleMember(&'a TSNamedTupleMember<'a>),
|
||||||
TSAnyKeyword(&'a TSAnyKeyword),
|
TSAnyKeyword(&'a TSAnyKeyword),
|
||||||
|
|
@ -438,6 +440,7 @@ impl<'a> GetSpan for AstKind<'a> {
|
||||||
Self::TSLiteralType(it) => it.span(),
|
Self::TSLiteralType(it) => it.span(),
|
||||||
Self::TSUnionType(it) => it.span(),
|
Self::TSUnionType(it) => it.span(),
|
||||||
Self::TSIntersectionType(it) => it.span(),
|
Self::TSIntersectionType(it) => it.span(),
|
||||||
|
Self::TSParenthesizedType(it) => it.span(),
|
||||||
Self::TSIndexedAccessType(it) => it.span(),
|
Self::TSIndexedAccessType(it) => it.span(),
|
||||||
Self::TSNamedTupleMember(it) => it.span(),
|
Self::TSNamedTupleMember(it) => it.span(),
|
||||||
Self::TSAnyKeyword(it) => it.span(),
|
Self::TSAnyKeyword(it) => it.span(),
|
||||||
|
|
|
||||||
|
|
@ -1384,6 +1384,7 @@ impl<'a> GetSpan for TSType<'a> {
|
||||||
Self::TSTypeQuery(it) => it.span(),
|
Self::TSTypeQuery(it) => it.span(),
|
||||||
Self::TSTypeReference(it) => it.span(),
|
Self::TSTypeReference(it) => it.span(),
|
||||||
Self::TSUnionType(it) => it.span(),
|
Self::TSUnionType(it) => it.span(),
|
||||||
|
Self::TSParenthesizedType(it) => it.span(),
|
||||||
Self::JSDocNullableType(it) => it.span(),
|
Self::JSDocNullableType(it) => it.span(),
|
||||||
Self::JSDocNonNullableType(it) => it.span(),
|
Self::JSDocNonNullableType(it) => it.span(),
|
||||||
Self::JSDocUnknownType(it) => it.span(),
|
Self::JSDocUnknownType(it) => it.span(),
|
||||||
|
|
@ -1412,6 +1413,13 @@ impl<'a> GetSpan for TSIntersectionType<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> GetSpan for TSParenthesizedType<'a> {
|
||||||
|
#[inline]
|
||||||
|
fn span(&self) -> Span {
|
||||||
|
self.span
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> GetSpan for TSTypeOperator<'a> {
|
impl<'a> GetSpan for TSTypeOperator<'a> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn span(&self) -> Span {
|
fn span(&self) -> Span {
|
||||||
|
|
@ -1500,6 +1508,7 @@ impl<'a> GetSpan for TSTupleElement<'a> {
|
||||||
Self::TSTypeQuery(it) => it.span(),
|
Self::TSTypeQuery(it) => it.span(),
|
||||||
Self::TSTypeReference(it) => it.span(),
|
Self::TSTypeReference(it) => it.span(),
|
||||||
Self::TSUnionType(it) => it.span(),
|
Self::TSUnionType(it) => it.span(),
|
||||||
|
Self::TSParenthesizedType(it) => it.span(),
|
||||||
Self::JSDocNullableType(it) => it.span(),
|
Self::JSDocNullableType(it) => it.span(),
|
||||||
Self::JSDocNonNullableType(it) => it.span(),
|
Self::JSDocNonNullableType(it) => it.span(),
|
||||||
Self::JSDocUnknownType(it) => it.span(),
|
Self::JSDocUnknownType(it) => it.span(),
|
||||||
|
|
|
||||||
|
|
@ -2815,6 +2815,7 @@ pub mod walk {
|
||||||
TSType::TSTypeQuery(ty) => visitor.visit_ts_type_query(ty),
|
TSType::TSTypeQuery(ty) => visitor.visit_ts_type_query(ty),
|
||||||
TSType::TSTypeReference(ty) => visitor.visit_ts_type_reference(ty),
|
TSType::TSTypeReference(ty) => visitor.visit_ts_type_reference(ty),
|
||||||
TSType::TSUnionType(ty) => visitor.visit_ts_union_type(ty),
|
TSType::TSUnionType(ty) => visitor.visit_ts_union_type(ty),
|
||||||
|
TSType::TSParenthesizedType(ty) => visitor.visit_ts_type(&ty.type_annotation),
|
||||||
// JSDoc
|
// JSDoc
|
||||||
TSType::JSDocNullableType(_)
|
TSType::JSDocNullableType(_)
|
||||||
| TSType::JSDocNonNullableType(_)
|
| TSType::JSDocNonNullableType(_)
|
||||||
|
|
|
||||||
|
|
@ -2670,6 +2670,7 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for TSType<'a> {
|
||||||
Self::TSArrayType(ty) => ty.gen(p, ctx),
|
Self::TSArrayType(ty) => ty.gen(p, ctx),
|
||||||
Self::TSTupleType(ty) => ty.gen(p, ctx),
|
Self::TSTupleType(ty) => ty.gen(p, ctx),
|
||||||
Self::TSUnionType(ty) => ty.gen(p, ctx),
|
Self::TSUnionType(ty) => ty.gen(p, ctx),
|
||||||
|
Self::TSParenthesizedType(ty) => ty.gen(p, ctx),
|
||||||
Self::TSIntersectionType(ty) => ty.gen(p, ctx),
|
Self::TSIntersectionType(ty) => ty.gen(p, ctx),
|
||||||
Self::TSConditionalType(ty) => ty.gen(p, ctx),
|
Self::TSConditionalType(ty) => ty.gen(p, ctx),
|
||||||
Self::TSInferType(ty) => ty.gen(p, ctx),
|
Self::TSInferType(ty) => ty.gen(p, ctx),
|
||||||
|
|
@ -2708,9 +2709,8 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for TSType<'a> {
|
||||||
|
|
||||||
impl<'a, const MINIFY: bool> Gen<MINIFY> for TSArrayType<'a> {
|
impl<'a, const MINIFY: bool> Gen<MINIFY> for TSArrayType<'a> {
|
||||||
fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) {
|
fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) {
|
||||||
p.print_str(b"(");
|
|
||||||
self.element_type.gen(p, ctx);
|
self.element_type.gen(p, ctx);
|
||||||
p.print_str(b")[]");
|
p.print_str(b"[]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2728,18 +2728,22 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for TSUnionType<'a> {
|
||||||
self.types[0].gen(p, ctx);
|
self.types[0].gen(p, ctx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
p.print_str(b"(");
|
|
||||||
for (index, item) in self.types.iter().enumerate() {
|
for (index, item) in self.types.iter().enumerate() {
|
||||||
if index != 0 {
|
if index != 0 {
|
||||||
p.print_soft_space();
|
p.print_soft_space();
|
||||||
p.print_str(b"|");
|
p.print_str(b"|");
|
||||||
p.print_soft_space();
|
p.print_soft_space();
|
||||||
}
|
}
|
||||||
p.print_str(b"(");
|
|
||||||
item.gen(p, ctx);
|
item.gen(p, ctx);
|
||||||
p.print_str(b")");
|
|
||||||
}
|
}
|
||||||
p.print_str(b")");
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, const MINIFY: bool> Gen<MINIFY> for TSParenthesizedType<'a> {
|
||||||
|
fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) {
|
||||||
|
p.print(b'(');
|
||||||
|
self.type_annotation.gen(p, ctx);
|
||||||
|
p.print(b')');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2749,28 +2753,23 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for TSIntersectionType<'a> {
|
||||||
self.types[0].gen(p, ctx);
|
self.types[0].gen(p, ctx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
p.print_str(b"(");
|
|
||||||
for (index, item) in self.types.iter().enumerate() {
|
for (index, item) in self.types.iter().enumerate() {
|
||||||
if index != 0 {
|
if index != 0 {
|
||||||
p.print_soft_space();
|
p.print_soft_space();
|
||||||
p.print_str(b"&");
|
p.print_str(b"&");
|
||||||
p.print_soft_space();
|
p.print_soft_space();
|
||||||
}
|
}
|
||||||
p.print_str(b"(");
|
|
||||||
item.gen(p, ctx);
|
item.gen(p, ctx);
|
||||||
p.print_str(b")");
|
|
||||||
}
|
}
|
||||||
p.print_str(b")");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, const MINIFY: bool> Gen<MINIFY> for TSConditionalType<'a> {
|
impl<'a, const MINIFY: bool> Gen<MINIFY> for TSConditionalType<'a> {
|
||||||
fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) {
|
fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) {
|
||||||
self.check_type.gen(p, ctx);
|
self.check_type.gen(p, ctx);
|
||||||
p.print_str(b" extends (");
|
p.print_str(b" extends ");
|
||||||
self.extends_type.gen(p, ctx);
|
self.extends_type.gen(p, ctx);
|
||||||
p.print_str(b") ? ");
|
p.print_str(b" ? ");
|
||||||
self.true_type.gen(p, ctx);
|
self.true_type.gen(p, ctx);
|
||||||
p.print_str(b" : ");
|
p.print_str(b" : ");
|
||||||
self.false_type.gen(p, ctx);
|
self.false_type.gen(p, ctx);
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,7 @@ fn typescript() {
|
||||||
|
|
||||||
test_ts(
|
test_ts(
|
||||||
"let x: string[] = ['abc', 'def', 'ghi'];",
|
"let x: string[] = ['abc', 'def', 'ghi'];",
|
||||||
"let x: (string)[] = ['abc', 'def', 'ghi'];\n",
|
"let x: string[] = ['abc', 'def', 'ghi'];\n",
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
test_ts(
|
test_ts(
|
||||||
|
|
@ -160,8 +160,8 @@ fn typescript() {
|
||||||
"let x: [string, number] = ['abc', 123];\n",
|
"let x: [string, number] = ['abc', 123];\n",
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
test_ts("let x: string | number = 'abc';", "let x: ((string) | (number)) = 'abc';\n", false);
|
test_ts("let x: string | number = 'abc';", "let x: string | number = 'abc';\n", false);
|
||||||
test_ts("let x: string & number = 'abc';", "let x: ((string) & (number)) = 'abc';\n", false);
|
test_ts("let x: string & number = 'abc';", "let x: string & number = 'abc';\n", false);
|
||||||
test_ts("let x: typeof String = 'string';", "let x: typeof String = 'string';\n", false);
|
test_ts("let x: typeof String = 'string';", "let x: typeof String = 'string';\n", false);
|
||||||
test_ts("let x: keyof string = 'length';", "let x: keyof string = 'length';\n", false);
|
test_ts("let x: keyof string = 'length';", "let x: keyof string = 'length';\n", false);
|
||||||
test_ts(
|
test_ts(
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ input_file: crates/oxc_isolated_declarations/tests/fixtures/infer-return-type.ts
|
||||||
==================== .D.TS ====================
|
==================== .D.TS ====================
|
||||||
|
|
||||||
declare function foo(): number;
|
declare function foo(): number;
|
||||||
declare function bar(): ((number) | (undefined));
|
declare function bar(): number | undefined;
|
||||||
declare function baz();
|
declare function baz();
|
||||||
declare function qux(): string;
|
declare function qux(): string;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,4 +5,4 @@ input_file: crates/oxc_isolated_declarations/tests/fixtures/readonly.ts
|
||||||
==================== .D.TS ====================
|
==================== .D.TS ====================
|
||||||
|
|
||||||
export declare const EMPTY_OBJ: {readonly [key: string]: any};
|
export declare const EMPTY_OBJ: {readonly [key: string]: any};
|
||||||
export declare const EMPTY_ARR: readonly (never)[];
|
export declare const EMPTY_ARR: readonly never[];
|
||||||
|
|
|
||||||
|
|
@ -182,6 +182,7 @@ fn check_and_report_error_generic(
|
||||||
if matches!(config, ArrayOption::Array) {
|
if matches!(config, ArrayOption::Array) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let type_param = type_param.without_parenthesized();
|
||||||
if matches!(config, ArrayOption::ArraySimple) && is_simple_type(type_param) {
|
if matches!(config, ArrayOption::ArraySimple) && is_simple_type(type_param) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -889,10 +889,11 @@ impl<'a> ParserImpl<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_parenthesized_type(&mut self) -> Result<TSType<'a>> {
|
fn parse_parenthesized_type(&mut self) -> Result<TSType<'a>> {
|
||||||
|
let span = self.start_span();
|
||||||
self.bump_any(); // bump `(`
|
self.bump_any(); // bump `(`
|
||||||
let result = self.parse_ts_type()?;
|
let ty = self.parse_ts_type()?;
|
||||||
self.expect(Kind::RParen)?;
|
self.expect(Kind::RParen)?;
|
||||||
Ok(result)
|
Ok(self.ast.ts_parenthesized_type(self.end_span(span), ty))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_literal_type_node(&mut self, negative: bool) -> Result<TSType<'a>> {
|
fn parse_literal_type_node(&mut self, negative: bool) -> Result<TSType<'a>> {
|
||||||
|
|
|
||||||
|
|
@ -686,6 +686,7 @@ impl<'a> Format<'a> for TSType<'a> {
|
||||||
TSType::TSTypeQuery(v) => v.format(p),
|
TSType::TSTypeQuery(v) => v.format(p),
|
||||||
TSType::TSTypeReference(v) => v.format(p),
|
TSType::TSTypeReference(v) => v.format(p),
|
||||||
TSType::TSUnionType(v) => v.format(p),
|
TSType::TSUnionType(v) => v.format(p),
|
||||||
|
TSType::TSParenthesizedType(v) => v.format(p),
|
||||||
TSType::JSDocNullableType(v) => v.format(p),
|
TSType::JSDocNullableType(v) => v.format(p),
|
||||||
TSType::JSDocNonNullableType(v) => v.format(p),
|
TSType::JSDocNonNullableType(v) => v.format(p),
|
||||||
TSType::JSDocUnknownType(v) => v.format(p),
|
TSType::JSDocUnknownType(v) => v.format(p),
|
||||||
|
|
@ -920,6 +921,12 @@ impl<'a> Format<'a> for TSTypeReference<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> Format<'a> for TSParenthesizedType<'a> {
|
||||||
|
fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> {
|
||||||
|
wrap!(p, self, TSParenthesizedType, { self.type_annotation.format(p) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> Format<'a> for TSUnionType<'a> {
|
impl<'a> Format<'a> for TSUnionType<'a> {
|
||||||
fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> {
|
fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> {
|
||||||
line!()
|
line!()
|
||||||
|
|
|
||||||
|
|
@ -230,99 +230,100 @@ pub(crate) enum AncestorType {
|
||||||
TSConditionalTypeFalseType = 198,
|
TSConditionalTypeFalseType = 198,
|
||||||
TSUnionTypeTypes = 199,
|
TSUnionTypeTypes = 199,
|
||||||
TSIntersectionTypeTypes = 200,
|
TSIntersectionTypeTypes = 200,
|
||||||
TSTypeOperatorTypeAnnotation = 201,
|
TSParenthesizedTypeTypeAnnotation = 201,
|
||||||
TSArrayTypeElementType = 202,
|
TSTypeOperatorTypeAnnotation = 202,
|
||||||
TSIndexedAccessTypeObjectType = 203,
|
TSArrayTypeElementType = 203,
|
||||||
TSIndexedAccessTypeIndexType = 204,
|
TSIndexedAccessTypeObjectType = 204,
|
||||||
TSTupleTypeElementTypes = 205,
|
TSIndexedAccessTypeIndexType = 205,
|
||||||
TSNamedTupleMemberElementType = 206,
|
TSTupleTypeElementTypes = 206,
|
||||||
TSNamedTupleMemberLabel = 207,
|
TSNamedTupleMemberElementType = 207,
|
||||||
TSOptionalTypeTypeAnnotation = 208,
|
TSNamedTupleMemberLabel = 208,
|
||||||
TSRestTypeTypeAnnotation = 209,
|
TSOptionalTypeTypeAnnotation = 209,
|
||||||
TSTypeReferenceTypeName = 210,
|
TSRestTypeTypeAnnotation = 210,
|
||||||
TSTypeReferenceTypeParameters = 211,
|
TSTypeReferenceTypeName = 211,
|
||||||
TSQualifiedNameLeft = 212,
|
TSTypeReferenceTypeParameters = 212,
|
||||||
TSQualifiedNameRight = 213,
|
TSQualifiedNameLeft = 213,
|
||||||
TSTypeParameterInstantiationParams = 214,
|
TSQualifiedNameRight = 214,
|
||||||
TSTypeParameterName = 215,
|
TSTypeParameterInstantiationParams = 215,
|
||||||
TSTypeParameterConstraint = 216,
|
TSTypeParameterName = 216,
|
||||||
TSTypeParameterDefault = 217,
|
TSTypeParameterConstraint = 217,
|
||||||
TSTypeParameterDeclarationParams = 218,
|
TSTypeParameterDefault = 218,
|
||||||
TSTypeAliasDeclarationId = 219,
|
TSTypeParameterDeclarationParams = 219,
|
||||||
TSTypeAliasDeclarationTypeAnnotation = 220,
|
TSTypeAliasDeclarationId = 220,
|
||||||
TSTypeAliasDeclarationTypeParameters = 221,
|
TSTypeAliasDeclarationTypeAnnotation = 221,
|
||||||
TSClassImplementsExpression = 222,
|
TSTypeAliasDeclarationTypeParameters = 222,
|
||||||
TSClassImplementsTypeParameters = 223,
|
TSClassImplementsExpression = 223,
|
||||||
TSInterfaceDeclarationId = 224,
|
TSClassImplementsTypeParameters = 224,
|
||||||
TSInterfaceDeclarationBody = 225,
|
TSInterfaceDeclarationId = 225,
|
||||||
TSInterfaceDeclarationTypeParameters = 226,
|
TSInterfaceDeclarationBody = 226,
|
||||||
TSInterfaceDeclarationExtends = 227,
|
TSInterfaceDeclarationTypeParameters = 227,
|
||||||
TSInterfaceBodyBody = 228,
|
TSInterfaceDeclarationExtends = 228,
|
||||||
TSPropertySignatureKey = 229,
|
TSInterfaceBodyBody = 229,
|
||||||
TSPropertySignatureTypeAnnotation = 230,
|
TSPropertySignatureKey = 230,
|
||||||
TSIndexSignatureParameters = 231,
|
TSPropertySignatureTypeAnnotation = 231,
|
||||||
TSIndexSignatureTypeAnnotation = 232,
|
TSIndexSignatureParameters = 232,
|
||||||
TSCallSignatureDeclarationThisParam = 233,
|
TSIndexSignatureTypeAnnotation = 233,
|
||||||
TSCallSignatureDeclarationParams = 234,
|
TSCallSignatureDeclarationThisParam = 234,
|
||||||
TSCallSignatureDeclarationReturnType = 235,
|
TSCallSignatureDeclarationParams = 235,
|
||||||
TSCallSignatureDeclarationTypeParameters = 236,
|
TSCallSignatureDeclarationReturnType = 236,
|
||||||
TSMethodSignatureKey = 237,
|
TSCallSignatureDeclarationTypeParameters = 237,
|
||||||
TSMethodSignatureThisParam = 238,
|
TSMethodSignatureKey = 238,
|
||||||
TSMethodSignatureParams = 239,
|
TSMethodSignatureThisParam = 239,
|
||||||
TSMethodSignatureReturnType = 240,
|
TSMethodSignatureParams = 240,
|
||||||
TSMethodSignatureTypeParameters = 241,
|
TSMethodSignatureReturnType = 241,
|
||||||
TSConstructSignatureDeclarationParams = 242,
|
TSMethodSignatureTypeParameters = 242,
|
||||||
TSConstructSignatureDeclarationReturnType = 243,
|
TSConstructSignatureDeclarationParams = 243,
|
||||||
TSConstructSignatureDeclarationTypeParameters = 244,
|
TSConstructSignatureDeclarationReturnType = 244,
|
||||||
TSIndexSignatureNameTypeAnnotation = 245,
|
TSConstructSignatureDeclarationTypeParameters = 245,
|
||||||
TSInterfaceHeritageExpression = 246,
|
TSIndexSignatureNameTypeAnnotation = 246,
|
||||||
TSInterfaceHeritageTypeParameters = 247,
|
TSInterfaceHeritageExpression = 247,
|
||||||
TSTypePredicateParameterName = 248,
|
TSInterfaceHeritageTypeParameters = 248,
|
||||||
TSTypePredicateTypeAnnotation = 249,
|
TSTypePredicateParameterName = 249,
|
||||||
TSModuleDeclarationId = 250,
|
TSTypePredicateTypeAnnotation = 250,
|
||||||
TSModuleDeclarationBody = 251,
|
TSModuleDeclarationId = 251,
|
||||||
TSModuleBlockDirectives = 252,
|
TSModuleDeclarationBody = 252,
|
||||||
TSModuleBlockBody = 253,
|
TSModuleBlockDirectives = 253,
|
||||||
TSTypeLiteralMembers = 254,
|
TSModuleBlockBody = 254,
|
||||||
TSInferTypeTypeParameter = 255,
|
TSTypeLiteralMembers = 255,
|
||||||
TSTypeQueryExprName = 256,
|
TSInferTypeTypeParameter = 256,
|
||||||
TSTypeQueryTypeParameters = 257,
|
TSTypeQueryExprName = 257,
|
||||||
TSImportTypeParameter = 258,
|
TSTypeQueryTypeParameters = 258,
|
||||||
TSImportTypeQualifier = 259,
|
TSImportTypeParameter = 259,
|
||||||
TSImportTypeAttributes = 260,
|
TSImportTypeQualifier = 260,
|
||||||
TSImportTypeTypeParameters = 261,
|
TSImportTypeAttributes = 261,
|
||||||
TSImportAttributesElements = 262,
|
TSImportTypeTypeParameters = 262,
|
||||||
TSImportAttributeName = 263,
|
TSImportAttributesElements = 263,
|
||||||
TSImportAttributeValue = 264,
|
TSImportAttributeName = 264,
|
||||||
TSFunctionTypeThisParam = 265,
|
TSImportAttributeValue = 265,
|
||||||
TSFunctionTypeParams = 266,
|
TSFunctionTypeThisParam = 266,
|
||||||
TSFunctionTypeReturnType = 267,
|
TSFunctionTypeParams = 267,
|
||||||
TSFunctionTypeTypeParameters = 268,
|
TSFunctionTypeReturnType = 268,
|
||||||
TSConstructorTypeParams = 269,
|
TSFunctionTypeTypeParameters = 269,
|
||||||
TSConstructorTypeReturnType = 270,
|
TSConstructorTypeParams = 270,
|
||||||
TSConstructorTypeTypeParameters = 271,
|
TSConstructorTypeReturnType = 271,
|
||||||
TSMappedTypeTypeParameter = 272,
|
TSConstructorTypeTypeParameters = 272,
|
||||||
TSMappedTypeNameType = 273,
|
TSMappedTypeTypeParameter = 273,
|
||||||
TSMappedTypeTypeAnnotation = 274,
|
TSMappedTypeNameType = 274,
|
||||||
TSTemplateLiteralTypeQuasis = 275,
|
TSMappedTypeTypeAnnotation = 275,
|
||||||
TSTemplateLiteralTypeTypes = 276,
|
TSTemplateLiteralTypeQuasis = 276,
|
||||||
TSAsExpressionExpression = 277,
|
TSTemplateLiteralTypeTypes = 277,
|
||||||
TSAsExpressionTypeAnnotation = 278,
|
TSAsExpressionExpression = 278,
|
||||||
TSSatisfiesExpressionExpression = 279,
|
TSAsExpressionTypeAnnotation = 279,
|
||||||
TSSatisfiesExpressionTypeAnnotation = 280,
|
TSSatisfiesExpressionExpression = 280,
|
||||||
TSTypeAssertionExpression = 281,
|
TSSatisfiesExpressionTypeAnnotation = 281,
|
||||||
TSTypeAssertionTypeAnnotation = 282,
|
TSTypeAssertionExpression = 282,
|
||||||
TSImportEqualsDeclarationId = 283,
|
TSTypeAssertionTypeAnnotation = 283,
|
||||||
TSImportEqualsDeclarationModuleReference = 284,
|
TSImportEqualsDeclarationId = 284,
|
||||||
TSExternalModuleReferenceExpression = 285,
|
TSImportEqualsDeclarationModuleReference = 285,
|
||||||
TSNonNullExpressionExpression = 286,
|
TSExternalModuleReferenceExpression = 286,
|
||||||
DecoratorExpression = 287,
|
TSNonNullExpressionExpression = 287,
|
||||||
TSExportAssignmentExpression = 288,
|
DecoratorExpression = 288,
|
||||||
TSNamespaceExportDeclarationId = 289,
|
TSExportAssignmentExpression = 289,
|
||||||
TSInstantiationExpressionExpression = 290,
|
TSNamespaceExportDeclarationId = 290,
|
||||||
TSInstantiationExpressionTypeParameters = 291,
|
TSInstantiationExpressionExpression = 291,
|
||||||
JSDocNullableTypeTypeAnnotation = 292,
|
TSInstantiationExpressionTypeParameters = 292,
|
||||||
JSDocNonNullableTypeTypeAnnotation = 293,
|
JSDocNullableTypeTypeAnnotation = 293,
|
||||||
|
JSDocNonNullableTypeTypeAnnotation = 294,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ancestor type used in AST traversal.
|
/// Ancestor type used in AST traversal.
|
||||||
|
|
@ -683,6 +684,8 @@ pub enum Ancestor<'a> {
|
||||||
TSUnionTypeTypes(TSUnionTypeWithoutTypes<'a>) = AncestorType::TSUnionTypeTypes as u16,
|
TSUnionTypeTypes(TSUnionTypeWithoutTypes<'a>) = AncestorType::TSUnionTypeTypes as u16,
|
||||||
TSIntersectionTypeTypes(TSIntersectionTypeWithoutTypes<'a>) =
|
TSIntersectionTypeTypes(TSIntersectionTypeWithoutTypes<'a>) =
|
||||||
AncestorType::TSIntersectionTypeTypes as u16,
|
AncestorType::TSIntersectionTypeTypes as u16,
|
||||||
|
TSParenthesizedTypeTypeAnnotation(TSParenthesizedTypeWithoutTypeAnnotation<'a>) =
|
||||||
|
AncestorType::TSParenthesizedTypeTypeAnnotation as u16,
|
||||||
TSTypeOperatorTypeAnnotation(TSTypeOperatorWithoutTypeAnnotation<'a>) =
|
TSTypeOperatorTypeAnnotation(TSTypeOperatorWithoutTypeAnnotation<'a>) =
|
||||||
AncestorType::TSTypeOperatorTypeAnnotation as u16,
|
AncestorType::TSTypeOperatorTypeAnnotation as u16,
|
||||||
TSArrayTypeElementType(TSArrayTypeWithoutElementType<'a>) =
|
TSArrayTypeElementType(TSArrayTypeWithoutElementType<'a>) =
|
||||||
|
|
@ -1511,6 +1514,11 @@ impl<'a> Ancestor<'a> {
|
||||||
matches!(self, Self::TSIntersectionTypeTypes(_))
|
matches!(self, Self::TSIntersectionTypeTypes(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_ts_parenthesized_type(&self) -> bool {
|
||||||
|
matches!(self, Self::TSParenthesizedTypeTypeAnnotation(_))
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_ts_type_operator(&self) -> bool {
|
pub fn is_ts_type_operator(&self) -> bool {
|
||||||
matches!(self, Self::TSTypeOperatorTypeAnnotation(_))
|
matches!(self, Self::TSTypeOperatorTypeAnnotation(_))
|
||||||
|
|
@ -2088,6 +2096,7 @@ impl<'a> Ancestor<'a> {
|
||||||
| Self::TSConditionalTypeFalseType(_)
|
| Self::TSConditionalTypeFalseType(_)
|
||||||
| Self::TSUnionTypeTypes(_)
|
| Self::TSUnionTypeTypes(_)
|
||||||
| Self::TSIntersectionTypeTypes(_)
|
| Self::TSIntersectionTypeTypes(_)
|
||||||
|
| Self::TSParenthesizedTypeTypeAnnotation(_)
|
||||||
| Self::TSTypeOperatorTypeAnnotation(_)
|
| Self::TSTypeOperatorTypeAnnotation(_)
|
||||||
| Self::TSArrayTypeElementType(_)
|
| Self::TSArrayTypeElementType(_)
|
||||||
| Self::TSIndexedAccessTypeObjectType(_)
|
| Self::TSIndexedAccessTypeObjectType(_)
|
||||||
|
|
@ -8996,6 +9005,21 @@ impl<'a> TSIntersectionTypeWithoutTypes<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) const OFFSET_TS_PARENTHESIZED_TYPE_SPAN: usize = offset_of!(TSParenthesizedType, span);
|
||||||
|
pub(crate) const OFFSET_TS_PARENTHESIZED_TYPE_TYPE_ANNOTATION: usize =
|
||||||
|
offset_of!(TSParenthesizedType, type_annotation);
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TSParenthesizedTypeWithoutTypeAnnotation<'a>(pub(crate) *const TSParenthesizedType<'a>);
|
||||||
|
|
||||||
|
impl<'a> TSParenthesizedTypeWithoutTypeAnnotation<'a> {
|
||||||
|
#[inline]
|
||||||
|
pub fn span(&self) -> &Span {
|
||||||
|
unsafe { &*((self.0 as *const u8).add(OFFSET_TS_PARENTHESIZED_TYPE_SPAN) as *const Span) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) const OFFSET_TS_TYPE_OPERATOR_SPAN: usize = offset_of!(TSTypeOperator, span);
|
pub(crate) const OFFSET_TS_TYPE_OPERATOR_SPAN: usize = offset_of!(TSTypeOperator, span);
|
||||||
pub(crate) const OFFSET_TS_TYPE_OPERATOR_OPERATOR: usize = offset_of!(TSTypeOperator, operator);
|
pub(crate) const OFFSET_TS_TYPE_OPERATOR_OPERATOR: usize = offset_of!(TSTypeOperator, operator);
|
||||||
pub(crate) const OFFSET_TS_TYPE_OPERATOR_TYPE_ANNOTATION: usize =
|
pub(crate) const OFFSET_TS_TYPE_OPERATOR_TYPE_ANNOTATION: usize =
|
||||||
|
|
|
||||||
|
|
@ -1638,6 +1638,21 @@ pub trait Traverse<'a> {
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn enter_ts_parenthesized_type(
|
||||||
|
&mut self,
|
||||||
|
node: &mut TSParenthesizedType<'a>,
|
||||||
|
ctx: &mut TraverseCtx<'a>,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn exit_ts_parenthesized_type(
|
||||||
|
&mut self,
|
||||||
|
node: &mut TSParenthesizedType<'a>,
|
||||||
|
ctx: &mut TraverseCtx<'a>,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn enter_ts_type_operator(&mut self, node: &mut TSTypeOperator<'a>, ctx: &mut TraverseCtx<'a>) {
|
fn enter_ts_type_operator(&mut self, node: &mut TSTypeOperator<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3896,6 +3896,9 @@ pub(crate) unsafe fn walk_ts_type<'a, Tr: Traverse<'a>>(
|
||||||
walk_ts_type_reference(traverser, (&mut **node) as *mut _, ctx)
|
walk_ts_type_reference(traverser, (&mut **node) as *mut _, ctx)
|
||||||
}
|
}
|
||||||
TSType::TSUnionType(node) => walk_ts_union_type(traverser, (&mut **node) as *mut _, ctx),
|
TSType::TSUnionType(node) => walk_ts_union_type(traverser, (&mut **node) as *mut _, ctx),
|
||||||
|
TSType::TSParenthesizedType(node) => {
|
||||||
|
walk_ts_parenthesized_type(traverser, (&mut **node) as *mut _, ctx)
|
||||||
|
}
|
||||||
TSType::JSDocNullableType(node) => {
|
TSType::JSDocNullableType(node) => {
|
||||||
walk_js_doc_nullable_type(traverser, (&mut **node) as *mut _, ctx)
|
walk_js_doc_nullable_type(traverser, (&mut **node) as *mut _, ctx)
|
||||||
}
|
}
|
||||||
|
|
@ -3980,6 +3983,25 @@ pub(crate) unsafe fn walk_ts_intersection_type<'a, Tr: Traverse<'a>>(
|
||||||
traverser.exit_ts_intersection_type(&mut *node, ctx);
|
traverser.exit_ts_intersection_type(&mut *node, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) unsafe fn walk_ts_parenthesized_type<'a, Tr: Traverse<'a>>(
|
||||||
|
traverser: &mut Tr,
|
||||||
|
node: *mut TSParenthesizedType<'a>,
|
||||||
|
ctx: &mut TraverseCtx<'a>,
|
||||||
|
) {
|
||||||
|
traverser.enter_ts_parenthesized_type(&mut *node, ctx);
|
||||||
|
ctx.push_stack(Ancestor::TSParenthesizedTypeTypeAnnotation(
|
||||||
|
ancestor::TSParenthesizedTypeWithoutTypeAnnotation(node),
|
||||||
|
));
|
||||||
|
walk_ts_type(
|
||||||
|
traverser,
|
||||||
|
(node as *mut u8).add(ancestor::OFFSET_TS_PARENTHESIZED_TYPE_TYPE_ANNOTATION)
|
||||||
|
as *mut TSType,
|
||||||
|
ctx,
|
||||||
|
);
|
||||||
|
ctx.pop_stack();
|
||||||
|
traverser.exit_ts_parenthesized_type(&mut *node, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) unsafe fn walk_ts_type_operator<'a, Tr: Traverse<'a>>(
|
pub(crate) unsafe fn walk_ts_type_operator<'a, Tr: Traverse<'a>>(
|
||||||
traverser: &mut Tr,
|
traverser: &mut Tr,
|
||||||
node: *mut TSTypeOperator<'a>,
|
node: *mut TSTypeOperator<'a>,
|
||||||
|
|
@ -4165,6 +4187,7 @@ pub(crate) unsafe fn walk_ts_tuple_element<'a, Tr: Traverse<'a>>(
|
||||||
| TSTupleElement::TSTypeQuery(_)
|
| TSTupleElement::TSTypeQuery(_)
|
||||||
| TSTupleElement::TSTypeReference(_)
|
| TSTupleElement::TSTypeReference(_)
|
||||||
| TSTupleElement::TSUnionType(_)
|
| TSTupleElement::TSUnionType(_)
|
||||||
|
| TSTupleElement::TSParenthesizedType(_)
|
||||||
| TSTupleElement::JSDocNullableType(_)
|
| TSTupleElement::JSDocNullableType(_)
|
||||||
| TSTupleElement::JSDocNonNullableType(_)
|
| TSTupleElement::JSDocNonNullableType(_)
|
||||||
| TSTupleElement::JSDocUnknownType(_) => walk_ts_type(traverser, node as *mut _, ctx),
|
| TSTupleElement::JSDocUnknownType(_) => walk_ts_type(traverser, node as *mut _, ctx),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue