]> git.lizzy.rs Git - rust.git/commitdiff
Format lazy_static! macro
authortopecongiro <seuchida@gmail.com>
Fri, 23 Feb 2018 16:05:19 +0000 (01:05 +0900)
committertopecongiro <seuchida@gmail.com>
Fri, 23 Feb 2018 16:05:19 +0000 (01:05 +0900)
Some macros like `lazy_static!` cannot be parsed as a valid AST. Therefore, we
need to parse them manually to be able to format them. Obviously this method
will not scale, we will never be able to cover every macro in the entire
ecosystem. That being said, I think it will not hurt to be able to format macros
that are popular (such as `lazy_static!`) as long as rustfmt will not produce
broken code when the macro has syntax that is not expected.

rustfmt-core/src/macros.rs

index bb0f066e9573f0d6218642f192c79d5cd4e844e1..6d65cd76eba0858ac3c03023bde280e891772d7f 100644 (file)
@@ -155,7 +155,8 @@ pub fn rewrite_macro(
     };
 
     let ts: TokenStream = mac.node.stream();
-    if ts.is_empty() && !contains_comment(context.snippet(mac.span)) {
+    let has_comment = contains_comment(context.snippet(mac.span));
+    if ts.is_empty() && !has_comment {
         return match style {
             MacroStyle::Parens if position == MacroPosition::Item => {
                 Some(format!("{}();", macro_name))
@@ -165,6 +166,13 @@ pub fn rewrite_macro(
             MacroStyle::Braces => Some(format!("{}{{}}", macro_name)),
         };
     }
+    // Format well-known macros which cannot be parsed as a valid AST.
+    // TODO: Maybe add more macros?
+    if macro_name == "lazy_static!" && !has_comment {
+        if let success @ Some(..) = format_lazy_static(context, shape, &ts) {
+            return success;
+        }
+    }
 
     let mut parser = new_parser_from_tts(context.parse_session, ts.trees().collect());
     let mut arg_vec = Vec::new();
@@ -848,6 +856,82 @@ fn rewrite(
     }
 }
 
+/// Format `lazy_static!` from https://crates.io/crates/lazy_static.
+///
+/// # Expected syntax
+///
+/// ```
+/// lazy_static! {
+///     [pub] static ref NAME_1: TYPE_1 = EXPR_1;
+///     [pub] static ref NAME_2: TYPE_2 = EXPR_2;
+///     ...
+///     [pub] static ref NAME_N: TYPE_N = EXPR_N;
+/// }
+/// ```
+fn format_lazy_static(context: &RewriteContext, shape: Shape, ts: &TokenStream) -> Option<String> {
+    let mut result = String::with_capacity(1024);
+    let mut parser = new_parser_from_tts(context.parse_session, ts.trees().collect());
+    let nested_shape = shape.block_indent(context.config.tab_spaces());
+
+    result.push_str("lazy_static! {");
+    result.push_str(&nested_shape.indent.to_string_with_newline(context.config));
+
+    macro parse_or($method:ident $(,)* $($arg:expr),* $(,)*) {
+        match parser.$method($($arg,)*) {
+            Ok(val) => {
+                if parser.sess.span_diagnostic.has_errors() {
+                    parser.sess.span_diagnostic.reset_err_count();
+                    return None;
+                } else {
+                    val
+                }
+            }
+            Err(mut err) => {
+                err.cancel();
+                parser.sess.span_diagnostic.reset_err_count();
+                return None;
+            }
+        }
+    }
+
+    while parser.token != Token::Eof {
+        // Parse a `lazy_static!` item.
+        let vis = ::utils::format_visibility(&parse_or!(parse_visibility, false));
+        parser.eat_keyword(symbol::keywords::Static);
+        parser.eat_keyword(symbol::keywords::Ref);
+        let id = parse_or!(parse_ident);
+        parser.eat(&Token::Colon);
+        let ty = parse_or!(parse_ty);
+        parser.eat(&Token::Eq);
+        let expr = parse_or!(parse_expr);
+        parser.eat(&Token::Semi);
+
+        // Rewrite as a static item.
+        let mut stmt = String::with_capacity(128);
+        stmt.push_str(&format!(
+            "{}static ref {}: {} =",
+            vis,
+            id,
+            ty.rewrite(context, shape)?
+        ));
+        result.push_str(&::expr::rewrite_assign_rhs(
+            context,
+            stmt,
+            &*expr,
+            nested_shape.sub_width(1)?,
+        )?);
+        result.push(';');
+        if parser.token != Token::Eof {
+            result.push_str(&nested_shape.indent.to_string_with_newline(context.config));
+        }
+    }
+
+    result.push_str(&shape.indent.to_string_with_newline(context.config));
+    result.push('}');
+
+    Some(result)
+}
+
 #[cfg(test)]
 mod test {
     use super::*;