mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
fix(formatter): break mapped type brackets (#25297)
Follow up #25296, same cases but for mapped type.
This commit is contained in:
1 parent
4e6f3f1356
commit
ef1d04be86
6 files changed
+235
-26
No files matched your search
@@ -2,7 +2,11 @@ use oxc_ast::ast::{TSMappedType, TSMappedTypeModifierOperator};
|
||||
|
||||
use crate::{
|
||||
ast_nodes::AstNode,
|
||||
formatter::{prelude::*, trivia::FormatLeadingComments},
|
||||
format_args,
|
||||
formatter::{
|
||||
prelude::*,
|
||||
trivia::{DanglingIndentMode, FormatDanglingComments},
|
||||
},
|
||||
print::semicolon::OptionalSemicolon,
|
||||
utils::suppressed::FormatSuppressedNode,
|
||||
write,
|
||||
@@ -32,13 +36,42 @@ impl<'a> FormatWrite<'a> for AstNode<'a, TSMappedType<'a>> {
|
||||
f.source_text().has_line_terminator_after_skipping_comments(self.span.start + 1);
|
||||
|
||||
let format_inner = format_with(|f| {
|
||||
if should_expand {
|
||||
let comments = if f.comments().has_leading_own_line_comment(self.key.span.start) {
|
||||
f.context().comments().comments_before(self.key.span.start)
|
||||
// Comments between `{` and `[` are the mapped type's dangling comments, joined by hard line breaks.
|
||||
// After the last one: a hard line break when it is a line comment or the source breaks after it,
|
||||
// otherwise a group collapsing to a space when the member fits on the line.
|
||||
// Only the last comment goes inside that group: the ones before it always hard-break.
|
||||
let comments = f.context().comments().comments_before_character(self.span.start, b'[');
|
||||
if let Some((last, rest)) = comments.split_last() {
|
||||
if last.is_line() || last.followed_by_newline() {
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
FormatDanglingComments::Comments {
|
||||
comments,
|
||||
indent: DanglingIndentMode::None
|
||||
},
|
||||
hard_line_break()
|
||||
]
|
||||
);
|
||||
} else {
|
||||
f.context().comments().comments_before_character(self.span.start, b'[')
|
||||
};
|
||||
write!(f, FormatLeadingComments::Comments(comments));
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
FormatDanglingComments::Comments {
|
||||
comments: rest,
|
||||
indent: DanglingIndentMode::None
|
||||
},
|
||||
(!rest.is_empty()).then_some(hard_line_break()),
|
||||
group(&format_args!(
|
||||
FormatDanglingComments::Comments {
|
||||
comments: std::slice::from_ref(last),
|
||||
indent: DanglingIndentMode::None
|
||||
},
|
||||
soft_line_break_or_space()
|
||||
))
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(readonly) = self.readonly() {
|
||||
@@ -50,28 +83,25 @@ impl<'a> FormatWrite<'a> for AstNode<'a, TSMappedType<'a>> {
|
||||
write!(f, [prefix, "readonly", space()]);
|
||||
}
|
||||
|
||||
let format_inner_inner = format_with(|f| {
|
||||
write!(f, "[");
|
||||
write!(f, key);
|
||||
write!(f, [space(), "in", space(), constraint]);
|
||||
let format_key_in_constraint = format_with(|f| {
|
||||
write!(f, [key, space(), "in", space(), constraint]);
|
||||
if let Some(name_type) = &name_type {
|
||||
write!(f, [space(), "as", space(), name_type]);
|
||||
}
|
||||
key.format_trailing_comments(f);
|
||||
write!(f, "]");
|
||||
if let Some(optional) = self.optional() {
|
||||
write!(
|
||||
f,
|
||||
match optional {
|
||||
TSMappedTypeModifierOperator::True => "?",
|
||||
TSMappedTypeModifierOperator::Plus => "+?",
|
||||
TSMappedTypeModifierOperator::Minus => "-?",
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
write!(f, [group(&format_inner_inner)]);
|
||||
write!(f, ["[", group(&soft_block_indent(&format_key_in_constraint)), "]"]);
|
||||
if let Some(optional) = self.optional() {
|
||||
write!(
|
||||
f,
|
||||
match optional {
|
||||
TSMappedTypeModifierOperator::True => "?",
|
||||
TSMappedTypeModifierOperator::Plus => "+?",
|
||||
TSMappedTypeModifierOperator::Minus => "-?",
|
||||
}
|
||||
);
|
||||
}
|
||||
if let Some(type_annotation) = &self.type_annotation() {
|
||||
write!(f, [":", space(), type_annotation]);
|
||||
}
|
||||
|
||||
@@ -4,3 +4,26 @@ type _Test = {
|
||||
// BinaryOperatorToText, then this line will error with "Type 'T' cannot
|
||||
// be used to index type 'BinaryOperatorToText'."
|
||||
};
|
||||
|
||||
// issue #25286 (sibling): a leading comment on the key breaks the bracket
|
||||
// group and stays inside the brackets, above the key
|
||||
type _Key = {
|
||||
[
|
||||
// comment on the key
|
||||
K in keyof T
|
||||
]: T[K];
|
||||
};
|
||||
|
||||
// dangling comments hoist before `readonly` (matches Prettier, which treats
|
||||
// everything between `{` and `[` as dangling on the mapped type)
|
||||
type _Readonly = { readonly /* c */ [K in T]: X };
|
||||
|
||||
// adjacent multiline JSDoc comments stay nestled, like Prettier
|
||||
type _Nestled = {
|
||||
/**
|
||||
* a
|
||||
*//**
|
||||
* b
|
||||
*/
|
||||
[K in T]: X;
|
||||
};
|
||||
@@ -9,6 +9,29 @@ type _Test = {
|
||||
// be used to index type 'BinaryOperatorToText'."
|
||||
};
|
||||
|
||||
// issue #25286 (sibling): a leading comment on the key breaks the bracket
|
||||
// group and stays inside the brackets, above the key
|
||||
type _Key = {
|
||||
[
|
||||
// comment on the key
|
||||
K in keyof T
|
||||
]: T[K];
|
||||
};
|
||||
|
||||
// dangling comments hoist before `readonly` (matches Prettier, which treats
|
||||
// everything between `{` and `[` as dangling on the mapped type)
|
||||
type _Readonly = { readonly /* c */ [K in T]: X };
|
||||
|
||||
// adjacent multiline JSDoc comments stay nestled, like Prettier
|
||||
type _Nestled = {
|
||||
/**
|
||||
* a
|
||||
*//**
|
||||
* b
|
||||
*/
|
||||
[K in T]: X;
|
||||
};
|
||||
|
||||
==================== Output ====================
|
||||
------------------
|
||||
{ printWidth: 80 }
|
||||
@@ -20,6 +43,29 @@ type _Test = {
|
||||
// be used to index type 'BinaryOperatorToText'."
|
||||
};
|
||||
|
||||
// issue #25286 (sibling): a leading comment on the key breaks the bracket
|
||||
// group and stays inside the brackets, above the key
|
||||
type _Key = {
|
||||
[
|
||||
// comment on the key
|
||||
K in keyof T
|
||||
]: T[K];
|
||||
};
|
||||
|
||||
// dangling comments hoist before `readonly` (matches Prettier, which treats
|
||||
// everything between `{` and `[` as dangling on the mapped type)
|
||||
type _Readonly = { /* c */ readonly [K in T]: X };
|
||||
|
||||
// adjacent multiline JSDoc comments stay nestled, like Prettier
|
||||
type _Nestled = {
|
||||
/**
|
||||
* a
|
||||
*//**
|
||||
* b
|
||||
*/
|
||||
[K in T]: X;
|
||||
};
|
||||
|
||||
-------------------
|
||||
{ printWidth: 100 }
|
||||
-------------------
|
||||
@@ -30,4 +76,27 @@ type _Test = {
|
||||
// be used to index type 'BinaryOperatorToText'."
|
||||
};
|
||||
|
||||
// issue #25286 (sibling): a leading comment on the key breaks the bracket
|
||||
// group and stays inside the brackets, above the key
|
||||
type _Key = {
|
||||
[
|
||||
// comment on the key
|
||||
K in keyof T
|
||||
]: T[K];
|
||||
};
|
||||
|
||||
// dangling comments hoist before `readonly` (matches Prettier, which treats
|
||||
// everything between `{` and `[` as dangling on the mapped type)
|
||||
type _Readonly = { /* c */ readonly [K in T]: X };
|
||||
|
||||
// adjacent multiline JSDoc comments stay nestled, like Prettier
|
||||
type _Nestled = {
|
||||
/**
|
||||
* a
|
||||
*//**
|
||||
* b
|
||||
*/
|
||||
[K in T]: X;
|
||||
};
|
||||
|
||||
===================== End =====================
|
||||
@@ -0,0 +1,9 @@
|
||||
// issue #25286 (sibling): the mapped type key gets its own bracket group,
|
||||
// so an overlong key breaks after `[` instead of overflowing printWidth
|
||||
type A = {
|
||||
[KeyNameHere in keyof SomeVeryLongGenericTypeNameHere as Uppercase<KeyNameHere & string>]: X;
|
||||
};
|
||||
type B = {
|
||||
+readonly [VeryLongKeyName in keyof SomeExtremelyLongSourceTypeNameGoesHere]-?: SomeValue;
|
||||
};
|
||||
type C = { [K in keyof T]: T[K] };
|
||||
@@ -0,0 +1,80 @@
|
||||
---
|
||||
source: crates/oxc_formatter/tests/fixtures/mod.rs
|
||||
---
|
||||
==================== Input ====================
|
||||
// issue #25286 (sibling): the mapped type key gets its own bracket group,
|
||||
// so an overlong key breaks after `[` instead of overflowing printWidth
|
||||
type A = {
|
||||
[KeyNameHere in keyof SomeVeryLongGenericTypeNameHere as Uppercase<KeyNameHere & string>]: X;
|
||||
};
|
||||
type B = {
|
||||
+readonly [VeryLongKeyName in keyof SomeExtremelyLongSourceTypeNameGoesHere]-?: SomeValue;
|
||||
};
|
||||
type C = { [K in keyof T]: T[K] };
|
||||
|
||||
==================== Output ====================
|
||||
----------------------------------------
|
||||
{ bracketSpacing: true, printWidth: 80 }
|
||||
----------------------------------------
|
||||
// issue #25286 (sibling): the mapped type key gets its own bracket group,
|
||||
// so an overlong key breaks after `[` instead of overflowing printWidth
|
||||
type A = {
|
||||
[
|
||||
KeyNameHere in keyof SomeVeryLongGenericTypeNameHere as Uppercase<
|
||||
KeyNameHere & string
|
||||
>
|
||||
]: X;
|
||||
};
|
||||
type B = {
|
||||
+readonly [
|
||||
VeryLongKeyName in keyof SomeExtremelyLongSourceTypeNameGoesHere
|
||||
]-?: SomeValue;
|
||||
};
|
||||
type C = { [K in keyof T]: T[K] };
|
||||
|
||||
-----------------------------------------
|
||||
{ bracketSpacing: true, printWidth: 100 }
|
||||
-----------------------------------------
|
||||
// issue #25286 (sibling): the mapped type key gets its own bracket group,
|
||||
// so an overlong key breaks after `[` instead of overflowing printWidth
|
||||
type A = {
|
||||
[KeyNameHere in keyof SomeVeryLongGenericTypeNameHere as Uppercase<KeyNameHere & string>]: X;
|
||||
};
|
||||
type B = {
|
||||
+readonly [VeryLongKeyName in keyof SomeExtremelyLongSourceTypeNameGoesHere]-?: SomeValue;
|
||||
};
|
||||
type C = { [K in keyof T]: T[K] };
|
||||
|
||||
-----------------------------------------
|
||||
{ bracketSpacing: false, printWidth: 80 }
|
||||
-----------------------------------------
|
||||
// issue #25286 (sibling): the mapped type key gets its own bracket group,
|
||||
// so an overlong key breaks after `[` instead of overflowing printWidth
|
||||
type A = {
|
||||
[
|
||||
KeyNameHere in keyof SomeVeryLongGenericTypeNameHere as Uppercase<
|
||||
KeyNameHere & string
|
||||
>
|
||||
]: X;
|
||||
};
|
||||
type B = {
|
||||
+readonly [
|
||||
VeryLongKeyName in keyof SomeExtremelyLongSourceTypeNameGoesHere
|
||||
]-?: SomeValue;
|
||||
};
|
||||
type C = {[K in keyof T]: T[K]};
|
||||
|
||||
------------------------------------------
|
||||
{ bracketSpacing: false, printWidth: 100 }
|
||||
------------------------------------------
|
||||
// issue #25286 (sibling): the mapped type key gets its own bracket group,
|
||||
// so an overlong key breaks after `[` instead of overflowing printWidth
|
||||
type A = {
|
||||
[KeyNameHere in keyof SomeVeryLongGenericTypeNameHere as Uppercase<KeyNameHere & string>]: X;
|
||||
};
|
||||
type B = {
|
||||
+readonly [VeryLongKeyName in keyof SomeExtremelyLongSourceTypeNameGoesHere]-?: SomeValue;
|
||||
};
|
||||
type C = {[K in keyof T]: T[K]};
|
||||
|
||||
===================== End =====================
|
||||
@@ -1,4 +1,4 @@
|
||||
ts compatibility: 622/659 (94.39%), 15 files skipped
|
||||
ts compatibility: 624/659 (94.69%), 15 files skipped
|
||||
|
||||
# Failed
|
||||
|
||||
@@ -9,7 +9,6 @@ ts compatibility: 622/659 (94.39%), 15 files skipped
|
||||
| typescript/as/comments/17407.ts | 💥 | 47.06% |
|
||||
| typescript/call/callee-comments.ts | 💥 | 75.00% |
|
||||
| typescript/cast/18406.ts | 💥 | 84.21% |
|
||||
| typescript/comments/mapped-types.ts | 💥 | 85.71% |
|
||||
| typescript/comments/method_types.ts | 💥 | 82.05% |
|
||||
| typescript/comments/type-parameters.ts | 💥 | 81.82% |
|
||||
| typescript/comments/first-argument/first-argument.ts | 💥 | 81.16% |
|
||||
@@ -23,7 +22,6 @@ ts compatibility: 622/659 (94.39%), 15 files skipped
|
||||
| typescript/property-signature/consistent-with-flow/comments.ts | 💥 | 80.00% |
|
||||
| typescript/property-signature/consistent-with-flow/union.ts | 💥 | 80.00% |
|
||||
| typescript/template-literals/member-expression.ts | 💥 | 65.12% |
|
||||
| typescript/type-parameters-arguments/10732.ts | 💥 | 66.67% |
|
||||
| typescript/type-parameters-arguments/18041.ts | 💥 | 91.43% |
|
||||
| typescript/type-parameters-arguments/18604.ts | 💥 | 60.00% |
|
||||
| typescript/type-parameters-arguments/19505.ts | 💥 | 61.54% |
|
||||
|
||||
Reference in new issue
Block a user