use syntax::codemap::Span;
-use {Indent, Shape};
use config::Config;
use rewrite::RewriteContext;
+use shape::{Indent, Shape};
use string::{rewrite_string, StringFormat};
-use utils::{first_line_width, last_line_width, wrap_str};
+use utils::{first_line_width, last_line_width};
fn is_custom_comment(comment: &str) -> bool {
if !comment.starts_with("//") {
pub fn closer(&self) -> &'a str {
match *self {
- CommentStyle::DoubleSlash |
- CommentStyle::TripleSlash |
- CommentStyle::Custom(..) |
- CommentStyle::Doc => "",
+ CommentStyle::DoubleSlash
+ | CommentStyle::TripleSlash
+ | CommentStyle::Custom(..)
+ | CommentStyle::Doc => "",
CommentStyle::DoubleBullet => " **/",
CommentStyle::SingleBullet | CommentStyle::Exclamation => " */",
}
pub fn line_with_same_comment_style(&self, line: &str, normalize_comments: bool) -> bool {
match *self {
CommentStyle::DoubleSlash | CommentStyle::TripleSlash | CommentStyle::Doc => {
- line.trim_left().starts_with(self.line_start().trim_left()) ||
- comment_style(line, normalize_comments) == *self
+ line.trim_left().starts_with(self.line_start().trim_left())
+ || comment_style(line, normalize_comments) == *self
}
CommentStyle::DoubleBullet | CommentStyle::SingleBullet | CommentStyle::Exclamation => {
- line.trim_left().starts_with(self.closer().trim_left()) ||
- line.trim_left().starts_with(self.line_start().trim_left()) ||
- comment_style(line, normalize_comments) == *self
+ line.trim_left().starts_with(self.closer().trim_left())
+ || line.trim_left().starts_with(self.line_start().trim_left())
+ || comment_style(line, normalize_comments) == *self
}
CommentStyle::Custom(opener) => line.trim_left().starts_with(opener.trim_right()),
}
} else {
CommentStyle::DoubleSlash
}
- } else if (orig.starts_with("///") && orig.chars().nth(3).map_or(true, |c| c != '/')) ||
- (orig.starts_with("/**") && !orig.starts_with("/**/"))
+ } else if (orig.starts_with("///") && orig.chars().nth(3).map_or(true, |c| c != '/'))
+ || (orig.starts_with("/**") && !orig.starts_with("/**/"))
{
CommentStyle::TripleSlash
} else if orig.starts_with("//!") || orig.starts_with("/*!") {
last_line_width(prev_str) + first_line_width(next_str) + first_sep.len();
let indent_str = shape.indent.to_string(context.config);
- let missing_comment = try_opt!(rewrite_missing_comment(span, shape, context));
+ let missing_comment = rewrite_missing_comment(span, shape, context)?;
if missing_comment.is_empty() {
if allow_extend && prev_str.len() + first_sep.len() + next_str.len() <= shape.width {
// We have a missing comment between the first expression and the second expression.
// Peek the the original source code and find out whether there is a newline between the first
- // expression and the second expression or the missing comment. We will preserve the orginal
+ // expression and the second expression or the missing comment. We will preserve the original
// layout whenever possible.
let original_snippet = context.snippet(span);
let prefer_same_line = if let Some(pos) = original_snippet.chars().position(|c| c == '/') {
};
Some(format!(
"{}{}{}{}{}",
- prev_str,
- first_sep,
- missing_comment,
- second_sep,
- next_str,
+ prev_str, first_sep, missing_comment, second_sep, next_str,
))
}
// we should stop now.
let num_bare_lines = orig.lines()
.map(|line| line.trim())
- .filter(|l| {
- !(l.starts_with('*') || l.starts_with("//") || l.starts_with("/*"))
- })
+ .filter(|l| !(l.starts_with('*') || l.starts_with("//") || l.starts_with("/*")))
.count();
if num_bare_lines > 0 && !config.normalize_comments() {
return Some(orig.to_owned());
.collect::<Vec<_>>()
.join("\n");
- let first_group_str = try_opt!(rewrite_comment_inner(
- &first_group,
- block_style,
- style,
- shape,
- config,
- ));
+ let first_group_str = rewrite_comment_inner(&first_group, block_style, style, shape, config)?;
if rest.is_empty() {
Some(first_group_str)
} else {
.checked_sub(closer.len() + opener.len())
.unwrap_or(1);
let indent_str = shape.indent.to_string(config);
- let fmt = StringFormat {
+ let fmt_indent = shape.indent + (opener.len() - line_start.len());
+ let mut fmt = StringFormat {
opener: "",
closer: "",
line_start: line_start,
line_end: "",
- shape: Shape::legacy(max_chars, shape.indent + (opener.len() - line_start.len())),
+ shape: Shape::legacy(max_chars, fmt_indent),
trim_end: true,
config: config,
};
line
})
.map(|s| left_trim_comment_line(s, &style))
- .map(|line| if orig.starts_with("/*") && line_breaks == 0 {
- line.trim_left()
- } else {
- line
+ .map(|line| {
+ if orig.starts_with("/*") && line_breaks == 0 {
+ line.trim_left()
+ } else {
+ line
+ }
});
let mut result = opener.to_owned();
+ let mut is_prev_line_multi_line = false;
+ let mut inside_code_block = false;
+ let comment_line_separator = format!("\n{}{}", indent_str, line_start);
for line in lines {
if result == opener {
if line.is_empty() {
continue;
}
+ } else if is_prev_line_multi_line && !line.is_empty() {
+ result.push(' ')
} else {
- result.push('\n');
- result.push_str(&indent_str);
- result.push_str(line_start);
+ result.push_str(&comment_line_separator);
+ }
+
+ if line.starts_with("```") {
+ inside_code_block = !inside_code_block;
+ }
+ if inside_code_block {
+ result.push_str(line);
+ continue;
}
- if config.wrap_comments() && line.len() > max_chars {
- let rewrite = rewrite_string(line, &fmt).unwrap_or_else(|| line.to_owned());
- result.push_str(&rewrite);
+ if config.wrap_comments() && line.len() > fmt.shape.width && !has_url(line) {
+ match rewrite_string(line, &fmt, Some(max_chars)) {
+ Some(ref s) => {
+ is_prev_line_multi_line = s.contains('\n');
+ result.push_str(s);
+ }
+ None if is_prev_line_multi_line => {
+ // We failed to put the current `line` next to the previous `line`.
+ // Remove the trailing space, then start rewrite on the next line.
+ result.pop();
+ result.push_str(&comment_line_separator);
+ fmt.shape = Shape::legacy(max_chars, fmt_indent);
+ match rewrite_string(line, &fmt, Some(max_chars)) {
+ Some(ref s) => {
+ is_prev_line_multi_line = s.contains('\n');
+ result.push_str(s);
+ }
+ None => {
+ is_prev_line_multi_line = false;
+ result.push_str(line);
+ }
+ }
+ }
+ None => {
+ is_prev_line_multi_line = false;
+ result.push_str(line);
+ }
+ }
+
+ fmt.shape = if is_prev_line_multi_line {
+ // 1 = " "
+ let offset = 1 + last_line_width(&result) - line_start.len();
+ Shape {
+ width: max_chars.checked_sub(offset).unwrap_or(0),
+ indent: fmt_indent,
+ offset: fmt.shape.offset + offset,
+ }
+ } else {
+ Shape::legacy(max_chars, fmt_indent)
+ };
} else {
if line.is_empty() && result.ends_with(' ') {
// Remove space if this is an empty comment or a doc comment.
result.pop();
}
result.push_str(line);
+ fmt.shape = Shape::legacy(max_chars, fmt_indent);
+ is_prev_line_multi_line = false;
}
}
Some(result)
}
+/// Returns true if the given string MAY include URLs or alike.
+fn has_url(s: &str) -> bool {
+ // This function may return false positive, but should get its job done in most cases.
+ s.contains("https://") || s.contains("http://") || s.contains("ftp://") || s.contains("file://")
+}
+
/// Given the span, rewrite the missing comment inside it if available.
/// Note that the given span must only include comments (or leading/trailing whitespaces).
pub fn rewrite_missing_comment(
context: &RewriteContext,
used_width: usize,
) -> Option<String> {
- let missing_comment = try_opt!(rewrite_missing_comment(span, shape, context));
+ let missing_comment = rewrite_missing_comment(span, shape, context)?;
if missing_comment.is_empty() {
Some(String::new())
} else {
/// Trims comment characters and possibly a single space from the left of a string.
/// Does not trim all whitespace.
fn left_trim_comment_line<'a>(line: &'a str, style: &CommentStyle) -> &'a str {
- if line.starts_with("//! ") || line.starts_with("/// ") || line.starts_with("/*! ") ||
- line.starts_with("/** ")
+ if line.starts_with("//! ") || line.starts_with("/// ") || line.starts_with("/*! ")
+ || line.starts_with("/** ")
{
&line[4..]
} else if let CommentStyle::Custom(opener) = *style {
} else {
&line[opener.trim_right().len()..]
}
- } else if line.starts_with("/* ") || line.starts_with("// ") || line.starts_with("//!") ||
- line.starts_with("///") || line.starts_with("** ") ||
- line.starts_with("/*!") ||
- (line.starts_with("/**") && !line.starts_with("/**/"))
+ } else if line.starts_with("/* ") || line.starts_with("// ") || line.starts_with("//!")
+ || line.starts_with("///") || line.starts_with("** ")
+ || line.starts_with("/*!")
+ || (line.starts_with("/**") && !line.starts_with("/**/"))
{
&line[3..]
- } else if line.starts_with("/*") || line.starts_with("* ") || line.starts_with("//") ||
- line.starts_with("**")
+ } else if line.starts_with("/*") || line.starts_with("* ") || line.starts_with("//")
+ || line.starts_with("**")
{
&line[2..]
} else if line.starts_with('*') {
return Some(i - pat.len());
}
Some(c) => match kind {
- FullCodeCharKind::Normal if b == c => {}
+ FullCodeCharKind::Normal | FullCodeCharKind::InString if b == c => {}
_ => {
needle_iter = pat.chars();
}
pub fn find_comment_end(s: &str) -> Option<usize> {
let mut iter = CharClasses::new(s.char_indices());
for (kind, (i, _c)) in &mut iter {
- if kind == FullCodeCharKind::Normal {
+ if kind == FullCodeCharKind::Normal || kind == FullCodeCharKind::InString {
return Some(i);
}
}
CharClasses::new(text.chars()).any(|(kind, _)| kind.is_comment())
}
+/// Remove trailing spaces from the specified snippet. We do not remove spaces
+/// inside strings or comments.
+pub fn remove_trailing_white_spaces(text: &str) -> String {
+ let mut buffer = String::with_capacity(text.len());
+ let mut space_buffer = String::with_capacity(128);
+ for (char_kind, c) in CharClasses::new(text.chars()) {
+ match c {
+ '\n' => {
+ if char_kind == FullCodeCharKind::InString {
+ buffer.push_str(&space_buffer);
+ }
+ space_buffer.clear();
+ buffer.push('\n');
+ }
+ _ if c.is_whitespace() => {
+ space_buffer.push(c);
+ }
+ _ => {
+ if !space_buffer.is_empty() {
+ buffer.push_str(&space_buffer);
+ space_buffer.clear();
+ }
+ buffer.push(c);
+ }
+ }
+ }
+ buffer
+}
+
struct CharClasses<T>
where
T: Iterator,
InComment,
/// Last character of a comment, '\n' for a line comment, '/' for a block comment.
EndComment,
+ /// Inside a string.
+ InString,
}
impl FullCodeCharKind {
fn is_comment(&self) -> bool {
match *self {
- FullCodeCharKind::Normal => false,
- FullCodeCharKind::StartComment |
- FullCodeCharKind::InComment |
- FullCodeCharKind::EndComment => true,
+ FullCodeCharKind::StartComment
+ | FullCodeCharKind::InComment
+ | FullCodeCharKind::EndComment => true,
+ _ => false,
}
}
type Item = (FullCodeCharKind, T::Item);
fn next(&mut self) -> Option<(FullCodeCharKind, T::Item)> {
- let item = try_opt!(self.base.next());
+ let item = self.base.next()?;
let chr = item.get_char();
+ let mut char_kind = FullCodeCharKind::Normal;
self.status = match self.status {
CharClassesStatus::LitString => match chr {
'"' => CharClassesStatus::Normal,
- '\\' => CharClassesStatus::LitStringEscape,
- _ => CharClassesStatus::LitString,
+ '\\' => {
+ char_kind = FullCodeCharKind::InString;
+ CharClassesStatus::LitStringEscape
+ }
+ _ => {
+ char_kind = FullCodeCharKind::InString;
+ CharClassesStatus::LitString
+ }
},
- CharClassesStatus::LitStringEscape => CharClassesStatus::LitString,
+ CharClassesStatus::LitStringEscape => {
+ char_kind = FullCodeCharKind::InString;
+ CharClassesStatus::LitString
+ }
CharClassesStatus::LitChar => match chr {
'\\' => CharClassesStatus::LitCharEscape,
'\'' => CharClassesStatus::Normal,
},
CharClassesStatus::LitCharEscape => CharClassesStatus::LitChar,
CharClassesStatus::Normal => match chr {
- '"' => CharClassesStatus::LitString,
+ '"' => {
+ char_kind = FullCodeCharKind::InString;
+ CharClassesStatus::LitString
+ }
'\'' => CharClassesStatus::LitChar,
'/' => match self.base.peek() {
Some(next) if next.get_char() == '*' => {
}
},
};
- Some((FullCodeCharKind::Normal, item))
+ Some((char_kind, item))
}
}
type Item = (CodeCharKind, usize, &'a str);
fn next(&mut self) -> Option<Self::Item> {
- let (kind, (start_idx, _)) = try_opt!(self.iter.next());
+ let (kind, (start_idx, _)) = self.iter.next()?;
match kind {
- FullCodeCharKind::Normal => {
+ FullCodeCharKind::Normal | FullCodeCharKind::InString => {
// Consume all the Normal code
- while let Some(&(FullCodeCharKind::Normal, (_, _))) = self.iter.peek() {
+ while let Some(&(char_kind, _)) = self.iter.peek() {
+ if char_kind.is_comment() {
+ break;
+ }
let _ = self.iter.next();
}
}
let mut iter = CharClasses::new(subslice.char_indices());
for (kind, (i, c)) in &mut iter {
- let is_comment_connector = self.last_slice_kind == CodeCharKind::Normal &&
- &subslice[..2] == "//" &&
- [' ', '\t'].contains(&c);
+ let is_comment_connector = self.last_slice_kind == CodeCharKind::Normal
+ && &subslice[..2] == "//"
+ && [' ', '\t'].contains(&c);
if is_comment_connector && first_whitespace.is_none() {
first_whitespace = Some(i);
new: String,
span: Span,
context: &RewriteContext,
- shape: Shape,
) -> Option<String> {
let snippet = context.snippet(span);
- if changed_comment_content(&snippet, &new) {
- // We missed some comments
- // Keep previous formatting if it satisfies the constrains
- wrap_str(snippet, context.config.max_width(), shape)
+ if snippet != new && changed_comment_content(&snippet, &new) {
+ // We missed some comments. Keep the original text.
+ Some(snippet)
} else {
Some(new)
}
type Item = char;
fn next(&mut self) -> Option<Self::Item> {
loop {
- let mut c = try_opt!(self.iter.next());
+ let mut c = self.iter.next()?;
if self.is_block && self.at_start_line {
while c.is_whitespace() {
- c = try_opt!(self.iter.next());
+ c = self.iter.next()?;
}
// Ignore leading '*'
if c == '*' {
- c = try_opt!(self.iter.next());
+ c = self.iter.next()?;
}
} else if c == '\n' {
self.at_start_line = true;
&comment[3..]
} else if comment.starts_with("//") {
&comment[2..]
- } else if (comment.starts_with("/**") && !comment.starts_with("/**/")) ||
- comment.starts_with("/*!")
+ } else if (comment.starts_with("/**") && !comment.starts_with("/**/"))
+ || comment.starts_with("/*!")
{
&comment[3..comment.len() - 2]
} else {
mod test {
use super::{contains_comment, rewrite_comment, CharClasses, CodeCharKind, CommentCodeSlices,
FindUncommented, FullCodeCharKind};
- use {Indent, Shape};
+ use shape::{Indent, Shape};
#[test]
fn char_classes() {
fn uncommented(text: &str) -> String {
CharClasses::new(text.chars())
.filter_map(|(s, c)| match s {
- FullCodeCharKind::Normal => Some(c),
+ FullCodeCharKind::Normal | FullCodeCharKind::InString => Some(c),
_ => None,
})
.collect()