diff --git a/crates/oxc_formatter/src/write/mapped_type.rs b/crates/oxc_formatter/src/write/mapped_type.rs new file mode 100644 index 0000000000000..4afe685004649 --- /dev/null +++ b/crates/oxc_formatter/src/write/mapped_type.rs @@ -0,0 +1,99 @@ +use oxc_ast::ast::{TSMappedType, TSMappedTypeModifierOperator}; + +use crate::{ + FormatResult, + formatter::{Formatter, SourceText, prelude::*, trivia::FormatLeadingComments}, + generated::ast_nodes::AstNode, + write, + write::semicolon::OptionalSemicolon, +}; + +use super::FormatWrite; + +impl<'a> FormatWrite<'a> for AstNode<'a, TSMappedType<'a>> { + fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { + let type_parameter = self.type_parameter(); + let name_type = self.name_type(); + let should_expand = has_line_break_before_property_name(self, f.source_text()); + + let type_annotation_has_leading_comment = + f.comments().has_comment_before(type_parameter.span.start); + + let format_inner = format_with(|f| { + if should_expand { + let comments = + f.context().comments().comments_before_character(self.span.start, b'['); + write!(f, FormatLeadingComments::Comments(comments))?; + } + + if let Some(readonly) = self.readonly() { + let prefix = match readonly { + TSMappedTypeModifierOperator::True => "", + TSMappedTypeModifierOperator::Plus => "+", + TSMappedTypeModifierOperator::Minus => "-", + }; + write!(f, [prefix, "readonly", space()])?; + } + + let format_inner_inner = format_with(|f| { + write!(f, "[")?; + write!(f, type_parameter.name())?; + if let Some(constraint) = &type_parameter.constraint() { + write!(f, [space(), "in", space(), constraint])?; + } + if let Some(default) = &type_parameter.default() { + write!(f, [space(), "=", space(), default])?; + } + if let Some(name_type) = &name_type { + write!(f, [space(), "as", space(), name_type])?; + } + write!(f, "]")?; + if let Some(optional) = self.optional() { + write!( + f, + match optional { + TSMappedTypeModifierOperator::True => "?", + TSMappedTypeModifierOperator::Plus => "+?", + TSMappedTypeModifierOperator::Minus => "-?", + } + )?; + } + Ok(()) + }); + + write!(f, [space(), group(&format_inner_inner)])?; + if let Some(type_annotation) = &self.type_annotation() { + write!(f, [":", space(), type_annotation])?; + } + write!(f, if_group_breaks(&OptionalSemicolon)) + }); + + let should_insert_space_around_brackets = f.options().bracket_spacing.value(); + write!( + f, + [ + "{", + group(&soft_block_indent_with_maybe_space( + &format_inner, + should_insert_space_around_brackets + )) + .should_expand(should_expand), + "}", + ] + ) + } +} + +/// Check if the user introduced a new line inside the node, but only if +/// that new line occurs at or before the property name. For example, +/// this would break: +/// { [ +/// A in B]: T} +/// Because the line break occurs before `A`, the property name. But this +/// would _not_ break: +/// { [A +/// in B]: T} +/// Because the break is _after_ the `A`. +fn has_line_break_before_property_name(node: &TSMappedType, f: SourceText) -> bool { + f.contains_newline_between(node.span.start, node.type_parameter.span.start) +} diff --git a/crates/oxc_formatter/src/write/mod.rs b/crates/oxc_formatter/src/write/mod.rs index 220b7baffc308..4cc021aeccd15 100644 --- a/crates/oxc_formatter/src/write/mod.rs +++ b/crates/oxc_formatter/src/write/mod.rs @@ -15,6 +15,7 @@ mod import_declaration; mod import_expression; mod intersection_type; mod jsx; +mod mapped_type; mod member_expression; mod object_like; mod object_pattern_like; @@ -1828,75 +1829,6 @@ impl<'a> FormatWrite<'a> for AstNode<'a, TSConstructorType<'a>> { } } -impl<'a> FormatWrite<'a> for AstNode<'a, TSMappedType<'a>> { - fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { - let type_parameter = self.type_parameter(); - let name_type = self.name_type(); - - let should_expand = false; // TODO has_line_break_before_property_name(node)?; - - let type_annotation_has_leading_comment = false; - //TODO - // - // mapped_type - // .as_ref() - // .is_some_and(|annotation| comments.has_leading_comments(annotation.syntax())); - - let format_inner = format_with(|f| { - // TODO: - // write!(f, FormatLeadingComments::Comments(comments.dangling_comments(self.span())))?; - - match self.readonly() { - Some(TSMappedTypeModifierOperator::True) => write!(f, ["readonly", space()])?, - Some(TSMappedTypeModifierOperator::Plus) => write!(f, ["+readonly", space()])?, - Some(TSMappedTypeModifierOperator::Minus) => write!(f, ["-readonly", space()])?, - None => {} - } - - let format_inner_inner = format_with(|f| { - write!(f, "[")?; - write!(f, type_parameter.name())?; - if let Some(constraint) = &type_parameter.constraint() { - write!(f, [space(), "in", space(), constraint])?; - } - if let Some(default) = &type_parameter.default() { - write!(f, [space(), "=", space(), default])?; - } - if let Some(name_type) = &name_type { - write!(f, [space(), "as", space(), name_type])?; - } - write!(f, "]")?; - match self.optional() { - Some(TSMappedTypeModifierOperator::True) => write!(f, "?"), - Some(TSMappedTypeModifierOperator::Plus) => write!(f, "+?"), - Some(TSMappedTypeModifierOperator::Minus) => write!(f, "-?"), - None => Ok(()), - } - }); - - write!(f, [space(), group(&format_inner_inner)])?; - if let Some(type_annotation) = &self.type_annotation() { - write!(f, [":", space(), type_annotation])?; - } - write!(f, if_group_breaks(&OptionalSemicolon)) - }); - - let should_insert_space_around_brackets = f.options().bracket_spacing.value(); - write!( - f, - [ - "{", - group(&soft_block_indent_with_maybe_space( - &format_inner, - should_insert_space_around_brackets - )) - .should_expand(should_expand), - "}", - ] - ) - } -} - impl<'a> FormatWrite<'a> for AstNode<'a, TSTypeAssertion<'a>> { fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { let break_after_cast = !matches!( diff --git a/tasks/prettier_conformance/snapshots/prettier.ts.snap.md b/tasks/prettier_conformance/snapshots/prettier.ts.snap.md index db33b945492a7..f70a26cb9f087 100644 --- a/tasks/prettier_conformance/snapshots/prettier.ts.snap.md +++ b/tasks/prettier_conformance/snapshots/prettier.ts.snap.md @@ -1,4 +1,4 @@ -ts compatibility: 487/573 (84.99%) +ts compatibility: 495/573 (86.39%) # Failed @@ -27,7 +27,6 @@ ts compatibility: 487/573 (84.99%) | typescript/comments/16207.ts | 💥 | 71.43% | | typescript/comments/16889.ts | 💥 | 62.61% | | typescript/comments/location.ts | 💥 | 95.00% | -| typescript/comments/mapped_types.ts | 💥 | 58.82% | | typescript/comments/method_types.ts | 💥 | 79.49% | | typescript/comments/type-parameters.ts | 💥 | 65.52% | | typescript/comments/type_literals.ts | 💥 | 68.97% | @@ -38,14 +37,11 @@ ts compatibility: 487/573 (84.99%) | typescript/conditional-types/conditonal-types.ts | 💥✨ | 34.48% | | typescript/conditional-types/infer-type.ts | 💥✨ | 4.76% | | typescript/conditional-types/nested-in-condition.ts | 💥✨ | 15.79% | -| typescript/conditional-types/new-ternary-spec.ts | 💥💥 | 58.18% | +| typescript/conditional-types/new-ternary-spec.ts | 💥✨ | 10.67% | | typescript/conditional-types/parentheses.ts | 💥✨ | 15.22% | | typescript/conformance/types/functions/functionOverloadErrorsSyntax.ts | 💥 | 0.00% | | typescript/conformance/types/namespaceExportDeclaration/exportAsNamespace.d.ts | 💥 | 75.00% | | typescript/custom/computedProperties/string.ts | 💥 | 73.33% | -| typescript/custom/modifiers/minustoken.ts | 💥 | 20.00% | -| typescript/custom/modifiers/question.ts | 💥 | 0.00% | -| typescript/custom/modifiers/readonly.ts | 💥 | 0.00% | | typescript/declare/object-type-in-declare-function.ts | 💥 | 56.25% | | typescript/decorators/comments.ts | 💥 | 60.00% | | typescript/decorators/decorators-comments.ts | 💥 | 65.71% | @@ -63,17 +59,13 @@ ts compatibility: 487/573 (84.99%) | typescript/interface2/comments.ts | 💥 | 78.87% | | typescript/intersection/intersection-parens.ts | 💥💥 | 72.25% | | typescript/intersection/consistent-with-flow/intersection-parens.ts | 💥 | 69.77% | -| typescript/key-remapping-in-mapped-types/key-remapping.ts | 💥 | 55.56% | | typescript/last-argument-expansion/decorated-function.tsx | 💥 | 29.06% | -| typescript/mapped-type/intersection.ts | 💥 | 0.00% | -| typescript/mapped-type/issue-11098.ts | 💥 | 60.00% | -| typescript/mapped-type/break-mode/break-mode.ts | 💥 | 40.00% | | typescript/method/issue-10352-consistency.ts | 💥 | 63.64% | | typescript/module/global.ts | 💥 | 75.00% | | typescript/multiparser-css/issue-6259.ts | 💥 | 57.14% | | typescript/non-null/optional-chain.ts | 💥 | 72.22% | | typescript/object-multiline/multiline.ts | 💥✨ | 23.21% | -| typescript/prettier-ignore/mapped-types.ts | 💥 | 54.72% | +| typescript/prettier-ignore/mapped-types.ts | 💥 | 63.16% | | typescript/prettier-ignore/prettier-ignore-nested-unions.ts | 💥 | 29.17% | | typescript/prettier-ignore/prettier-ignore-parenthesized-type.ts | 💥 | 0.00% | | typescript/satisfies-operators/expression-statement.ts | 💥💥 | 78.38% |