use crate::parentheses::NeedsParentheses;
use crate::prelude::*;
use crate::utils::member_chain::chain_member::ChainMember;
use biome_formatter::write;
use std::cell::RefCell;
#[derive(Default)]
pub(super) struct MemberChainGroupsBuilder {
groups: Vec<MemberChainGroup>,
current_group: Option<MemberChainGroup>,
}
impl MemberChainGroupsBuilder {
pub fn start_group(&mut self, member: ChainMember) {
debug_assert!(self.current_group.is_none());
let mut group = MemberChainGroup::default();
group.members.push(member);
self.current_group = Some(group);
}
pub fn start_or_continue_group(&mut self, member: ChainMember) {
match &mut self.current_group {
None => self.start_group(member),
Some(group) => group.members.push(member),
}
}
pub fn continue_group(&mut self, member: ChainMember) {
match &mut self.current_group {
None => {
panic!("It is necessary to start a group first using `start_group`.");
}
Some(group) => {
group.members.push(member);
}
}
}
pub fn close_group(&mut self) {
if let Some(group) = self.current_group.take() {
self.groups.push(group);
}
}
pub(super) fn finish(self) -> TailChainGroups {
let mut groups = self.groups;
if let Some(group) = self.current_group {
groups.push(group);
}
TailChainGroups { groups }
}
}
#[derive(Clone, Debug)]
pub(super) struct TailChainGroups {
groups: Vec<MemberChainGroup>,
}
impl TailChainGroups {
pub(crate) fn is_empty(&self) -> bool {
self.groups.is_empty()
}
pub(crate) fn len(&self) -> usize {
self.groups.len()
}
pub(crate) fn first(&self) -> Option<&MemberChainGroup> {
self.groups.first()
}
pub(crate) fn last(&self) -> Option<&MemberChainGroup> {
self.groups.last()
}
pub(super) fn pop_first(&mut self) -> Option<MemberChainGroup> {
match self.groups.len() {
0 => None,
_ => Some(self.groups.remove(0)),
}
}
pub fn has_comments(&self, comments: &JsComments) -> bool {
let mut members = self.groups.iter().flat_map(|item| item.members.iter());
let has_comments = members.any(|item| {
comments.has_trailing_comments(item.syntax())
|| comments.has_leading_comments(item.syntax())
});
let cutoff_has_leading_comments = if !self.groups.is_empty() {
let group = self.groups.get(1);
if let Some(group) = group {
let first_item = group.members.first();
first_item.map_or(false, |first_item| {
comments.has_leading_comments(first_item.syntax())
})
} else {
false
}
} else {
false
};
has_comments || cutoff_has_leading_comments
}
pub(crate) fn is_member_call_chain(&self, comments: &JsComments) -> bool {
self.groups.len() > 1 || self.has_comments(comments)
}
pub(super) fn iter(&self) -> impl Iterator<Item = &MemberChainGroup> + DoubleEndedIterator {
self.groups.iter()
}
pub(super) fn any_except_last_will_break(&self, f: &mut JsFormatter) -> FormatResult<bool> {
for group in &self.groups[..self.groups.len().saturating_sub(1)] {
if group.will_break(f)? {
return Ok(true);
}
}
Ok(false)
}
pub(super) fn members(&self) -> impl Iterator<Item = &ChainMember> + DoubleEndedIterator {
self.groups.iter().flat_map(|group| group.members().iter())
}
}
impl Format<JsFormatContext> for TailChainGroups {
fn fmt(&self, f: &mut Formatter<JsFormatContext>) -> FormatResult<()> {
f.join().entries(self.groups.iter()).finish()
}
}
#[derive(Clone, Default)]
pub(super) struct MemberChainGroup {
members: Vec<ChainMember>,
formatted: RefCell<Option<FormatElement>>,
}
impl MemberChainGroup {
pub(super) fn into_members(self) -> Vec<ChainMember> {
self.members
}
pub(super) fn members(&self) -> &[ChainMember] {
&self.members
}
pub(super) fn extend_members(&mut self, members: impl IntoIterator<Item = ChainMember>) {
self.members.extend(members)
}
pub(super) fn will_break(&self, f: &mut JsFormatter) -> FormatResult<bool> {
let mut cell = self.formatted.borrow_mut();
let result = match cell.as_ref() {
Some(formatted) => formatted.will_break(),
None => {
let interned = f.intern(&FormatMemberChainGroup { group: self })?;
if let Some(interned) = interned {
let breaks = interned.will_break();
*cell = Some(interned);
breaks
} else {
false
}
}
};
Ok(result)
}
pub(super) fn has_comments(&self, comments: &JsComments) -> bool {
self.members.iter().enumerate().any(|(index, member)| {
if index == 0 {
comments.has_trailing_comments(member.syntax())
} else if index < self.members.len() {
comments.has_leading_comments(member.syntax())
|| comments.has_trailing_comments(member.syntax())
} else {
false
}
})
}
}
impl From<Vec<ChainMember>> for MemberChainGroup {
fn from(entries: Vec<ChainMember>) -> Self {
Self {
members: entries,
formatted: RefCell::new(None),
}
}
}
impl std::fmt::Debug for MemberChainGroup {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("MemberChainGroup")
.field(&self.members)
.finish()
}
}
impl Format<JsFormatContext> for MemberChainGroup {
fn fmt(&self, f: &mut Formatter<JsFormatContext>) -> FormatResult<()> {
if let Some(formatted) = self.formatted.borrow().as_ref() {
return f.write_element(formatted.clone());
}
FormatMemberChainGroup { group: self }.fmt(f)
}
}
pub struct FormatMemberChainGroup<'a> {
group: &'a MemberChainGroup,
}
impl Format<JsFormatContext> for FormatMemberChainGroup<'_> {
fn fmt(&self, f: &mut Formatter<JsFormatContext>) -> FormatResult<()> {
let group = self.group;
let last = group.members.last();
let needs_parens = last.map_or(false, |last| match last {
ChainMember::StaticMember { expression, .. } => expression.needs_parentheses(),
ChainMember::ComputedMember { expression, .. } => expression.needs_parentheses(),
_ => false,
});
let format_entries = format_with(|f| f.join().entries(group.members.iter()).finish());
if needs_parens {
write!(f, [text("("), format_entries, text(")")])
} else {
write!(f, [format_entries])
}
}
}