Skip to content
This repository has been archived by the owner on Aug 31, 2023. It is now read-only.

feat: Support extends constraints on infer type #4018

Merged
Merged
36 changes: 28 additions & 8 deletions crates/rome_js_factory/src/generated/node_factory.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion crates/rome_js_factory/src/generated/syntax_factory.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

76 changes: 38 additions & 38 deletions crates/rome_js_formatter/src/generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8065,6 +8065,44 @@ impl IntoFormat<JsFormatContext> for rome_js_syntax::TsTypeParameterName {
)
}
}
impl FormatRule<rome_js_syntax::TsTypeConstraintClause>
for crate::ts::auxiliary::type_constraint_clause::FormatTsTypeConstraintClause
{
type Context = JsFormatContext;
#[inline(always)]
fn fmt(
&self,
node: &rome_js_syntax::TsTypeConstraintClause,
f: &mut JsFormatter,
) -> FormatResult<()> {
FormatNodeRule::<rome_js_syntax::TsTypeConstraintClause>::fmt(self, node, f)
}
}
impl AsFormat<JsFormatContext> for rome_js_syntax::TsTypeConstraintClause {
type Format<'a> = FormatRefWithRule<
'a,
rome_js_syntax::TsTypeConstraintClause,
crate::ts::auxiliary::type_constraint_clause::FormatTsTypeConstraintClause,
>;
fn format(&self) -> Self::Format<'_> {
FormatRefWithRule::new(
self,
crate::ts::auxiliary::type_constraint_clause::FormatTsTypeConstraintClause::default(),
)
}
}
impl IntoFormat<JsFormatContext> for rome_js_syntax::TsTypeConstraintClause {
type Format = FormatOwnedWithRule<
rome_js_syntax::TsTypeConstraintClause,
crate::ts::auxiliary::type_constraint_clause::FormatTsTypeConstraintClause,
>;
fn into_format(self) -> Self::Format {
FormatOwnedWithRule::new(
self,
crate::ts::auxiliary::type_constraint_clause::FormatTsTypeConstraintClause::default(),
)
}
}
impl FormatRule<rome_js_syntax::TsPredicateReturnType>
for crate::ts::types::predicate_return_type::FormatTsPredicateReturnType
{
Expand Down Expand Up @@ -8213,44 +8251,6 @@ impl IntoFormat<JsFormatContext> for rome_js_syntax::TsTypeParameter {
)
}
}
impl FormatRule<rome_js_syntax::TsTypeConstraintClause>
for crate::ts::auxiliary::type_constraint_clause::FormatTsTypeConstraintClause
{
type Context = JsFormatContext;
#[inline(always)]
fn fmt(
&self,
node: &rome_js_syntax::TsTypeConstraintClause,
f: &mut JsFormatter,
) -> FormatResult<()> {
FormatNodeRule::<rome_js_syntax::TsTypeConstraintClause>::fmt(self, node, f)
}
}
impl AsFormat<JsFormatContext> for rome_js_syntax::TsTypeConstraintClause {
type Format<'a> = FormatRefWithRule<
'a,
rome_js_syntax::TsTypeConstraintClause,
crate::ts::auxiliary::type_constraint_clause::FormatTsTypeConstraintClause,
>;
fn format(&self) -> Self::Format<'_> {
FormatRefWithRule::new(
self,
crate::ts::auxiliary::type_constraint_clause::FormatTsTypeConstraintClause::default(),
)
}
}
impl IntoFormat<JsFormatContext> for rome_js_syntax::TsTypeConstraintClause {
type Format = FormatOwnedWithRule<
rome_js_syntax::TsTypeConstraintClause,
crate::ts::auxiliary::type_constraint_clause::FormatTsTypeConstraintClause,
>;
fn into_format(self) -> Self::Format {
FormatOwnedWithRule::new(
self,
crate::ts::auxiliary::type_constraint_clause::FormatTsTypeConstraintClause::default(),
)
}
}
impl FormatRule<rome_js_syntax::TsDefaultTypeClause>
for crate::ts::auxiliary::default_type_clause::FormatTsDefaultTypeClause
{
Expand Down
71 changes: 65 additions & 6 deletions crates/rome_js_formatter/src/parentheses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,13 @@ use crate::utils::{AnyJsBinaryLikeExpression, AnyJsBinaryLikeLeftExpression};

use rome_js_syntax::{
AnyJsAssignment, AnyJsAssignmentPattern, AnyJsExpression, AnyJsFunctionBody,
AnyJsLiteralExpression, AnyTsType, JsArrowFunctionExpression, JsAssignmentExpression,
JsBinaryExpression, JsBinaryOperator, JsComputedMemberAssignment, JsComputedMemberExpression,
JsConditionalExpression, JsLanguage, JsParenthesizedAssignment, JsParenthesizedExpression,
JsPrivateName, JsSequenceExpression, JsStaticMemberAssignment, JsStaticMemberExpression,
JsSyntaxKind, JsSyntaxNode, JsSyntaxToken, TsConditionalType, TsIndexedAccessType,
TsIntersectionTypeElementList, TsParenthesizedType, TsUnionTypeVariantList,
AnyJsLiteralExpression, AnyTsReturnType, AnyTsType, JsArrowFunctionExpression,
JsAssignmentExpression, JsBinaryExpression, JsBinaryOperator, JsComputedMemberAssignment,
JsComputedMemberExpression, JsConditionalExpression, JsLanguage, JsParenthesizedAssignment,
JsParenthesizedExpression, JsPrivateName, JsSequenceExpression, JsStaticMemberAssignment,
JsStaticMemberExpression, JsSyntaxKind, JsSyntaxNode, JsSyntaxToken, TsConditionalType,
TsConstructorType, TsFunctionType, TsIndexedAccessType, TsIntersectionTypeElementList,
TsParenthesizedType, TsUnionTypeVariantList,
};
use rome_rowan::{declare_node_union, match_ast, AstNode, AstSeparatedList, SyntaxResult};

Expand Down Expand Up @@ -725,6 +726,64 @@ pub(crate) fn is_check_type(node: &JsSyntaxNode, parent: &JsSyntaxNode) -> bool
}
}

