From b7b3073f3fb4dbec8ad20d6a683c6e74e166f3a3 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 2 Dec 2023 12:40:25 +0000 Subject: [PATCH] feat(prettier) port `should_break_after_operator` (#1606) --- crates/oxc_prettier/src/format/assignment.rs | 122 ++++++++++++++++++- crates/oxc_prettier/src/format/binaryish.rs | 22 ++++ tasks/prettier_conformance/prettier.snap.md | 8 +- 3 files changed, 141 insertions(+), 11 deletions(-) diff --git a/crates/oxc_prettier/src/format/assignment.rs b/crates/oxc_prettier/src/format/assignment.rs index 850f5bbec..ad01489fe 100644 --- a/crates/oxc_prettier/src/format/assignment.rs +++ b/crates/oxc_prettier/src/format/assignment.rs @@ -1,8 +1,9 @@ use oxc_ast::{ ast::{ - AccessorProperty, AssignmentExpression, AssignmentTarget, AssignmentTargetPattern, - AssignmentTargetProperty, BindingPatternKind, Expression, ObjectProperty, - PropertyDefinition, PropertyKind, Statement, VariableDeclarator, + AccessorProperty, Argument, AssignmentExpression, AssignmentTarget, + AssignmentTargetPattern, AssignmentTargetProperty, BindingPatternKind, Expression, + ObjectProperty, PropertyDefinition, PropertyKind, Statement, TSTypeParameterInstantiation, + VariableDeclarator, }, AstKind, }; @@ -13,7 +14,7 @@ use crate::{ group, indent, line, ss, Format, Prettier, }; -use super::class::ClassMemberish; +use super::{binaryish::should_inline_logical_expression, class::ClassMemberish}; pub(super) fn print_assignment_expression<'a>( p: &mut Prettier<'a>, @@ -200,6 +201,10 @@ fn choose_layout<'a>( // wrapping object properties with very short keys usually doesn't add much value let has_short_key = is_object_property_with_short_key(p, assignment_like_node, left_doc); + if should_break_after_operator(p, right_expr, has_short_key) { + return Layout::BreakAfterOperator; + } + if !can_break_left_doc && (has_short_key || matches!( @@ -299,3 +304,112 @@ fn is_object_property_with_short_key<'a>( true } + +/// https://github.com/prettier/prettier/blob/eebf0e4b5ec8ac24393c56ced4b4819d4c551f31/src/language-js/print/assignment.js#L182 +fn should_break_after_operator<'a>( + p: &Prettier<'a>, + expr: &Expression<'a>, + has_short_key: bool, +) -> bool { + if matches!(expr, Expression::BinaryExpression(_) | Expression::LogicalExpression(_)) + && !should_inline_logical_expression(expr) + { + return true; + } + + match expr { + Expression::SequenceExpression(_) => return true, + Expression::ConditionalExpression(conditional_expr) => { + return matches!( + conditional_expr.test, + Expression::LogicalExpression(_) | Expression::BinaryExpression(_) + ) && !should_inline_logical_expression(&conditional_expr.test); + } + Expression::ClassExpression(class_expr) => { + if class_expr.decorators.len() > 0 { + return true; + } + } + _ => {} + } + + if has_short_key { + return false; + } + + let mut current_expr = expr; + + while let Expression::UnaryExpression(_) | Expression::TSNonNullExpression(_) = current_expr { + current_expr = match current_expr { + Expression::UnaryExpression(unary) => &unary.argument, + Expression::TSNonNullExpression(non_null_expr) => &non_null_expr.expression, + // SAFETY: the `while` loop above ensures that `current_expr` is either a `UnaryExpression` or a `TSNonNullExpression`. + _ => unreachable!(), + }; + } + + if current_expr.is_string_literal() || is_poorly_breakable_member_or_call_chain(p, expr) { + return true; + }; + + false +} + +fn is_poorly_breakable_member_or_call_chain<'a>(p: &Prettier<'a>, expr: &Expression<'a>) -> bool { + let mut is_chain_expression = false; + let mut is_ident_or_this_expr = false; + let mut call_expressions = vec![]; + + let mut expression = Some(expr); + + while let Some(node) = expression.take() { + expression = match node { + Expression::TSNonNullExpression(non_null_expr) => Some(&non_null_expr.expression), + Expression::CallExpression(call_expr) => { + is_chain_expression = true; + let callee = &call_expr.callee; + call_expressions.push(call_expr); + Some(callee) + } + Expression::MemberExpression(member_expr) => { + is_chain_expression = true; + Some(member_expr.object()) + } + Expression::Identifier(_) | Expression::ThisExpression(_) => { + is_ident_or_this_expr = true; + break; + } + _ => { + break; + } + } + } + + if !is_chain_expression || !is_ident_or_this_expr { + return false; + } + + for call_expression in call_expressions { + let is_poorly_breakable_call = call_expression.arguments.len() == 0 + || (call_expression.arguments.len() == 1 + && is_lone_short_argument(&call_expression.arguments[0])); + + if !is_poorly_breakable_call { + return false; + } + + if let Some(type_parameters) = &call_expression.type_parameters { + return is_complex_type_arguments(type_parameters); + } + } + + true +} + +fn is_lone_short_argument(arg: &Argument) -> bool { + false +} + +fn is_complex_type_arguments(type_parameters: &TSTypeParameterInstantiation) -> bool { + false +} diff --git a/crates/oxc_prettier/src/format/binaryish.rs b/crates/oxc_prettier/src/format/binaryish.rs index 731909b36..b9307409e 100644 --- a/crates/oxc_prettier/src/format/binaryish.rs +++ b/crates/oxc_prettier/src/format/binaryish.rs @@ -39,3 +39,25 @@ pub(super) fn print_binaryish_expression<'a>( Doc::Array(parts) } } + +pub(super) fn should_inline_logical_expression(expr: &Expression) -> bool { + let Expression::LogicalExpression(logical_expr) = expr else { return false }; + + if let Expression::ObjectExpression(obj_expr) = &logical_expr.right { + if obj_expr.properties.len() > 0 { + return true; + } + } + + if let Expression::ArrayExpression(array_expr) = &logical_expr.right { + if array_expr.elements.len() > 0 { + return true; + } + } + + if matches!(logical_expr.right, Expression::JSXElement(_) | Expression::JSXFragment(_)) { + return true; + } + + false +} diff --git a/tasks/prettier_conformance/prettier.snap.md b/tasks/prettier_conformance/prettier.snap.md index 7ebd6df1c..55fd4c7bf 100644 --- a/tasks/prettier_conformance/prettier.snap.md +++ b/tasks/prettier_conformance/prettier.snap.md @@ -1,4 +1,4 @@ -Compatibility: 217/561 (38.68%) +Compatibility: 221/561 (39.39%) # Failed @@ -46,7 +46,6 @@ Compatibility: 217/561 (38.68%) * assignment/discussion-15196.js * assignment/issue-10218.js * assignment/issue-15534.js -* assignment/issue-2482-2.js * assignment/issue-2540.js * assignment/issue-6922.js * assignment/issue-7572.js @@ -78,9 +77,6 @@ Compatibility: 217/561 (38.68%) * binary-expressions/short-right.js * binary-expressions/test.js -### binary_math -* binary_math/parens.js - ### break-calls * break-calls/break.js * break-calls/parent.js @@ -117,7 +113,6 @@ Compatibility: 217/561 (38.68%) ### classes-private-fields * classes-private-fields/private_fields.js -* classes-private-fields/with_comments.js ### comments * comments/arrow.js @@ -358,7 +353,6 @@ Compatibility: 217/561 (38.68%) * objects/escape-sequence-key.js * objects/expand.js * objects/expression.js -* objects/right-break.js ### objects/assignment-expression * objects/assignment-expression/object-value.js