difftreelog
feat(fmt) reformat text block
in: master
6 files changed
crates/jrsonnet-formatter/src/lib.rsdiffbeforeafterboth9};9};10use hi_doc::{Formatting, SnippetBuilder};10use hi_doc::{Formatting, SnippetBuilder};11use jrsonnet_rowan_parser::{11use jrsonnet_rowan_parser::{12 collect_lexed_str_block,12 nodes::{13 nodes::{13 Arg, ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,14 Arg, ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,14 DestructRest, Expr, ExprBase, FieldName, ForSpec, IfSpec, ImportKind, Literal, Member,15 DestructRest, Expr, ExprBase, FieldName, ForSpec, IfSpec, ImportKind, Literal, Member,83 $o.push_signal(dprint_core::formatting::Signal::FinishIndent);84 $o.push_signal(dprint_core::formatting::Signal::FinishIndent);84 pi!(@s; $o: $($t)*);85 pi!(@s; $o: $($t)*);85 }};86 }};87 (@s; $o:ident: >ii $($t:tt)*) => {{88 $o.push_signal(dprint_core::formatting::Signal::StartIgnoringIndent);89 pi!(@s; $o: $($t)*);90 }};91 (@s; $o:ident: <ii $($t:tt)*) => {{92 $o.push_signal(dprint_core::formatting::Signal::FinishIgnoringIndent);93 pi!(@s; $o: $($t)*);94 }};86 (@s; $o:ident: info($v:expr) $($t:tt)*) => {{95 (@s; $o:ident: info($v:expr) $($t:tt)*) => {{87 $o.push_info($v);96 $o.push_info($v);88 pi!(@s; $o: $($t)*);97 pi!(@s; $o: $($t)*);201 fn print(&self, out: &mut PrintItems) {210 fn print(&self, out: &mut PrintItems) {202 if matches!(self.kind(), TextKind::StringBlock) {211 if matches!(self.kind(), TextKind::StringBlock) {203 let text = self.text();212 let text = self.text();204213 let mut text = collect_lexed_str_block(&text[3..])214 .expect("formatting is not performed on code with parsing errors");215216 if text.truncate && text.lines.ends_with(&[""]) {217 text.truncate = false;218 text.lines.pop();219 }220221 p!(out, str("|||"));222 if text.truncate {223 p!(out, str("-"));224 }225 p!(out, nl > i);205 for (i, ele) in text.split("\n").enumerate() {226 for ele in text.lines {206 if i != 0 {227 if ele.is_empty() {207 p!(out, nl);228 p!(out, >ii nl <ii);208 }229 } else {209 // TODO: Trim and recreate whitespace210 p!(out, string(ele.to_string()));230 p!(out, string(ele.to_string()) nl);231 }211 }232 }233 p!(out, <i str("|||"));234212 return;235 return;213 }236 }crates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__snapshots@string_styles.jsonnet.snapdiffbeforeafterboth8 single_quote: 'hello world',8 single_quote: 'hello world',9 escaped: 'line1\nline2',9 escaped: 'line1\nline2',10 multiline: |||10 multiline: |||11 This is a11 This is a1213 multiline string14 |||,15 multiline_truncated: |||-16 This is a1712 multiline string18 multiline string with truncated newline13 |||,19 |||,20 multiline_to_truncated: |||21 This is a2223 multiline string with to-be truncated newline24 |||,14}25}1526crates/jrsonnet-formatter/src/tests.rsdiffbeforeafterboth3use std::fs;3use std::fs;445use dprint_core::formatting::{PrintItems, PrintOptions};5use dprint_core::formatting::{PrintItems, PrintOptions};6use indoc::indoc;7use insta::{assert_snapshot, glob};6use insta::{assert_snapshot, glob};879use crate::Printable;8use crate::Printable;crates/jrsonnet-formatter/src/tests/string_styles.jsonnetdiffbeforeafterboth4 escaped: 'line1\nline2',4 escaped: 'line1\nline2',5 multiline: |||5 multiline: |||6 This is a6 This is a77 multiline string8 multiline string8 |||,9 |||,10 multiline_truncated: |||-11 This is a1213 multiline string with truncated newline14 |||,15 multiline_to_truncated: |||-16 This is a1718 multiline string with to-be truncated newline1920 |||,9}21}1022crates/jrsonnet-rowan-parser/src/lib.rsdiffbeforeafterboth22pub use generated::{nodes, syntax_kinds::SyntaxKind};22pub use generated::{nodes, syntax_kinds::SyntaxKind};23pub use language::*;23pub use language::*;24pub use token_set::SyntaxKindSet;24pub use token_set::SyntaxKindSet;25pub use string_block::{collect_lexed_str_block, CollectStrBlock};252626use self::{27use self::{27 ast::support,28 ast::support,crates/jrsonnet-rowan-parser/src/string_block.rsdiffbeforeafterboth6 MissingIndent,6 MissingIndent,7}7}89use std::ops::Range;10811use logos::Lexer;9use logos::Lexer;12use StringBlockError::*;10use StringBlockError::*;131114use crate::SyntaxKind;12use crate::SyntaxKind;151316pub fn lex_str_block_test(lex: &mut Lexer<SyntaxKind>) {14pub(crate) fn lex_str_block_test<'d>(lex: &mut Lexer<'d, SyntaxKind>) {17 let _ = lex_str_block(lex);15 let _ = lex_str_block(lex);18}16}1718pub(crate) struct Context<'a> {19 source: &'a str,20 index: usize,21}2223impl<'a> Context<'a> {24 fn rest(&self) -> &'a str {25 &self.source[self.index..]26 }2728 fn next(&mut self) -> Option<char> {29 if self.index == self.source.len() {30 return None;31 }3233 match self.rest().chars().next() {34 None => None,35 Some(c) => {36 self.index += c.len_utf8();37 Some(c)38 }39 }40 }4142 fn peek(&self) -> Option<char> {43 if self.index == self.source.len() {44 return None;45 }4647 self.rest().chars().next()48 }4950 fn eat_if(&mut self, f: impl Fn(char) -> bool) -> usize {51 if self.peek().map(f).unwrap_or(false) {52 self.index += 1;53 return 1;54 }55 056 }5758 fn eat_while(&mut self, f: impl Fn(char) -> bool) -> usize {59 if self.index == self.source.len() {60 return 0;61 }6263 let next_char = self.rest().char_indices().find(|(_, c)| !f(*c));6465 match next_char {66 None => {67 let diff = self.source.len() - self.index;68 self.index = self.source.len();69 diff70 }71 Some((idx, _)) => {72 self.index += idx;73 idx74 }75 }76 }7778 fn skip(&mut self, len: usize) {79 self.index = match self.index + len {80 n if n > self.source.len() => self.source.len(),81 n => n,82 };83 }84}8586// Check that b has at least the same whitespace prefix as a and returns the87// amount of this whitespace, otherwise returns 0. If a has no whitespace88// prefix than return 0.89fn check_whitespace(a: &str, b: &str) -> usize {90 let a = a.as_bytes();91 let b = b.as_bytes();9293 for i in 0..a.len() {94 if a[i] != b' ' && a[i] != b'\t' {95 // a has run out of whitespace and b matched up to this point. Return result.96 return i;97 }9899 if i >= b.len() {100 // We ran off the edge of b while a still has whitespace. Return 0 as failure.101 return 0;102 }103104 if a[i] != b[i] {105 // a has whitespace but b does not. Return 0 as failure.106 return 0;107 }108 }109110 // We ran off the end of a and b kept up111 a.len()112}113114pub(crate) trait StrBlockLexCtx<'d> {115 fn remainder(&self) -> &'d str;116 fn eat_error(&mut self, ctx: &Context<'d>);117 fn bump_pos(&mut self, s: usize);118 fn mark_truncating(&mut self);119 fn mark_line(&mut self, line: &'d str);120}121122impl<'d> StrBlockLexCtx<'d> for Lexer<'d, SyntaxKind> {123 fn remainder(&self) -> &'d str {124 self.remainder()125 }126 fn eat_error(&mut self, ctx: &Context<'d>) {127 let end_index = ctx128 .rest()129 .find("|||")130 .map_or_else(|| ctx.rest().len(), |v| v + 3);131 self.bump(ctx.index + end_index);132 }133 fn bump_pos(&mut self, s: usize) {134 self.bump(s);135 }136 fn mark_truncating(&mut self) {137 // Lexer test doesn't collect anything138 }139 fn mark_line(&mut self, _line: &'d str) {140 // Lexer test doesn't collect anything141 }142}143144pub fn collect_lexed_str_block<'s>(145 input: &'s str,146) -> Result<CollectStrBlock<'s>, StringBlockError> {147 let mut collect = CollectStrBlock {148 truncate: false,149 lines: vec![],150 input,151 offset: 0,152 };153 lex_str_block(&mut collect)?;154 Ok(collect)155}156157pub struct CollectStrBlock<'s> {158 pub truncate: bool,159 pub lines: Vec<&'s str>,160 input: &'s str,161 offset: usize,162}163164impl<'d> StrBlockLexCtx<'d> for CollectStrBlock<'d> {165 fn remainder(&self) -> &'d str {166 self.input167 }168169 fn eat_error(&mut self, _ctx: &Context<'d>) {170 // Error will be returned, no need to record it here171 }172173 fn bump_pos(&mut self, s: usize) {174 self.offset += s;175 }176177 fn mark_truncating(&mut self) {178 self.truncate = true;179 }180181 fn mark_line(&mut self, line: &'d str) {182 self.lines.push(line)183 }184}1918520#[allow(clippy::too_many_lines)]186pub(crate) fn lex_str_block<'a>(lex: &mut impl StrBlockLexCtx<'a>) -> Result<(), StringBlockError> {21pub fn lex_str_block(lex: &mut Lexer<SyntaxKind>) -> Result<(), StringBlockError> {187 // debug_assert_eq!(lex.slice(), "|||");22 struct Context<'a> {23 source: &'a str,24 index: usize,25 offset: usize,26 }2728 impl<'a> Context<'a> {29 fn rest(&self) -> &'a str {30 &self.source[self.index..]31 }3233 fn next(&mut self) -> Option<char> {34 if self.index == self.source.len() {35 return None;36 }3738 match self.rest().chars().next() {39 None => None,40 Some(c) => {41 self.index += c.len_utf8();42 Some(c)43 }44 }45 }4647 fn peek(&self) -> Option<char> {48 if self.index == self.source.len() {49 return None;50 }5152 self.rest().chars().next()53 }5455 fn eat_if(&mut self, f: impl Fn(char) -> bool) -> usize {56 if self.peek().map(f).unwrap_or(false) {57 self.index += 1;58 return 1;59 }60 061 }6263 fn eat_while(&mut self, f: impl Fn(char) -> bool) -> usize {64 if self.index == self.source.len() {65 return 0;66 }6768 let next_char = self.rest().char_indices().find(|(_, c)| !f(*c));6970 match next_char {71 None => {72 let diff = self.source.len() - self.index;73 self.index = self.source.len();74 diff75 }76 Some((idx, _)) => {77 self.index += idx;78 idx79 }80 }81 }8283 fn skip(&mut self, len: usize) {84 self.index = match self.index + len {85 n if n > self.source.len() => self.source.len(),86 n => n,87 };88 }8990 #[allow(clippy::range_plus_one)]91 fn pos(&self) -> Range<usize> {92 if self.index == self.source.len() {93 self.offset + self.index..self.offset + self.index94 } else {95 // TODO: char size96 self.offset + self.index..self.offset + self.index + 197 }98 }99 }100101 // Check that b has at least the same whitespace prefix as a and returns the102 // amount of this whitespace, otherwise returns 0. If a has no whitespace103 // prefix than return 0.104 fn check_whitespace(a: &str, b: &str) -> usize {105 let a = a.as_bytes();106 let b = b.as_bytes();107108 for i in 0..a.len() {109 if a[i] != b' ' && a[i] != b'\t' {110 // a has run out of whitespace and b matched up to this point. Return result.111 return i;112 }113114 if i >= b.len() {115 // We ran off the edge of b while a still has whitespace. Return 0 as failure.116 return 0;117 }118119 if a[i] != b[i] {120 // a has whitespace but b does not. Return 0 as failure.121 return 0;122 }123 }124125 // We ran off the end of a and b kept up126 a.len()127 }128129 fn guess_token_end_and_bump<'a>(lex: &mut Lexer<'a, SyntaxKind>, ctx: &Context<'a>) {130 let end_index = ctx131 .rest()132 .find("|||")133 .map_or_else(|| ctx.rest().len(), |v| v + 3);134 lex.bump(ctx.index + end_index);135 }136137 debug_assert_eq!(lex.slice(), "|||");138 let mut ctx = Context {188 let mut ctx = Context::<'a> {139 source: lex.remainder(),189 source: lex.remainder(),140 index: 0,190 index: 0,141 offset: lex.span().end,142 };191 };143192144 ctx.eat_if(|v| v == '-');193 if ctx.eat_if(|v| v == '-') != 0 {194 lex.mark_truncating();195 }145196146 // Skip whitespaces197 // Skip whitespaces147 ctx.eat_while(|r| r == ' ' || r == '\t' || r == '\r');198 ctx.eat_while(|r| r == ' ' || r == '\t' || r == '\r');150 match ctx.next() {201 match ctx.next() {151 Some('\n') => (),202 Some('\n') => (),152 None => {203 None => {153 guess_token_end_and_bump(lex, &ctx);204 lex.eat_error(&ctx);154 return Err(UnexpectedEnd);205 return Err(UnexpectedEnd);155 }206 }156 // Text block requires new line after |||.207 // Text block requires new line after |||.157 Some(_) => {208 Some(_) => {158 guess_token_end_and_bump(lex, &ctx);209 lex.eat_error(&ctx);159 return Err(MissingNewLine);210 return Err(MissingNewLine);160 }211 }161 }212 }170221171 if num_whitespace == 0 {222 if num_whitespace == 0 {172 // Text block's first line must start with whitespace223 // Text block's first line must start with whitespace173 guess_token_end_and_bump(lex, &ctx);224 lex.eat_error(&ctx);174 return Err(MissingIndent);225 return Err(MissingIndent);175 }226 }176227177 loop {228 loop {178 debug_assert_ne!(num_whitespace, 0, "Unexpected value for num_whitespace");229 debug_assert_ne!(num_whitespace, 0, "Unexpected value for num_whitespace");179 ctx.skip(num_whitespace);230 ctx.skip(num_whitespace);180231232 let line_start = ctx.index;233 let mut line_size = 0;181 loop {234 loop {182 match ctx.next() {235 match ctx.next() {183 None => {236 None => {184 guess_token_end_and_bump(lex, &ctx);237 lex.eat_error(&ctx);185 return Err(UnexpectedEnd);238 return Err(UnexpectedEnd);186 }239 }187 Some('\n') => break,240 Some('\n') => {241 lex.mark_line(&ctx.source[line_start..line_start + line_size]);242 break;243 }188 Some(_) => (),244 Some(c) => {245 line_size += c.len_utf8();246 }189 }247 }190 }248 }191249192 // Skip any blank lines250 // Skip any blank lines193 while ctx.peek() == Some('\n') {251 while ctx.peek() == Some('\n') {252 lex.mark_line("");194 ctx.next();253 ctx.next();195 }254 }196255206 }265 }207266208 if !ctx.rest().starts_with("|||") {267 if !ctx.rest().starts_with("|||") {209 // Text block not terminated with |||210 let pos = ctx.pos();211 if pos.is_empty() {268 if ctx.rest().is_empty() {212 // eof213 lex.bump(ctx.index);269 lex.bump_pos(ctx.index);214 return Err(UnexpectedEnd);270 return Err(UnexpectedEnd);215 }271 }216217 guess_token_end_and_bump(lex, &ctx);272 lex.eat_error(&ctx);218 return Err(MissingTermination);273 return Err(MissingTermination);219 }274 }220275224 }279 }225 }280 }226281227 lex.bump(ctx.index);282 lex.bump_pos(ctx.index);228 Ok(())283 Ok(())229}284}230285