/// Tests if `node` is the extends type of a [TsConditionalType]
///
/// ```javascript
/// type s = A extends string ? boolean : number // true for `string`, false for `A`, `boolean` and `number`
/// ```
fn is_extends_type(node: &JsSyntaxNode, parent: &JsSyntaxNode) -> bool {
debug_assert_is_parent(node, parent);

match parent.kind() {
JsSyntaxKind::TS_CONDITIONAL_TYPE => {
let conditional = TsConditionalType::unwrap_cast(parent.clone());

conditional
.extends_type()
.map(AstNode::into_syntax)
.as_ref()
== Ok(node)
}
_ => false,
}
}

/// Tests if `node` includes inferred return types with extends constraints
///
/// ```javascript
/// type Type<A> = A extends ((a: string) => infer B extends string) ? B : never; // true
/// ```
pub(crate) fn is_includes_inferred_return_types_with_extends_constraints(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I referred to prettier/prettier#13275

node: &JsSyntaxNode,
parent: &JsSyntaxNode,
) -> bool {
if is_extends_type(node, parent) {
match node.kind() {
JsSyntaxKind::TS_FUNCTION_TYPE => {
let function_type = TsFunctionType::unwrap_cast(node.clone());
if let Ok(AnyTsReturnType::AnyTsType(AnyTsType::TsInferType(infer_type))) =
function_type.return_type()
{
if infer_type.constraint().is_some() {
return true;
}
}
}
JsSyntaxKind::TS_CONSTRUCTOR_TYPE => {
let constructor_type = TsConstructorType::unwrap_cast(node.clone());
if let Ok(AnyTsType::TsInferType(infer_type)) = constructor_type.return_type() {
if infer_type.constraint().is_some() {
return true;
}
}
}
_ => (),
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit

Suggested change
match node.kind() {
JsSyntaxKind::TS_FUNCTION_TYPE => {
let function_type = TsFunctionType::unwrap_cast(node.clone());
if let Ok(AnyTsReturnType::AnyTsType(AnyTsType::TsInferType(infer_type))) =
function_type.return_type()
{
if infer_type.constraint().is_some() {
return true;
}
}
}
JsSyntaxKind::TS_CONSTRUCTOR_TYPE => {
let constructor_type = TsConstructorType::unwrap_cast(node.clone());
if let Ok(AnyTsType::TsInferType(infer_type)) = constructor_type.return_type() {
if infer_type.constraint().is_some() {
return true;
}
}
}
_ => (),
}
let return_type = match node.kind() {
JsSyntaxKind::TS_FUNCTION_TYPE => {
let function_type = TsFunctionType::unwrap_cast(node.clone());
function_type.return_type()
}
JsSyntaxKind::TS_CONSTRUCTOR_TYPE => {
let constructor_type = TsConstructorType::unwrap_cast(node.clone());
constructor_type.return_type()
}
_ => {
return false;
}
};
match return_type {
Ok(AnyTsType::TsInferType(infer_type)) => infer_type.constraint().is_some(),
_ => false
}

}

false
}

/// Returns `true` if node is in a union or intersection type with more than one variant
///
/// ```javascript
Expand Down
2 changes: 2 additions & 0 deletions crates/rome_js_formatter/src/ts/types/function_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::prelude::*;
use crate::js::declarations::function_declaration::should_group_function_parameters;
use crate::parentheses::{
is_check_type, is_in_many_type_union_or_intersection_list,
is_includes_inferred_return_types_with_extends_constraints,
operator_type_or_higher_needs_parens, NeedsParentheses,
};
use rome_formatter::write;
Expand Down Expand Up @@ -78,6 +79,7 @@ pub(super) fn function_like_type_needs_parentheses(
}
_ => {
is_check_type(node, parent)
|| is_includes_inferred_return_types_with_extends_constraints(node, parent)
|| operator_type_or_higher_needs_parens(node, parent)
|| is_in_many_type_union_or_intersection_list(node, parent)
}
Expand Down
12 changes: 10 additions & 2 deletions crates/rome_js_formatter/src/ts/types/infer_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,17 @@ impl FormatNodeRule<TsInferType> for FormatTsInferType {
fn fmt_fields(&self, node: &TsInferType, f: &mut JsFormatter) -> FormatResult<()> {
let TsInferTypeFields {
infer_token,
type_parameter,
name,
constraint,
} = node.as_fields();
write![f, [infer_token.format(), space(), type_parameter.format()]]

write!(f, [infer_token.format(), space(), name.format()])?;

if let Some(constraint) = constraint {
write!(f, [space(), constraint.format()])?;
}

Ok(())
}

fn needs_parentheses(&self, item: &TsInferType) -> bool {
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
type Foo<T> = T extends (...a: any[]) => infer R extends string ? R : never;
type Foo<T> = T extends ((...a: any[]) => infer R extends string) ? R : never;

This file was deleted.

Loading