From 0e8e8cfc9be642311be9995934bb864560b8c553 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Sun, 26 Jul 2015 20:23:11 +0530 Subject: [PATCH] Basic framework for structured logging --- Cargo.toml | 4 ++++ src/approx_const.rs | 3 ++- src/attrs.rs | 4 ++-- src/bit_mask.rs | 23 ++++++++++++----------- src/collapsible_if.rs | 4 ++-- src/eq_op.rs | 3 ++- src/eta_reduction.rs | 4 +++- src/identity_op.rs | 4 ++-- src/len_zero.rs | 9 +++++---- src/misc.rs | 16 ++++++++-------- src/mut_mut.rs | 8 ++++---- src/needless_bool.rs | 10 +++++----- src/ptr_arg.rs | 5 +++-- src/types.rs | 4 +++- src/unicode.rs | 3 ++- src/utils.rs | 15 ++++++++++++++- 16 files changed, 73 insertions(+), 46 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2d00f863e29..bd4be213b91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,7 @@ compiletest_rs = "*" regex = "*" regex_macros = "*" lazy_static = "*" + +[features] + +structured_logging = [] \ No newline at end of file diff --git a/src/approx_const.rs b/src/approx_const.rs index 8a93bbfa933..03d4da1ab7f 100644 --- a/src/approx_const.rs +++ b/src/approx_const.rs @@ -7,6 +7,7 @@ use syntax::ast_util::{is_comparison_binop, binop_to_string}; use syntax::ptr::P; use syntax::codemap::Span; use std::f64::consts as f64; +use utils::span_lint; declare_lint! { pub APPROX_CONSTANT, @@ -51,7 +52,7 @@ fn check_known_consts(cx: &Context, span: Span, str: &str, module: &str) { if let Ok(value) = str.parse::() { for &(constant, name) in KNOWN_CONSTS { if within_epsilon(constant, value) { - cx.span_lint(APPROX_CONSTANT, span, &format!( + span_lint(cx, APPROX_CONSTANT, span, &format!( "Approximate value of {}::{} found, consider using it directly.", module, &name)); } } diff --git a/src/attrs.rs b/src/attrs.rs index d5a56b1547f..647e471c45e 100644 --- a/src/attrs.rs +++ b/src/attrs.rs @@ -6,7 +6,7 @@ use syntax::ast::*; use syntax::ptr::P; use syntax::codemap::{Span, ExpnInfo}; use syntax::parse::token::InternedString; -use utils::{in_macro, match_path}; +use utils::{in_macro, match_path, span_lint}; declare_lint! { pub INLINE_ALWAYS, Warn, "#[inline(always)] is usually a bad idea."} @@ -100,7 +100,7 @@ fn check_attrs(cx: &Context, info: Option<&ExpnInfo>, ident: &Ident, if values.len() != 1 || inline != &"inline" { continue; } if let MetaWord(ref always) = values[0].node { if always != &"always" { continue; } - cx.span_lint(INLINE_ALWAYS, attr.span, &format!( + span_lint(cx, INLINE_ALWAYS, attr.span, &format!( "You have declared #[inline(always)] on {}. This \ is usually a bad idea. Are you sure?", ident.as_str())); diff --git a/src/bit_mask.rs b/src/bit_mask.rs index f3f95f92d9a..5ce574007bc 100644 --- a/src/bit_mask.rs +++ b/src/bit_mask.rs @@ -6,6 +6,7 @@ use syntax::ast::*; use syntax::ast_util::{is_comparison_binop, binop_to_string}; use syntax::ptr::P; use syntax::codemap::Span; +use utils::span_lint; declare_lint! { pub BAD_BIT_MASK, @@ -95,18 +96,18 @@ fn check_bit_mask(cx: &Context, bit_op: BinOp_, cmp_op: BinOp_, BiEq | BiNe => match bit_op { BiBitAnd => if mask_value & cmp_value != mask_value { if cmp_value != 0 { - cx.span_lint(BAD_BIT_MASK, *span, &format!( + span_lint(cx, BAD_BIT_MASK, *span, &format!( "incompatible bit mask: _ & {} can never be equal to {}", mask_value, cmp_value)); } } else { if mask_value == 0 { - cx.span_lint(BAD_BIT_MASK, *span, + span_lint(cx, BAD_BIT_MASK, *span, &format!("&-masking with zero")); } }, BiBitOr => if mask_value | cmp_value != cmp_value { - cx.span_lint(BAD_BIT_MASK, *span, &format!( + span_lint(cx, BAD_BIT_MASK, *span, &format!( "incompatible bit mask: _ | {} can never be equal to {}", mask_value, cmp_value)); }, @@ -114,22 +115,22 @@ fn check_bit_mask(cx: &Context, bit_op: BinOp_, cmp_op: BinOp_, }, BiLt | BiGe => match bit_op { BiBitAnd => if mask_value < cmp_value { - cx.span_lint(BAD_BIT_MASK, *span, &format!( + span_lint(cx, BAD_BIT_MASK, *span, &format!( "incompatible bit mask: _ & {} will always be lower than {}", mask_value, cmp_value)); } else { if mask_value == 0 { - cx.span_lint(BAD_BIT_MASK, *span, + span_lint(cx, BAD_BIT_MASK, *span, &format!("&-masking with zero")); } }, BiBitOr => if mask_value >= cmp_value { - cx.span_lint(BAD_BIT_MASK, *span, &format!( + span_lint(cx, BAD_BIT_MASK, *span, &format!( "incompatible bit mask: _ | {} will never be lower than {}", mask_value, cmp_value)); } else { if mask_value < cmp_value { - cx.span_lint(INEFFECTIVE_BIT_MASK, *span, &format!( + span_lint(cx, INEFFECTIVE_BIT_MASK, *span, &format!( "ineffective bit mask: x | {} compared to {} is the same as x compared directly", mask_value, cmp_value)); } @@ -138,22 +139,22 @@ fn check_bit_mask(cx: &Context, bit_op: BinOp_, cmp_op: BinOp_, }, BiLe | BiGt => match bit_op { BiBitAnd => if mask_value <= cmp_value { - cx.span_lint(BAD_BIT_MASK, *span, &format!( + span_lint(cx, BAD_BIT_MASK, *span, &format!( "incompatible bit mask: _ & {} will never be higher than {}", mask_value, cmp_value)); } else { if mask_value == 0 { - cx.span_lint(BAD_BIT_MASK, *span, + span_lint(cx, BAD_BIT_MASK, *span, &format!("&-masking with zero")); } }, BiBitOr => if mask_value > cmp_value { - cx.span_lint(BAD_BIT_MASK, *span, &format!( + span_lint(cx, BAD_BIT_MASK, *span, &format!( "incompatible bit mask: _ | {} will always be higher than {}", mask_value, cmp_value)); } else { if mask_value < cmp_value { - cx.span_lint(INEFFECTIVE_BIT_MASK, *span, &format!( + span_lint(cx, INEFFECTIVE_BIT_MASK, *span, &format!( "ineffective bit mask: x | {} compared to {} is the same as x compared directly", mask_value, cmp_value)); } diff --git a/src/collapsible_if.rs b/src/collapsible_if.rs index 85c1b25a673..dc2d3852237 100644 --- a/src/collapsible_if.rs +++ b/src/collapsible_if.rs @@ -19,7 +19,7 @@ use syntax::ast::*; use syntax::ptr::P; use syntax::codemap::{Span, Spanned, ExpnInfo}; use syntax::print::pprust::expr_to_string; -use utils::in_macro; +use utils::{in_macro, span_lint}; declare_lint! { pub COLLAPSIBLE_IF, @@ -47,7 +47,7 @@ fn check_expr_expd(cx: &Context, e: &Expr, info: Option<&ExpnInfo>) { if let ExprIf(ref check, ref then, None) = e.node { if let Some(&Expr{ node: ExprIf(ref check_inner, _, None), ..}) = single_stmt_of_block(then) { - cx.span_lint(COLLAPSIBLE_IF, e.span, &format!( + span_lint(cx, COLLAPSIBLE_IF, e.span, &format!( "This if statement can be collapsed. Try: if {} && {}\n{:?}", check_to_string(check), check_to_string(check_inner), e)); } diff --git a/src/eq_op.rs b/src/eq_op.rs index 94a49e748e2..10dbca2cf3c 100644 --- a/src/eq_op.rs +++ b/src/eq_op.rs @@ -3,6 +3,7 @@ use syntax::ast::*; use syntax::ast_util as ast_util; use syntax::ptr::P; use syntax::codemap as code; +use utils::span_lint; declare_lint! { pub EQ_OP, @@ -21,7 +22,7 @@ impl LintPass for EqOp { fn check_expr(&mut self, cx: &Context, e: &Expr) { if let ExprBinary(ref op, ref left, ref right) = e.node { if is_cmp_or_bit(op) && is_exp_equal(left, right) { - cx.span_lint(EQ_OP, e.span, &format!( + span_lint(cx, EQ_OP, e.span, &format!( "equal expressions as operands to {}", ast_util::binop_to_string(op.node))); } diff --git a/src/eta_reduction.rs b/src/eta_reduction.rs index 17dac5930c4..18011c61831 100644 --- a/src/eta_reduction.rs +++ b/src/eta_reduction.rs @@ -3,6 +3,8 @@ use rustc::lint::{Context, LintPass, LintArray, Lint, Level}; use syntax::codemap::{Span, Spanned}; use syntax::print::pprust::expr_to_string; +use utils::span_lint; + #[allow(missing_copy_implementations)] pub struct EtaPass; @@ -48,7 +50,7 @@ impl LintPass for EtaPass { return } } - cx.span_lint(REDUNDANT_CLOSURE, expr.span, + span_lint(cx, REDUNDANT_CLOSURE, expr.span, &format!("Redundant closure found, consider using `{}` in its place", expr_to_string(caller))[..]) } diff --git a/src/identity_op.rs b/src/identity_op.rs index 9511bcdee8d..b3fb3e05447 100644 --- a/src/identity_op.rs +++ b/src/identity_op.rs @@ -7,7 +7,7 @@ use syntax::ast_util::{is_comparison_binop, binop_to_string}; use syntax::ptr::P; use syntax::codemap::Span; -use utils::snippet; +use utils::{span_lint, snippet}; declare_lint! { pub IDENTITY_OP, Warn, "Warn on identity operations, e.g. '_ + 0'"} @@ -48,7 +48,7 @@ impl LintPass for IdentityOp { fn check(cx: &Context, e: &Expr, m: i8, span: Span, arg: Span) { if have_lit(cx, e, m) { - cx.span_lint(IDENTITY_OP, span, &format!( + span_lint(cx, IDENTITY_OP, span, &format!( "The operation is ineffective. Consider reducing it to '{}'", snippet(cx, arg, ".."))); } diff --git a/src/len_zero.rs b/src/len_zero.rs index 35e11dfdcb9..7e71df2dd79 100644 --- a/src/len_zero.rs +++ b/src/len_zero.rs @@ -10,6 +10,7 @@ use rustc::middle::def::{DefTy, DefStruct, DefTrait}; use syntax::codemap::{Span, Spanned}; use syntax::ast::*; use misc::walk_ty; +use utils::span_lint; declare_lint!(pub LEN_ZERO, Warn, "Warn when .is_empty() could be used instead of checking .len()"); @@ -54,10 +55,10 @@ fn check_trait_items(cx: &Context, item: &Item, trait_items: &[P]) { } if !trait_items.iter().any(|i| is_named_self(i, "is_empty")) { - //cx.span_lint(LEN_WITHOUT_IS_EMPTY, item.span, &format!("trait {}", item.ident.as_str())); + //span_lint(cx, LEN_WITHOUT_IS_EMPTY, item.span, &format!("trait {}", item.ident.as_str())); for i in trait_items { if is_named_self(i, "len") { - cx.span_lint(LEN_WITHOUT_IS_EMPTY, i.span, + span_lint(cx, LEN_WITHOUT_IS_EMPTY, i.span, &format!("Trait '{}' has a '.len(_: &Self)' method, but no \ '.is_empty(_: &Self)' method. Consider adding one.", item.ident.as_str())); @@ -76,7 +77,7 @@ fn check_impl_items(cx: &Context, item: &Item, impl_items: &[P]) { for i in impl_items { if is_named_self(i, "len") { let s = i.span; - cx.span_lint(LEN_WITHOUT_IS_EMPTY, + span_lint(cx, LEN_WITHOUT_IS_EMPTY, Span{ lo: s.lo, hi: s.lo, expn_id: s.expn_id }, &format!("Item '{}' has a '.len(_: &Self)' method, but no \ '.is_empty(_: &Self)' method. Consider adding one.", @@ -107,7 +108,7 @@ fn check_len_zero(cx: &Context, span: Span, method: &SpannedIdent, if let &Spanned{node: LitInt(0, _), ..} = lit { if method.node.as_str() == "len" && args.len() == 1 && has_is_empty(cx, &*args[0]) { - cx.span_lint(LEN_ZERO, span, &format!( + span_lint(cx, LEN_ZERO, span, &format!( "Consider replacing the len comparison with '{}_.is_empty()'", empty)) } diff --git a/src/misc.rs b/src/misc.rs index da2df2cc820..a140671b1bb 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -8,7 +8,7 @@ use rustc::middle::ty; use syntax::codemap::{Span, Spanned}; use types::span_note_and_lint; -use utils::{match_path, snippet}; +use utils::{match_path, snippet, span_lint}; pub fn walk_ty<'t>(ty: ty::Ty<'t>) -> ty::Ty<'t> { match ty.sty { @@ -72,7 +72,7 @@ impl LintPass for StrToStringPass { ast::ExprMethodCall(ref method, _, ref args) if method.node.as_str() == "to_string" && is_str(cx, &*args[0]) => { - cx.span_lint(STR_TO_STRING, expr.span, "str.to_owned() is faster"); + span_lint(cx, STR_TO_STRING, expr.span, "str.to_owned() is faster"); }, _ => () } @@ -100,7 +100,7 @@ impl LintPass for TopLevelRefPass { fn check_fn(&mut self, cx: &Context, _: FnKind, decl: &FnDecl, _: &Block, _: Span, _: NodeId) { for ref arg in decl.inputs.iter() { if let PatIdent(BindByRef(_), _, _) = arg.pat.node { - cx.span_lint( + span_lint(cx, TOPLEVEL_REF_ARG, arg.pat.span, "`ref` directly on a function argument is ignored. Have you considered using a reference type instead?" @@ -136,7 +136,7 @@ impl LintPass for CmpNan { fn check_nan(cx: &Context, path: &Path, span: Span) { path.segments.last().map(|seg| if seg.identifier.as_str() == "NAN" { - cx.span_lint(CMP_NAN, span, "Doomed comparison with NAN, use std::{f32,f64}::is_nan instead"); + span_lint(cx, CMP_NAN, span, "Doomed comparison with NAN, use std::{f32,f64}::is_nan instead"); }); } @@ -155,7 +155,7 @@ impl LintPass for FloatCmp { if let ExprBinary(ref cmp, ref left, ref right) = expr.node { let op = cmp.node; if (op == BiEq || op == BiNe) && (is_float(cx, left) || is_float(cx, right)) { - cx.span_lint(FLOAT_CMP, expr.span, &format!( + span_lint(cx, FLOAT_CMP, expr.span, &format!( "{}-Comparison of f32 or f64 detected. You may want to change this to 'abs({} - {}) < epsilon' for some suitable value of epsilon", binop_to_string(op), snippet(cx, left.span, ".."), snippet(cx, right.span, ".."))); @@ -186,7 +186,7 @@ impl LintPass for Precedence { fn check_expr(&mut self, cx: &Context, expr: &Expr) { if let ExprBinary(Spanned { node: op, ..}, ref left, ref right) = expr.node { if is_bit_op(op) && (is_arith_expr(left) || is_arith_expr(right)) { - cx.span_lint(PRECEDENCE, expr.span, + span_lint(cx, PRECEDENCE, expr.span, "Operator precedence can trip the unwary. Consider adding parenthesis to the subexpression."); } } @@ -241,7 +241,7 @@ fn check_to_owned(cx: &Context, expr: &Expr, other_span: Span) { let name = ident.as_str(); if name == "to_string" || name == "to_owned" && is_str_arg(cx, args) { - cx.span_lint(CMP_OWNED, expr.span, &format!( + span_lint(cx, CMP_OWNED, expr.span, &format!( "this creates an owned instance just for comparison. \ Consider using {}.as_slice() to compare without allocation", snippet(cx, other_span, ".."))) @@ -251,7 +251,7 @@ fn check_to_owned(cx: &Context, expr: &Expr, other_span: Span) { if let &ExprPath(None, ref path) = &path.node { if match_path(path, &["String", "from_str"]) || match_path(path, &["String", "from"]) { - cx.span_lint(CMP_OWNED, expr.span, &format!( + span_lint(cx, CMP_OWNED, expr.span, &format!( "this creates an owned instance just for comparison. \ Consider using {}.as_slice() to compare without allocation", snippet(cx, other_span, ".."))) diff --git a/src/mut_mut.rs b/src/mut_mut.rs index cfa040ddb04..73e97ae31f4 100644 --- a/src/mut_mut.rs +++ b/src/mut_mut.rs @@ -3,7 +3,7 @@ use syntax::ast::*; use rustc::lint::{Context, LintPass, LintArray, Lint}; use rustc::middle::ty::{TypeVariants, TypeAndMut, TyRef}; use syntax::codemap::{BytePos, ExpnInfo, Span}; -use utils::in_macro; +use utils::{in_macro, span_lint}; declare_lint!(pub MUT_MUT, Warn, "Warn on usage of double-mut refs, e.g. '&mut &mut ...'"); @@ -22,7 +22,7 @@ impl LintPass for MutMut { } fn check_ty(&mut self, cx: &Context, ty: &Ty) { - unwrap_mut(ty).and_then(unwrap_mut).map_or((), |_| cx.span_lint(MUT_MUT, + unwrap_mut(ty).and_then(unwrap_mut).map_or((), |_| span_lint(cx, MUT_MUT, ty.span, "Generally you want to avoid &mut &mut _ if possible.")) } } @@ -39,12 +39,12 @@ fn check_expr_expd(cx: &Context, expr: &Expr, info: Option<&ExpnInfo>) { unwrap_addr(expr).map_or((), |e| { unwrap_addr(e).map(|_| { - cx.span_lint(MUT_MUT, expr.span, + span_lint(cx, MUT_MUT, expr.span, "Generally you want to avoid &mut &mut _ if possible.") }).unwrap_or_else(|| { if let TyRef(_, TypeAndMut{ty: _, mutbl: MutMutable}) = cx.tcx.expr_ty(e).sty { - cx.span_lint(MUT_MUT, expr.span, + span_lint(cx, MUT_MUT, expr.span, "This expression mutably borrows a mutable reference. \ Consider reborrowing") } diff --git a/src/needless_bool.rs b/src/needless_bool.rs index 9b52771d8af..35d921e8fa1 100644 --- a/src/needless_bool.rs +++ b/src/needless_bool.rs @@ -10,7 +10,7 @@ use syntax::ast::*; use syntax::ast_util::{is_comparison_binop, binop_to_string}; use syntax::ptr::P; use syntax::codemap::Span; -use utils::de_p; +use utils::{de_p, span_lint}; declare_lint! { pub NEEDLESS_BOOL, @@ -30,16 +30,16 @@ impl LintPass for NeedlessBool { if let ExprIf(_, ref then_block, Option::Some(ref else_expr)) = e.node { match (fetch_bool_block(then_block), fetch_bool_expr(else_expr)) { (Option::Some(true), Option::Some(true)) => { - cx.span_lint(NEEDLESS_BOOL, e.span, + span_lint(cx, NEEDLESS_BOOL, e.span, "your if-then-else expression will always return true"); }, (Option::Some(true), Option::Some(false)) => { - cx.span_lint(NEEDLESS_BOOL, e.span, + span_lint(cx, NEEDLESS_BOOL, e.span, "you can reduce your if-statement to its predicate"); }, (Option::Some(false), Option::Some(true)) => { - cx.span_lint(NEEDLESS_BOOL, e.span, + span_lint(cx, NEEDLESS_BOOL, e.span, "you can reduce your if-statement to '!' + your predicate"); }, (Option::Some(false), Option::Some(false)) => { - cx.span_lint(NEEDLESS_BOOL, e.span, + span_lint(cx, NEEDLESS_BOOL, e.span, "your if-then-else expression will always return false"); }, _ => () } diff --git a/src/ptr_arg.rs b/src/ptr_arg.rs index 64c3c84c7b6..dad4e48c832 100644 --- a/src/ptr_arg.rs +++ b/src/ptr_arg.rs @@ -11,6 +11,7 @@ use syntax::ast_util::{is_comparison_binop, binop_to_string}; use syntax::ptr::P; use syntax::codemap::Span; use types::match_ty_unwrap; +use utils::span_lint; declare_lint! { pub PTR_ARG, @@ -58,10 +59,10 @@ fn check_fn(cx: &Context, decl: &FnDecl) { fn check_ptr_subtype(cx: &Context, span: Span, ty: &Ty) { match_ty_unwrap(ty, &["Vec"]).map_or_else(|| match_ty_unwrap(ty, &["String"]).map_or((), |_| { - cx.span_lint(PTR_ARG, span, + span_lint(cx, PTR_ARG, span, "Writing '&String' instead of '&str' involves a new Object \ where a slices will do. Consider changing the type to &str") - }), |_| cx.span_lint(PTR_ARG, span, "Writing '&Vec<_>' instead of \ + }), |_| span_lint(cx, PTR_ARG, span, "Writing '&Vec<_>' instead of \ '&[_]' involves one more reference and cannot be used with \ non-vec-based slices. Consider changing the type to &[...]") ) diff --git a/src/types.rs b/src/types.rs index f0c91dc18b5..8c46fed2c1f 100644 --- a/src/types.rs +++ b/src/types.rs @@ -6,6 +6,8 @@ use syntax::ast::*; use rustc::lint::{Context, LintPass, LintArray, Lint, Level}; use syntax::codemap::Span; +use utils::span_lint; + /// Handles all the linting of funky types #[allow(missing_copy_implementations)] pub struct TypePass; @@ -40,7 +42,7 @@ pub fn match_ty_unwrap<'a>(ty: &'a Ty, segments: &[&str]) -> Option<&'a [P]> /// Lets me span a note only if the lint is shown pub fn span_note_and_lint(cx: &Context, lint: &'static Lint, span: Span, msg: &str, note: &str) { - cx.span_lint(lint, span, msg); + span_lint(cx, lint, span, msg); if cx.current_level(lint) != Level::Allow { cx.sess().span_note(span, note); } diff --git a/src/unicode.rs b/src/unicode.rs index 3ffcf699c26..9b908c3f94f 100644 --- a/src/unicode.rs +++ b/src/unicode.rs @@ -1,6 +1,7 @@ use rustc::lint::*; use syntax::ast::*; use syntax::codemap::{BytePos, Span}; +use utils::span_lint; declare_lint!{ pub ZERO_WIDTH_SPACE, Deny, "Zero-width space is confusing" } @@ -36,7 +37,7 @@ fn check_str(cx: &Context, string: &str, span: Span) { fn lint_zero_width(cx: &Context, span: Span, start: Option) { start.map(|index| { - cx.span_lint(ZERO_WIDTH_SPACE, Span { + span_lint(cx, ZERO_WIDTH_SPACE, Span { lo: span.lo + BytePos(index as u32), hi: span.lo + BytePos(index as u32), expn_id: span.expn_id, diff --git a/src/utils.rs b/src/utils.rs index 065c20717a1..e3cafc700a5 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,4 @@ -use rustc::lint::Context; +use rustc::lint::{Context, Lint}; use syntax::ast::{DefId, Name, Path}; use syntax::codemap::{ExpnInfo, Span}; use syntax::ptr::P; @@ -52,3 +52,16 @@ pub fn snippet<'a>(cx: &Context, span: Span, default: &'a str) -> Cow<'a, str> { /// dereference a P and return a ref on the result pub fn de_p(p: &P) -> &T { &*p } + +#[cfg(not(feature="structured_logging"))] +pub fn span_lint(cx: &Context, lint: &'static Lint, sp: Span, msg: &str) { + cx.span_lint(lint, sp, msg); +} + +#[cfg(feature="structured_logging")] +pub fn span_lint(cx: &Context, lint: &'static Lint, sp: Span, msg: &str) { + // lint.name / lint.desc is can give details of the lint + // cx.sess().codemap() has all these nice functions for line/column/snippet details + // http://doc.rust-lang.org/syntax/codemap/struct.CodeMap.html#method.span_to_string + cx.span_lint(lint, sp, msg); +}