git.delta.rocks / jrsonnet / refs/commits / 752087cb9057

difftreelog

source

crates/jrsonnet-formatter/src/children.rs6.4 KiBsourcehistory
1// TODO: Return errors as trivia23use std::{fmt::Debug, mem};45use jrsonnet_rowan_parser::{6	AstNode, AstToken, SyntaxElement, SyntaxNode, TS,7	nodes::{CustomError, Trivia, TriviaKind},8};910pub type ChildTrivia = Vec<Result<Trivia, String>>;1112/// Node should have no non-trivia tokens before element13pub fn trivia_before(node: SyntaxNode, end: Option<&SyntaxElement>) -> ChildTrivia {14	let mut out = Vec::new();15	for item in node.children_with_tokens() {16		if Some(&item) == end {17			break;18		}1920		if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {21			out.push(Ok(trivia));22		} else if CustomError::can_cast(item.kind()) {23			out.push(Err(item.to_string()));24		} else if end.is_none() {25			break;26		} else {27			assert!(28				TS![, ;].contains(item.kind()),29				"silently eaten token: {:?}",30				item.kind()31			);32		}33	}34	out35}36/// Node should have no non-trivia tokens after element37pub fn trivia_after(node: SyntaxNode, start: Option<&SyntaxElement>) -> ChildTrivia {38	if start.is_none() {39		return Vec::new();40	}41	let mut iter = node.children_with_tokens().peekable();42	while iter.peek() != start {43		iter.next();44	}45	iter.next();46	let mut out = Vec::new();47	for item in iter {48		if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {49			out.push(Ok(trivia));50		} else if CustomError::can_cast(item.kind()) {51			out.push(Err(item.to_string()));52		} else {53			assert!(54				TS![, ;].contains(item.kind()),55				"silently eaten token: {:?}",56				item.kind()57			);58		}59	}60	out61}6263pub fn children_between<T: AstNode + Debug>(64	node: SyntaxNode,65	start: Option<&SyntaxElement>,66	end: Option<&SyntaxElement>,67	trailing: Option<ChildTrivia>,68) -> (Vec<Child<T>>, EndingComments) {69	let mut iter = node.children_with_tokens().peekable();70	if start.is_some() {71		while iter.peek() != start {72			iter.next();73		}74		iter.next();75	}76	children(77		iter.take_while(|i| Some(i) != end),78		start.is_none() && end.is_none(),79		trailing,80	)81}8283// Should start, triggers multi-line render84pub fn should_start_with_newline(85	prev_inline: Option<&ChildTrivia>,86	tt: &ChildTrivia,87) -> (bool, bool) {88	let count =89		count_newlines_before(tt) + prev_inline.map(count_newlines_after).unwrap_or_default();9091	(92		// First for previous item end, second for current item93		count >= 2,94		count >= 1,95	)96}9798fn count_newlines_before(tt: &ChildTrivia) -> usize {99	let mut nl_count = 0;100	for t in tt {101		match t {102			Ok(t) => match t.kind() {103				TriviaKind::Whitespace => {104					nl_count += t.text().bytes().filter(|b| *b == b'\n').count();105				}106				_ => break,107			},108			Err(_) => {109				nl_count += 1;110			}111		}112	}113	nl_count114}115fn count_newlines_after(tt: &ChildTrivia) -> usize {116	let mut nl_count = 0;117	for t in tt.iter().rev() {118		match t {119			Ok(t) => match t.kind() {120				TriviaKind::Whitespace => {121					nl_count += t.text().bytes().filter(|b| *b == b'\n').count();122				}123				TriviaKind::SingleLineHashComment | TriviaKind::SingleLineSlashComment => {124					nl_count += 1;125					break;126				}127				_ => {}128			},129			Err(_) => nl_count += 1,130		}131	}132	nl_count133}134135pub fn children<T: AstNode + Debug>(136	items: impl Iterator<Item = SyntaxElement>,137	loose: bool,138	mut trailing: Option<ChildTrivia>,139) -> (Vec<Child<T>>, EndingComments) {140	let mut out = Vec::new();141	let mut current_child = None::<Child<T>>;142	let mut next = ChildTrivia::new();143	// Previous element ended, do not add more inline comments144	let mut started_next = false;145	let mut had_some = false;146147	for item in items {148		if let Some(value) = item.as_node().cloned().and_then(T::cast) {149			let before_trivia = if let Some(trailing) = trailing.take() {150				assert!(next.is_empty());151				trailing152			} else {153				mem::take(&mut next)154			};155			let (should_start_with_newline, triggers_multiline) = should_start_with_newline(156				current_child.as_ref().map(|c| &c.inline_trivia),157				&before_trivia,158			);159			let last_child = current_child.replace(Child {160				// First item should not start with newline161				should_start_with_newline: had_some && should_start_with_newline,162				triggers_multiline,163				before_trivia,164				value,165				inline_trivia: Vec::new(),166			});167			if let Some(last_child) = last_child {168				out.push(last_child);169			}170			had_some = true;171			started_next = false;172		} else if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {173			let is_single_line_comment = trivia.kind() == TriviaKind::SingleLineHashComment174				|| trivia.kind() == TriviaKind::SingleLineSlashComment;175			if trailing.is_some() {176				// Someone have already parsed trivia for us177				continue;178			} else if started_next179				|| current_child.is_none()180				|| trivia.text().contains('\n') && !is_single_line_comment181			{182				next.push(Ok(trivia.clone()));183				started_next = true;184			} else {185				let cur = current_child.as_mut().expect("checked not none");186				cur.inline_trivia.push(Ok(trivia));187				if is_single_line_comment {188					started_next = true;189				}190			}191			had_some = true;192		} else if CustomError::can_cast(item.kind()) {193			next.push(Err(item.to_string()));194		} else if loose {195			if had_some {196				break;197			}198			started_next = true;199		} else {200			assert!(201				TS![, ;].contains(item.kind()),202				"silently eaten token: {:?}",203				item.kind()204			);205		}206	}207208	let ending_comments = EndingComments {209		should_start_with_newline: should_start_with_newline(210			current_child.as_ref().map(|c| &c.inline_trivia),211			&next,212		)213		.0,214		trivia: next,215	};216217	if let Some(current_child) = current_child {218		out.push(current_child);219	}220221	(out, ending_comments)222}223224#[derive(Debug)]225pub struct Child<T> {226	/// If this child has two newlines above in source code, so it needs to have it in the output227	pub should_start_with_newline: bool,228	/// Comment before item, i.e229	///230	/// ```ignore231	/// // Comment232	/// item233	/// ```234	pub before_trivia: ChildTrivia,235	pub value: T,236	/// Comment after line, but located at same line237	///238	/// ```ignore239	/// item1, // Inline comment240	/// // Not inline comment241	/// item2,242	/// ```243	pub inline_trivia: ChildTrivia,244	/// Is this child has whitespace that is considered significant, meaning245	/// user has inserted it to split the value into multiple lines.246	pub triggers_multiline: bool,247}248249pub struct EndingComments {250	/// If this child has two newlines above in source code, so it needs to have it in the output251	pub should_start_with_newline: bool,252	pub trivia: ChildTrivia,253}254impl EndingComments {255	pub fn is_empty(&self) -> bool {256		!self.should_start_with_newline && self.trivia.is_empty()257	}258	pub fn extract_trailing(&mut self) -> ChildTrivia {259		mem::take(&mut self.trivia)260	}261}