Add a lint for starts_with
This commit is contained in:
parent
fdcd9743ee
commit
91ff1db5bc
5 changed files with 105 additions and 32 deletions
|
|
@ -191,6 +191,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
|
|||
matches::MATCH_OVERLAPPING_ARM,
|
||||
matches::MATCH_REF_PATS,
|
||||
matches::SINGLE_MATCH,
|
||||
methods::CHARS_NEXT_CMP,
|
||||
methods::FILTER_NEXT,
|
||||
methods::OK_EXPECT,
|
||||
methods::OPTION_MAP_UNWRAP_OR,
|
||||
|
|
|
|||
112
src/methods.rs
112
src/methods.rs
|
|
@ -7,11 +7,14 @@ use std::borrow::Cow;
|
|||
use syntax::ptr::P;
|
||||
use syntax::codemap::Span;
|
||||
|
||||
use utils::{snippet, span_lint, span_note_and_lint, match_path, match_type, method_chain_args, match_trait_method,
|
||||
walk_ptrs_ty_depth, walk_ptrs_ty, get_trait_def_id, implements_trait};
|
||||
use utils::{
|
||||
BTREEMAP_ENTRY_PATH, DEFAULT_TRAIT_PATH, HASHMAP_ENTRY_PATH, OPTION_PATH,
|
||||
RESULT_PATH, STRING_PATH
|
||||
snippet, span_lint, span_note_and_lint, match_path, match_type, method_chain_args,
|
||||
match_trait_method, walk_ptrs_ty_depth, walk_ptrs_ty, get_trait_def_id, implements_trait,
|
||||
span_lint_and_then
|
||||
};
|
||||
use utils::{
|
||||
BTREEMAP_ENTRY_PATH, DEFAULT_TRAIT_PATH, HASHMAP_ENTRY_PATH, OPTION_PATH, RESULT_PATH,
|
||||
STRING_PATH
|
||||
};
|
||||
use utils::MethodArgs;
|
||||
use rustc::middle::cstore::CrateStore;
|
||||
|
|
@ -176,6 +179,17 @@ declare_lint!(pub SEARCH_IS_SOME, Warn,
|
|||
"using an iterator search followed by `is_some()`, which is more succinctly \
|
||||
expressed as a call to `any()`");
|
||||
|
||||
/// **What it does:** This lint `Warn`s on using `.chars().next()` on a `str` to check if it
|
||||
/// starts with a given char.
|
||||
///
|
||||
/// **Why is this bad?** Readability, this can be written more concisely as `_.starts_with(_)`.
|
||||
///
|
||||
/// **Known problems:** None.
|
||||
///
|
||||
/// **Example:** `name.chars().next() == Some('_')`
|
||||
declare_lint!(pub CHARS_NEXT_CMP, Warn,
|
||||
"using `.chars().next()` to check if a string starts with a char");
|
||||
|
||||
/// **What it does:** This lint checks for calls to `.or(foo(..))`, `.unwrap_or(foo(..))`, etc., and
|
||||
/// suggests to use `or_else`, `unwrap_or_else`, etc., or `unwrap_or_default` instead.
|
||||
///
|
||||
|
|
@ -210,35 +224,44 @@ impl LintPass for MethodsPass {
|
|||
OK_EXPECT,
|
||||
OPTION_MAP_UNWRAP_OR,
|
||||
OPTION_MAP_UNWRAP_OR_ELSE,
|
||||
OR_FUN_CALL)
|
||||
OR_FUN_CALL,
|
||||
CHARS_NEXT_CMP)
|
||||
}
|
||||
}
|
||||
|
||||
impl LateLintPass for MethodsPass {
|
||||
fn check_expr(&mut self, cx: &LateContext, expr: &Expr) {
|
||||
if let ExprMethodCall(name, _, ref args) = expr.node {
|
||||
// Chain calls
|
||||
if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
|
||||
lint_unwrap(cx, expr, arglists[0]);
|
||||
} else if let Some(arglists) = method_chain_args(expr, &["to_string"]) {
|
||||
lint_to_string(cx, expr, arglists[0]);
|
||||
} else if let Some(arglists) = method_chain_args(expr, &["ok", "expect"]) {
|
||||
lint_ok_expect(cx, expr, arglists[0]);
|
||||
} else if let Some(arglists) = method_chain_args(expr, &["map", "unwrap_or"]) {
|
||||
lint_map_unwrap_or(cx, expr, arglists[0], arglists[1]);
|
||||
} else if let Some(arglists) = method_chain_args(expr, &["map", "unwrap_or_else"]) {
|
||||
lint_map_unwrap_or_else(cx, expr, arglists[0], arglists[1]);
|
||||
} else if let Some(arglists) = method_chain_args(expr, &["filter", "next"]) {
|
||||
lint_filter_next(cx, expr, arglists[0]);
|
||||
} else if let Some(arglists) = method_chain_args(expr, &["find", "is_some"]) {
|
||||
lint_search_is_some(cx, expr, "find", arglists[0], arglists[1]);
|
||||
} else if let Some(arglists) = method_chain_args(expr, &["position", "is_some"]) {
|
||||
lint_search_is_some(cx, expr, "position", arglists[0], arglists[1]);
|
||||
} else if let Some(arglists) = method_chain_args(expr, &["rposition", "is_some"]) {
|
||||
lint_search_is_some(cx, expr, "rposition", arglists[0], arglists[1]);
|
||||
}
|
||||
match expr.node {
|
||||
ExprMethodCall(name, _, ref args) => {
|
||||
// Chain calls
|
||||
if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
|
||||
lint_unwrap(cx, expr, arglists[0]);
|
||||
} else if let Some(arglists) = method_chain_args(expr, &["to_string"]) {
|
||||
lint_to_string(cx, expr, arglists[0]);
|
||||
} else if let Some(arglists) = method_chain_args(expr, &["ok", "expect"]) {
|
||||
lint_ok_expect(cx, expr, arglists[0]);
|
||||
} else if let Some(arglists) = method_chain_args(expr, &["map", "unwrap_or"]) {
|
||||
lint_map_unwrap_or(cx, expr, arglists[0], arglists[1]);
|
||||
} else if let Some(arglists) = method_chain_args(expr, &["map", "unwrap_or_else"]) {
|
||||
lint_map_unwrap_or_else(cx, expr, arglists[0], arglists[1]);
|
||||
} else if let Some(arglists) = method_chain_args(expr, &["filter", "next"]) {
|
||||
lint_filter_next(cx, expr, arglists[0]);
|
||||
} else if let Some(arglists) = method_chain_args(expr, &["find", "is_some"]) {
|
||||
lint_search_is_some(cx, expr, "find", arglists[0], arglists[1]);
|
||||
} else if let Some(arglists) = method_chain_args(expr, &["position", "is_some"]) {
|
||||
lint_search_is_some(cx, expr, "position", arglists[0], arglists[1]);
|
||||
} else if let Some(arglists) = method_chain_args(expr, &["rposition", "is_some"]) {
|
||||
lint_search_is_some(cx, expr, "rposition", arglists[0], arglists[1]);
|
||||
}
|
||||
|
||||
lint_or_fun_call(cx, expr, &name.node.as_str(), &args);
|
||||
lint_or_fun_call(cx, expr, &name.node.as_str(), &args);
|
||||
}
|
||||
ExprBinary(op, ref lhs, ref rhs) if op.node == BiEq || op.node == BiNe => {
|
||||
if !lint_chars_next(cx, expr, lhs, rhs, op.node == BiEq) {
|
||||
lint_chars_next(cx, expr, rhs, lhs, op.node == BiEq);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -570,6 +593,41 @@ fn lint_search_is_some(cx: &LateContext, expr: &Expr, search_method: &str, searc
|
|||
}
|
||||
}
|
||||
|
||||
/// Checks for the `CHARS_NEXT_CMP` lint.
|
||||
fn lint_chars_next(cx: &LateContext, expr: &Expr, chain: &Expr, other: &Expr, eq: bool) -> bool {
|
||||
if_let_chain! {[
|
||||
let Some(args) = method_chain_args(chain, &["chars", "next"]),
|
||||
let ExprCall(ref fun, ref arg_char) = other.node,
|
||||
arg_char.len() == 1,
|
||||
let ExprPath(None, ref path) = fun.node,
|
||||
path.segments.len() == 1 && path.segments[0].identifier.name.as_str() == "Some"
|
||||
], {
|
||||
let self_ty = walk_ptrs_ty(cx.tcx.expr_ty_adjusted(&args[0][0]));
|
||||
|
||||
if self_ty.sty != ty::TyStr {
|
||||
return false;
|
||||
}
|
||||
|
||||
span_lint_and_then(cx,
|
||||
CHARS_NEXT_CMP,
|
||||
expr.span,
|
||||
"you should use the `starts_with` method",
|
||||
|db| {
|
||||
let sugg = format!("{}{}.starts_with({})",
|
||||
if eq { "" } else { "!" },
|
||||
snippet(cx, args[0][0].span, "_"),
|
||||
snippet(cx, arg_char[0].span, "_")
|
||||
);
|
||||
|
||||
db.span_suggestion(expr.span, "like this", sugg);
|
||||
});
|
||||
|
||||
return true;
|
||||
}}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
// Given a `Result<T, E>` type, return its error type (`E`)
|
||||
fn get_error_type<'a>(cx: &LateContext, ty: ty::Ty<'a>) -> Option<ty::Ty<'a>> {
|
||||
if !match_type(cx, ty, &RESULT_PATH) {
|
||||
|
|
|
|||
|
|
@ -389,13 +389,14 @@ impl LateLintPass for UsedUnderscoreBinding {
|
|||
.last()
|
||||
.expect("path should always have at least one segment")
|
||||
.identifier;
|
||||
ident.name.as_str().chars().next() == Some('_') && // starts with '_'
|
||||
ident.name.as_str().chars().skip(1).next() != Some('_') && // doesn't start with "__"
|
||||
ident.name != ident.unhygienic_name && is_used(cx, expr) // not in bang macro
|
||||
ident.name.as_str().starts_with('_') &&
|
||||
!ident.name.as_str().starts_with("__") &&
|
||||
ident.name != ident.unhygienic_name &&
|
||||
is_used(cx, expr) // not in bang macro
|
||||
}
|
||||
ExprField(_, spanned) => {
|
||||
let name = spanned.node.as_str();
|
||||
name.chars().next() == Some('_') && name.chars().skip(1).next() != Some('_')
|
||||
name.starts_with('_') && !name.starts_with("__")
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue