Skip to content

Commit c3e462a

Browse files
authored
Finish support for non-UTF-8 source files (#842)
* Add fixture in EUC-JP encoding * Support non-UTF-8 source files * fmt
1 parent b2bcdc9 commit c3e462a

11 files changed

Lines changed: 241 additions & 246 deletions
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# encoding: EUC-JP
2+
3+
def 挨拶(名前)
4+
"こんにちは、#{名前}さん"
5+
end
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# encoding: EUC-JP
2+
3+
def 挨拶(名前)
4+
"こんにちは、#{名前}さん"
5+
end

librubyfmt/src/comment_block.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,7 @@ impl CommentBlock {
6464
// Ignore empty vecs -- these represent blank lines between
6565
// groups of comments
6666
if !comment.is_empty() && !comment.starts_with(b"=begin") {
67-
comment
68-
.to_mut()
69-
.splice(0..0, indent.as_bytes().iter().copied());
67+
comment.to_mut().splice(0..0, indent.iter().copied());
7068
}
7169
}
7270
self

librubyfmt/src/format_prism.rs

Lines changed: 80 additions & 76 deletions
Large diffs are not rendered by default.

librubyfmt/src/heredoc_string.rs

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ pub enum HeredocKind {
1111
}
1212

1313
impl HeredocKind {
14-
pub fn from_string(kind_str: &str) -> Self {
15-
if kind_str.contains('~') {
14+
pub fn from_bytes(kind_bytes: &[u8]) -> Self {
15+
if kind_bytes.contains(&b'~') {
1616
HeredocKind::Squiggly
17-
} else if kind_str.contains('-') {
17+
} else if kind_bytes.contains(&b'-') {
1818
HeredocKind::Dash
1919
} else {
2020
HeredocKind::Bare
@@ -35,23 +35,23 @@ impl HeredocKind {
3535
/// that should not be indented.
3636
#[derive(Debug, Clone)]
3737
pub enum HeredocSegment {
38-
Normal(String),
38+
Normal(Vec<u8>),
3939
/// Content from nested non-squiggly heredocs, should never receive squiggly indentation.
4040
/// This includes both the heredoc content and the closing identifier.
41-
Raw(String),
41+
Raw(Vec<u8>),
4242
}
4343

4444
#[derive(Debug, Clone)]
4545
pub struct HeredocString<'src> {
46-
symbol: Cow<'src, str>,
46+
symbol: Cow<'src, [u8]>,
4747
pub kind: HeredocKind,
4848
pub segments: Vec<HeredocSegment>,
4949
pub indent: ColNumber,
5050
}
5151

5252
impl<'src> HeredocString<'src> {
5353
pub fn new(
54-
symbol: Cow<'src, str>,
54+
symbol: Cow<'src, [u8]>,
5555
kind: HeredocKind,
5656
segments: Vec<HeredocSegment>,
5757
indent: ColNumber,
@@ -64,49 +64,50 @@ impl<'src> HeredocString<'src> {
6464
}
6565
}
6666

67-
pub fn render_as_string(self) -> String {
67+
pub fn render_as_bytes(self) -> Vec<u8> {
6868
let indent = self.indent;
6969

7070
if self.kind.is_squiggly() {
7171
// For squiggly heredocs, we need to apply indentation to Normal segments
7272
// but not to Raw segments (which come from nested non-squiggly heredocs).
73-
let mut result = String::new();
73+
let mut result = Vec::new();
7474
for segment in self.segments {
7575
match segment {
7676
HeredocSegment::Normal(content) => {
7777
// Apply squiggly indentation to each line
78-
for (i, line) in content.split('\n').enumerate() {
78+
for (i, line) in content.split(|&b| b == b'\n').enumerate() {
7979
if i > 0 {
80-
result.push('\n');
80+
result.push(b'\n');
8181
}
82-
let indented = format!("{}{}", get_indent(indent as usize + 2), line);
83-
result.push_str(indented.trim_end());
82+
let mut indented = get_indent(indent as usize + 2).into_owned();
83+
indented.extend_from_slice(line);
84+
result.extend_from_slice(indented.trim_ascii_end());
8485
}
8586
}
8687
HeredocSegment::Raw(content) => {
8788
// No indentation for raw content (nested non-squiggly heredocs)
88-
for (i, line) in content.split('\n').enumerate() {
89+
for (i, line) in content.split(|&b| b == b'\n').enumerate() {
8990
if i > 0 {
90-
result.push('\n');
91+
result.push(b'\n');
9192
}
92-
result.push_str(line.trim_end());
93+
result.extend_from_slice(line.trim_ascii_end());
9394
}
9495
}
9596
}
9697
}
9798
result
9899
} else {
99100
// For non-squiggly heredocs, just join segments and trim line endings
100-
let mut result = String::new();
101+
let mut result = Vec::new();
101102
for segment in self.segments {
102103
let content = match segment {
103104
HeredocSegment::Normal(s) | HeredocSegment::Raw(s) => s,
104105
};
105-
for (i, line) in content.split('\n').enumerate() {
106+
for (i, line) in content.split(|&b| b == b'\n').enumerate() {
106107
if i > 0 {
107-
result.push('\n');
108+
result.push(b'\n');
108109
}
109-
result.push_str(line.trim_end());
110+
result.extend_from_slice(line.trim_ascii_end());
110111
}
111112
}
112113
result
@@ -127,7 +128,11 @@ impl<'src> HeredocString<'src> {
127128
/// However, the closing symbol should *not* have
128129
/// quotes, so we must strip them from the symbol when
129130
/// rendering the closing symbol.
130-
pub fn closing_symbol(&self) -> String {
131-
self.symbol.replace(['\'', '"'], "")
131+
pub fn closing_symbol(&self) -> Vec<u8> {
132+
self.symbol
133+
.iter()
134+
.filter(|&&b| b != b'\'' && b != b'"')
135+
.copied()
136+
.collect()
132137
}
133138
}

librubyfmt/src/line_tokens.rs

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ pub fn clats_direct_part<'src>(
1616
})
1717
}
1818

19-
pub fn clats_heredoc_close<'src>(symbol: String) -> ConcreteLineTokenAndTargets<'src> {
19+
pub fn clats_heredoc_close<'src>(symbol: Vec<u8>) -> ConcreteLineTokenAndTargets<'src> {
2020
ConcreteLineTokenAndTargets::ConcreteLineToken(ConcreteLineToken::HeredocClose { symbol })
2121
}
2222

@@ -65,7 +65,7 @@ pub enum ConcreteLineToken<'src> {
6565
},
6666
DoubleQuote,
6767
LTStringContent {
68-
content: Cow<'src, str>,
68+
content: Cow<'src, [u8]>,
6969
},
7070
SingleSlash,
7171
Comment {
@@ -76,7 +76,7 @@ pub enum ConcreteLineToken<'src> {
7676
},
7777
End,
7878
HeredocClose {
79-
symbol: String,
79+
symbol: Vec<u8>,
8080
},
8181
// These are "magic" tokens. They have no concrete representation,
8282
// but they're meaningful inside of the render queue
@@ -85,21 +85,18 @@ pub enum ConcreteLineToken<'src> {
8585
EndCallChainIndent,
8686
HeredocStart {
8787
kind: HeredocKind,
88-
symbol: &'src str,
88+
symbol: &'src [u8],
8989
},
9090
RawHeredocContent {
91-
content: String,
91+
content: Vec<u8>,
9292
},
9393
}
9494

9595
impl<'src> ConcreteLineToken<'src> {
9696
pub fn into_ruby(self) -> Cow<'src, [u8]> {
9797
match self {
9898
Self::HardNewLine => Cow::Borrowed(b"\n"),
99-
Self::Indent { depth } => match get_indent(depth as usize) {
100-
Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()),
101-
Cow::Owned(s) => Cow::Owned(s.into_bytes()),
102-
},
99+
Self::Indent { depth } => get_indent(depth as usize),
103100
Self::Keyword { keyword } => Cow::Borrowed(keyword.as_bytes()),
104101
Self::ConditionalKeyword { contents } => Cow::Borrowed(contents.as_bytes()),
105102
Self::DoKeyword => Cow::Borrowed(b"do"),
@@ -122,17 +119,14 @@ impl<'src> ConcreteLineToken<'src> {
122119
Self::CloseParen => Cow::Borrowed(b")"),
123120
Self::Op { op } => Cow::Borrowed(op),
124121
Self::DoubleQuote => Cow::Borrowed(b"\""),
125-
Self::LTStringContent { content } => match content {
126-
Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()),
127-
Cow::Owned(s) => Cow::Owned(s.into_bytes()),
128-
},
122+
Self::LTStringContent { content } => content,
129123
Self::SingleSlash => Cow::Borrowed(b"\\"),
130124
Self::Comment { contents } => contents,
131125
Self::Delim { contents } => Cow::Borrowed(contents.as_bytes()),
132126
Self::End => Cow::Borrowed(b"end"),
133-
Self::HeredocClose { symbol } => Cow::Owned(symbol.into()),
134-
Self::HeredocStart { symbol, .. } => Cow::Borrowed(symbol.as_bytes()),
135-
Self::RawHeredocContent { content } => Cow::Owned(content.into()),
127+
Self::HeredocClose { symbol } => Cow::Owned(symbol),
128+
Self::HeredocStart { symbol, .. } => Cow::Borrowed(symbol),
129+
Self::RawHeredocContent { content } => Cow::Owned(content),
136130
// no-op, this is purely semantic information
137131
// for the render queue
138132
Self::AfterCallChain | Self::BeginCallChainIndent | Self::EndCallChainIndent => {
@@ -355,9 +349,9 @@ impl<'src> AbstractLineToken<'src> {
355349
let kind = hds.kind;
356350
let symbol = hds.closing_symbol();
357351

358-
let s = hds.render_as_string();
352+
let s = hds.render_as_bytes();
359353
if !s.is_empty() {
360-
out.push(clats_direct_part(s.into_bytes()));
354+
out.push(clats_direct_part(s));
361355
out.push(cltats_hard_newline());
362356
}
363357
if !kind.is_bare() {

librubyfmt/src/parser_state.rs

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ use crate::types::{ColNumber, LineNumber, SourceOffset};
1111
use log::debug;
1212
use std::borrow::Cow;
1313
use std::io::{self, Write};
14-
use std::str;
1514

1615
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1716
pub enum FormattingContext {
@@ -90,7 +89,7 @@ impl<'src> ParserState<'src> {
9089
}
9190
pub(crate) fn push_heredoc_content<F>(
9291
&mut self,
93-
symbol: impl Into<Cow<'src, str>>,
92+
symbol: impl Into<Cow<'src, [u8]>>,
9493
kind: HeredocKind,
9594
end_line: LineNumber,
9695
formatting_func: F,
@@ -121,11 +120,11 @@ impl<'src> ParserState<'src> {
121120
));
122121
}
123122

124-
pub(crate) fn emit_heredoc_start(&mut self, symbol: &'src str, kind: HeredocKind) {
123+
pub(crate) fn emit_heredoc_start(&mut self, symbol: &'src [u8], kind: HeredocKind) {
125124
self.push_concrete_token(ConcreteLineToken::HeredocStart { kind, symbol });
126125
}
127126

128-
pub(crate) fn emit_heredoc_close(&mut self, symbol: String) {
127+
pub(crate) fn emit_heredoc_close(&mut self, symbol: Vec<u8>) {
129128
self.push_concrete_token(ConcreteLineToken::HeredocClose { symbol });
130129
}
131130

@@ -390,9 +389,9 @@ impl<'src> ParserState<'src> {
390389
self.push_concrete_token(ConcreteLineToken::DoubleQuote);
391390
}
392391

393-
pub(crate) fn emit_string_content(&mut self, s: impl Into<Cow<'src, str>>) {
392+
pub(crate) fn emit_string_content(&mut self, s: impl Into<Cow<'src, [u8]>>) {
394393
let content = s.into();
395-
let newline_count = content.matches('\n').count() as u64;
394+
let newline_count = content.iter().filter(|&&b| b == b'\n').count() as u64;
396395
self.current_orig_line_number += newline_count;
397396
for be in self.breakable_entry_stack.iter_mut().rev() {
398397
be.push_line_number(self.current_orig_line_number);
@@ -653,7 +652,7 @@ impl<'src> ParserState<'src> {
653652
let kind = next_heredoc.kind;
654653
let symbol = next_heredoc.closing_symbol();
655654
let space_count = next_heredoc.indent;
656-
let string_contents = next_heredoc.render_as_string();
655+
let string_contents = next_heredoc.render_as_bytes();
657656

658657
// When inside a squiggly heredoc context and this is a non-squiggly heredoc,
659658
// emit RawHeredocContent tokens so the content won't receive squiggly indentation.
@@ -666,7 +665,7 @@ impl<'src> ParserState<'src> {
666665
});
667666
} else {
668667
self.push_concrete_token(ConcreteLineToken::DirectPart {
669-
part: Cow::Owned(string_contents.into_bytes()),
668+
part: Cow::Owned(string_contents),
670669
});
671670
}
672671
self.emit_newline();
@@ -675,14 +674,13 @@ impl<'src> ParserState<'src> {
675674
if emit_as_raw {
676675
// For bare/dash heredocs inside squiggly context, emit the close as raw content
677676
let close_content = if kind.is_bare() {
678-
symbol.replace('\'', "")
677+
symbol
679678
} else {
680679
// Dash heredocs can have indented close
681-
format!(
682-
"{}{}",
683-
crate::util::get_indent(space_count as usize),
684-
symbol.replace('\'', "")
685-
)
680+
let indent = crate::util::get_indent(space_count as usize);
681+
let mut content = indent.into_owned();
682+
content.extend_from_slice(&symbol);
683+
content
686684
};
687685
self.push_concrete_token(ConcreteLineToken::RawHeredocContent {
688686
content: close_content,
@@ -691,7 +689,7 @@ impl<'src> ParserState<'src> {
691689
if !kind.is_bare() {
692690
self.push_concrete_token(ConcreteLineToken::Indent { depth: space_count });
693691
}
694-
self.emit_heredoc_close(symbol.replace('\'', ""));
692+
self.emit_heredoc_close(symbol);
695693
}
696694

697695
if !skip {
@@ -820,9 +818,9 @@ impl<'src> ParserState<'src> {
820818
let final_tokens = rqw.into_tokens();
821819

822820
let mut segments = Vec::new();
823-
let mut current_normal = String::new();
821+
let mut current_normal = Vec::new();
824822

825-
fn flush_normal(current: &mut String, segments: &mut Vec<HeredocSegment>) {
823+
fn flush_normal(current: &mut Vec<u8>, segments: &mut Vec<HeredocSegment>) {
826824
if !current.is_empty() {
827825
segments.push(HeredocSegment::Normal(std::mem::take(current)));
828826
}
@@ -835,9 +833,7 @@ impl<'src> ParserState<'src> {
835833
segments.push(HeredocSegment::Raw(content));
836834
} else {
837835
// Accumulate into normal content
838-
current_normal
839-
// TODO(@reese): Use &[u8] here when string internals are updated
840-
.push_str(std::str::from_utf8(&token.into_ruby()).unwrap());
836+
current_normal.extend_from_slice(&token.into_ruby());
841837
}
842838
}
843839

librubyfmt/src/render_queue_writer.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ impl<'src> RenderQueueWriter<'src> {
6666
}) => {
6767
if !contents.is_empty() {
6868
let indent = get_indent(accum.additional_indent as usize * 2);
69-
let mut new_contents = indent.as_bytes().to_vec();
69+
let mut new_contents = indent.into_owned();
7070
new_contents.extend_from_slice(contents);
7171
next_token = ConcreteLineTokenAndTargets::ConcreteLineToken(
7272
ConcreteLineToken::Comment {
@@ -83,7 +83,7 @@ impl<'src> RenderQueueWriter<'src> {
8383
.unwrap_or(false)
8484
{
8585
let indent = get_indent(accum.additional_indent as usize * 2);
86-
let indent_bytes = indent.as_bytes();
86+
let indent_bytes = indent.as_ref();
8787
let mut new_contents = Vec::new();
8888
let parts = part.split(|&b| b == b'\n');
8989

@@ -108,11 +108,9 @@ impl<'src> RenderQueueWriter<'src> {
108108
// Bare heredocs (e.g. <<FOO) must have the closing ident completely unindented, so
109109
// ignore them in this case
110110
if current_heredoc_kind.map(|k| !k.is_bare()).unwrap_or(false) {
111-
let new_contents: String = format!(
112-
"{}{}",
113-
get_indent(accum.additional_indent as usize * 2),
114-
symbol
115-
);
111+
let indent = get_indent(accum.additional_indent as usize * 2);
112+
let mut new_contents = indent.into_owned();
113+
new_contents.extend_from_slice(symbol);
116114
next_token = clats_heredoc_close(new_contents);
117115
}
118116
current_heredoc_kind = None;

0 commit comments

Comments
 (0)