2015-08-16 01:54:43 -05:00
|
|
|
//! checks for attributes
|
2015-05-30 08:10:19 -05:00
|
|
|
|
2018-11-27 14:14:15 -06:00
|
|
|
use crate::utils::{
|
2020-11-17 14:16:15 -06:00
|
|
|
first_line_of_span, is_present_in_source, match_panic_def_id, snippet_opt, span_lint, span_lint_and_help,
|
2020-07-14 07:59:59 -05:00
|
|
|
span_lint_and_sugg, span_lint_and_then, without_block_comments,
|
2018-11-27 14:14:15 -06:00
|
|
|
};
|
|
|
|
use if_chain::if_chain;
|
2020-08-28 09:10:16 -05:00
|
|
|
use rustc_ast::{AttrKind, AttrStyle, Attribute, Lit, LitKind, MetaItemKind, NestedMetaItem};
|
2018-12-29 09:04:45 -06:00
|
|
|
use rustc_errors::Applicability;
|
2020-02-21 02:39:38 -06:00
|
|
|
use rustc_hir::{
|
2020-03-16 10:00:16 -05:00
|
|
|
Block, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, StmtKind, TraitFn, TraitItem, TraitItemKind,
|
2020-02-21 02:39:38 -06:00
|
|
|
};
|
2020-01-12 00:08:41 -06:00
|
|
|
use rustc_lint::{CheckLintNameResult, EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
|
2020-03-30 04:02:14 -05:00
|
|
|
use rustc_middle::lint::in_external_macro;
|
|
|
|
use rustc_middle::ty;
|
2020-01-11 05:37:08 -06:00
|
|
|
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
2020-12-06 08:01:03 -06:00
|
|
|
use rustc_span::lev_distance::find_best_match_for_name;
|
2020-01-04 04:00:00 -06:00
|
|
|
use rustc_span::source_map::Span;
|
2020-11-05 07:29:48 -06:00
|
|
|
use rustc_span::sym;
|
2020-07-14 07:59:59 -05:00
|
|
|
use rustc_span::symbol::{Symbol, SymbolStr};
|
2018-11-27 14:14:15 -06:00
|
|
|
use semver::Version;
|
2015-05-30 08:10:19 -05:00
|
|
|
|
2020-04-25 13:55:46 -05:00
|
|
|
static UNIX_SYSTEMS: &[&str] = &[
|
2020-04-22 16:01:25 -05:00
|
|
|
"android",
|
|
|
|
"dragonfly",
|
|
|
|
"emscripten",
|
|
|
|
"freebsd",
|
|
|
|
"fuchsia",
|
|
|
|
"haiku",
|
|
|
|
"illumos",
|
|
|
|
"ios",
|
|
|
|
"l4re",
|
|
|
|
"linux",
|
|
|
|
"macos",
|
|
|
|
"netbsd",
|
|
|
|
"openbsd",
|
|
|
|
"redox",
|
|
|
|
"solaris",
|
|
|
|
"vxworks",
|
|
|
|
];
|
|
|
|
|
2020-04-25 13:55:46 -05:00
|
|
|
// NOTE: windows is excluded from the list because it's also a valid target family.
|
2020-10-27 08:10:31 -05:00
|
|
|
static NON_UNIX_SYSTEMS: &[&str] = &["hermit", "none", "wasi"];
|
2020-04-25 13:55:46 -05:00
|
|
|
|
2018-03-28 08:24:26 -05:00
|
|
|
declare_clippy_lint! {
|
2019-03-05 10:50:33 -06:00
|
|
|
/// **What it does:** Checks for items annotated with `#[inline(always)]`,
|
|
|
|
/// unless the annotated function is empty or simply panics.
|
|
|
|
///
|
|
|
|
/// **Why is this bad?** While there are valid uses of this annotation (and once
|
|
|
|
/// you know when to use it, by all means `allow` this lint), it's a common
|
|
|
|
/// newbie-mistake to pepper one's code with it.
|
|
|
|
///
|
|
|
|
/// As a rule of thumb, before slapping `#[inline(always)]` on a function,
|
|
|
|
/// measure if that additional function call really affects your runtime profile
|
|
|
|
/// sufficiently to make up for the increase in compile time.
|
|
|
|
///
|
|
|
|
/// **Known problems:** False positives, big time. This lint is meant to be
|
|
|
|
/// deactivated by everyone doing serious performance work. This means having
|
|
|
|
/// done the measurement.
|
|
|
|
///
|
|
|
|
/// **Example:**
|
2019-03-05 16:23:50 -06:00
|
|
|
/// ```ignore
|
2019-03-05 10:50:33 -06:00
|
|
|
/// #[inline(always)]
|
|
|
|
/// fn not_quite_hot_code(..) { ... }
|
|
|
|
/// ```
|
2016-08-06 03:18:36 -05:00
|
|
|
pub INLINE_ALWAYS,
|
2018-03-28 08:24:26 -05:00
|
|
|
pedantic,
|
2016-08-06 03:18:36 -05:00
|
|
|
"use of `#[inline(always)]`"
|
2016-02-05 17:13:29 -06:00
|
|
|
}
|
2015-05-30 08:10:19 -05:00
|
|
|
|
2018-03-28 08:24:26 -05:00
|
|
|
declare_clippy_lint! {
|
2019-03-05 10:50:33 -06:00
|
|
|
/// **What it does:** Checks for `extern crate` and `use` items annotated with
|
|
|
|
/// lint attributes.
|
|
|
|
///
|
2020-09-10 10:47:07 -05:00
|
|
|
/// This lint permits `#[allow(unused_imports)]`, `#[allow(deprecated)]`,
|
|
|
|
/// `#[allow(unreachable_pub)]`, `#[allow(clippy::wildcard_imports)]` and
|
|
|
|
/// `#[allow(clippy::enum_glob_use)]` on `use` items and `#[allow(unused_imports)]` on
|
2019-05-17 05:42:43 -05:00
|
|
|
/// `extern crate` items with a `#[macro_use]` attribute.
|
2019-03-05 10:50:33 -06:00
|
|
|
///
|
|
|
|
/// **Why is this bad?** Lint attributes have no effect on crate imports. Most
|
|
|
|
/// likely a `!` was forgotten.
|
|
|
|
///
|
|
|
|
/// **Known problems:** None.
|
|
|
|
///
|
|
|
|
/// **Example:**
|
2019-03-05 16:23:50 -06:00
|
|
|
/// ```ignore
|
2019-03-05 10:50:33 -06:00
|
|
|
/// // Bad
|
|
|
|
/// #[deny(dead_code)]
|
|
|
|
/// extern crate foo;
|
|
|
|
/// #[forbid(dead_code)]
|
|
|
|
/// use foo::bar;
|
|
|
|
///
|
|
|
|
/// // Ok
|
|
|
|
/// #[allow(unused_imports)]
|
|
|
|
/// use foo::baz;
|
|
|
|
/// #[allow(unused_imports)]
|
|
|
|
/// #[macro_use]
|
|
|
|
/// extern crate baz;
|
|
|
|
/// ```
|
2016-08-17 04:36:04 -05:00
|
|
|
pub USELESS_ATTRIBUTE,
|
2018-03-28 08:24:26 -05:00
|
|
|
correctness,
|
2016-08-17 04:36:04 -05:00
|
|
|
"use of lint attributes on `extern crate` items"
|
|
|
|
}
|
|
|
|
|
2018-03-28 08:24:26 -05:00
|
|
|
declare_clippy_lint! {
|
2019-03-05 10:50:33 -06:00
|
|
|
/// **What it does:** Checks for `#[deprecated]` annotations with a `since`
|
|
|
|
/// field that is not a valid semantic version.
|
|
|
|
///
|
|
|
|
/// **Why is this bad?** For checking the version of the deprecation, it must be
|
|
|
|
/// a valid semver. Failing that, the contained information is useless.
|
|
|
|
///
|
|
|
|
/// **Known problems:** None.
|
|
|
|
///
|
|
|
|
/// **Example:**
|
|
|
|
/// ```rust
|
|
|
|
/// #[deprecated(since = "forever")]
|
2019-03-05 16:23:50 -06:00
|
|
|
/// fn something_else() { /* ... */ }
|
2019-03-05 10:50:33 -06:00
|
|
|
/// ```
|
2016-08-06 03:18:36 -05:00
|
|
|
pub DEPRECATED_SEMVER,
|
2018-03-28 08:24:26 -05:00
|
|
|
correctness,
|
2016-08-06 03:18:36 -05:00
|
|
|
"use of `#[deprecated(since = \"x\")]` where x is not semver"
|
2016-02-05 17:13:29 -06:00
|
|
|
}
|
2015-05-30 08:10:19 -05:00
|
|
|
|
2018-03-28 08:24:26 -05:00
|
|
|
declare_clippy_lint! {
|
2019-03-05 10:50:33 -06:00
|
|
|
/// **What it does:** Checks for empty lines after outer attributes
|
|
|
|
///
|
|
|
|
/// **Why is this bad?**
|
|
|
|
/// Most likely the attribute was meant to be an inner attribute using a '!'.
|
|
|
|
/// If it was meant to be an outer attribute, then the following item
|
|
|
|
/// should not be separated by empty lines.
|
|
|
|
///
|
|
|
|
/// **Known problems:** Can cause false positives.
|
|
|
|
///
|
|
|
|
/// From the clippy side it's difficult to detect empty lines between an attributes and the
|
|
|
|
/// following item because empty lines and comments are not part of the AST. The parsing
|
|
|
|
/// currently works for basic cases but is not perfect.
|
|
|
|
///
|
|
|
|
/// **Example:**
|
|
|
|
/// ```rust
|
|
|
|
/// // Good (as inner attribute)
|
2020-10-09 05:45:29 -05:00
|
|
|
/// #![allow(dead_code)]
|
2019-03-05 10:50:33 -06:00
|
|
|
///
|
2019-08-03 14:01:23 -05:00
|
|
|
/// fn this_is_fine() { }
|
|
|
|
///
|
|
|
|
/// // Bad
|
2020-10-09 05:45:29 -05:00
|
|
|
/// #[allow(dead_code)]
|
2019-08-03 14:01:23 -05:00
|
|
|
///
|
|
|
|
/// fn not_quite_good_code() { }
|
2019-03-05 10:50:33 -06:00
|
|
|
///
|
|
|
|
/// // Good (as outer attribute)
|
2020-10-09 05:45:29 -05:00
|
|
|
/// #[allow(dead_code)]
|
2019-08-03 14:01:23 -05:00
|
|
|
/// fn this_is_fine_too() { }
|
2019-03-05 10:50:33 -06:00
|
|
|
/// ```
|
2018-01-08 17:22:42 -06:00
|
|
|
pub EMPTY_LINE_AFTER_OUTER_ATTR,
|
2018-03-30 04:28:37 -05:00
|
|
|
nursery,
|
2018-01-08 17:22:42 -06:00
|
|
|
"empty line after outer attribute"
|
|
|
|
}
|
|
|
|
|
2018-09-10 10:09:15 -05:00
|
|
|
declare_clippy_lint! {
|
2019-03-05 10:50:33 -06:00
|
|
|
/// **What it does:** Checks for `allow`/`warn`/`deny`/`forbid` attributes with scoped clippy
|
2019-06-12 13:07:10 -05:00
|
|
|
/// lints and if those lints exist in clippy. If there is an uppercase letter in the lint name
|
2019-03-05 10:50:33 -06:00
|
|
|
/// (not the tool name) and a lowercase version of this lint exists, it will suggest to lowercase
|
|
|
|
/// the lint name.
|
|
|
|
///
|
|
|
|
/// **Why is this bad?** A lint attribute with a mistyped lint name won't have an effect.
|
|
|
|
///
|
|
|
|
/// **Known problems:** None.
|
|
|
|
///
|
|
|
|
/// **Example:**
|
|
|
|
/// Bad:
|
|
|
|
/// ```rust
|
|
|
|
/// #![warn(if_not_els)]
|
|
|
|
/// #![deny(clippy::All)]
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// Good:
|
|
|
|
/// ```rust
|
|
|
|
/// #![warn(if_not_else)]
|
|
|
|
/// #![deny(clippy::all)]
|
|
|
|
/// ```
|
2018-09-10 10:09:15 -05:00
|
|
|
pub UNKNOWN_CLIPPY_LINTS,
|
|
|
|
style,
|
|
|
|
"unknown_lints for scoped Clippy lints"
|
|
|
|
}
|
|
|
|
|
2020-07-14 07:59:59 -05:00
|
|
|
declare_clippy_lint! {
|
|
|
|
/// **What it does:** Checks for `warn`/`deny`/`forbid` attributes targeting the whole clippy::restriction category.
|
|
|
|
///
|
|
|
|
/// **Why is this bad?** Restriction lints sometimes are in contrast with other lints or even go against idiomatic rust.
|
|
|
|
/// These lints should only be enabled on a lint-by-lint basis and with careful consideration.
|
|
|
|
///
|
|
|
|
/// **Known problems:** None.
|
|
|
|
///
|
|
|
|
/// **Example:**
|
|
|
|
/// Bad:
|
|
|
|
/// ```rust
|
|
|
|
/// #![deny(clippy::restriction)]
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// Good:
|
|
|
|
/// ```rust
|
|
|
|
/// #![deny(clippy::as_conversions)]
|
|
|
|
/// ```
|
|
|
|
pub BLANKET_CLIPPY_RESTRICTION_LINTS,
|
|
|
|
style,
|
|
|
|
"enabling the complete restriction group"
|
|
|
|
}
|
|
|
|
|
2018-09-03 08:34:12 -05:00
|
|
|
declare_clippy_lint! {
|
2019-03-05 10:50:33 -06:00
|
|
|
/// **What it does:** Checks for `#[cfg_attr(rustfmt, rustfmt_skip)]` and suggests to replace it
|
|
|
|
/// with `#[rustfmt::skip]`.
|
|
|
|
///
|
|
|
|
/// **Why is this bad?** Since tool_attributes ([rust-lang/rust#44690](https://github.com/rust-lang/rust/issues/44690))
|
|
|
|
/// are stable now, they should be used instead of the old `cfg_attr(rustfmt)` attributes.
|
|
|
|
///
|
|
|
|
/// **Known problems:** This lint doesn't detect crate level inner attributes, because they get
|
|
|
|
/// processed before the PreExpansionPass lints get executed. See
|
|
|
|
/// [#3123](https://github.com/rust-lang/rust-clippy/pull/3123#issuecomment-422321765)
|
|
|
|
///
|
|
|
|
/// **Example:**
|
|
|
|
///
|
|
|
|
/// Bad:
|
|
|
|
/// ```rust
|
|
|
|
/// #[cfg_attr(rustfmt, rustfmt_skip)]
|
|
|
|
/// fn main() { }
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// Good:
|
|
|
|
/// ```rust
|
|
|
|
/// #[rustfmt::skip]
|
|
|
|
/// fn main() { }
|
|
|
|
/// ```
|
2018-09-03 08:34:12 -05:00
|
|
|
pub DEPRECATED_CFG_ATTR,
|
|
|
|
complexity,
|
2020-01-06 09:44:52 -06:00
|
|
|
"usage of `cfg_attr(rustfmt)` instead of tool attributes"
|
2018-09-03 08:34:12 -05:00
|
|
|
}
|
|
|
|
|
2020-04-22 16:01:25 -05:00
|
|
|
declare_clippy_lint! {
|
|
|
|
/// **What it does:** Checks for cfg attributes having operating systems used in target family position.
|
|
|
|
///
|
|
|
|
/// **Why is this bad?** The configuration option will not be recognised and the related item will not be included
|
|
|
|
/// by the conditional compilation engine.
|
|
|
|
///
|
|
|
|
/// **Known problems:** None.
|
|
|
|
///
|
|
|
|
/// **Example:**
|
|
|
|
///
|
|
|
|
/// Bad:
|
|
|
|
/// ```rust
|
|
|
|
/// #[cfg(linux)]
|
|
|
|
/// fn conditional() { }
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// Good:
|
|
|
|
/// ```rust
|
|
|
|
/// #[cfg(target_os = "linux")]
|
|
|
|
/// fn conditional() { }
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// Or:
|
|
|
|
/// ```rust
|
|
|
|
/// #[cfg(unix)]
|
|
|
|
/// fn conditional() { }
|
|
|
|
/// ```
|
2020-04-30 18:21:24 -05:00
|
|
|
/// Check the [Rust Reference](https://doc.rust-lang.org/reference/conditional-compilation.html#target_os) for more details.
|
2020-04-22 16:01:25 -05:00
|
|
|
pub MISMATCHED_TARGET_OS,
|
|
|
|
correctness,
|
|
|
|
"usage of `cfg(operating_system)` instead of `cfg(target_os = \"operating_system\")`"
|
|
|
|
}
|
|
|
|
|
2019-04-08 15:43:55 -05:00
|
|
|
declare_lint_pass!(Attributes => [
|
|
|
|
INLINE_ALWAYS,
|
|
|
|
DEPRECATED_SEMVER,
|
|
|
|
USELESS_ATTRIBUTE,
|
|
|
|
UNKNOWN_CLIPPY_LINTS,
|
2020-07-14 07:59:59 -05:00
|
|
|
BLANKET_CLIPPY_RESTRICTION_LINTS,
|
2019-04-08 15:43:55 -05:00
|
|
|
]);
|
2015-05-30 08:10:19 -05:00
|
|
|
|
2020-06-25 15:41:36 -05:00
|
|
|
impl<'tcx> LateLintPass<'tcx> for Attributes {
|
|
|
|
fn check_attribute(&mut self, cx: &LateContext<'tcx>, attr: &'tcx Attribute) {
|
2018-12-29 10:29:50 -06:00
|
|
|
if let Some(items) = &attr.meta_item_list() {
|
2019-03-26 04:55:03 -05:00
|
|
|
if let Some(ident) = attr.ident() {
|
2020-07-14 07:59:59 -05:00
|
|
|
let ident = &*ident.as_str();
|
|
|
|
match ident {
|
2019-03-18 06:31:49 -05:00
|
|
|
"allow" | "warn" | "deny" | "forbid" => {
|
2020-07-14 07:59:59 -05:00
|
|
|
check_clippy_lint_names(cx, ident, items);
|
2019-03-18 06:31:49 -05:00
|
|
|
},
|
|
|
|
_ => {},
|
|
|
|
}
|
2020-11-05 07:29:48 -06:00
|
|
|
if items.is_empty() || !attr.has_name(sym::deprecated) {
|
2019-03-18 06:31:49 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
for item in items {
|
|
|
|
if_chain! {
|
2019-03-18 05:59:09 -05:00
|
|
|
if let NestedMetaItem::MetaItem(mi) = &item;
|
2019-09-27 10:16:06 -05:00
|
|
|
if let MetaItemKind::NameValue(lit) = &mi.kind;
|
2020-11-05 07:29:48 -06:00
|
|
|
if mi.has_name(sym::since);
|
2019-03-18 06:31:49 -05:00
|
|
|
then {
|
2019-03-18 06:05:20 -05:00
|
|
|
check_semver(cx, item.span(), lit);
|
2019-03-18 06:31:49 -05:00
|
|
|
}
|
2017-10-23 14:18:02 -05:00
|
|
|
}
|
|
|
|
}
|
2016-01-30 06:48:39 -06:00
|
|
|
}
|
2016-01-08 19:05:43 -06:00
|
|
|
}
|
|
|
|
}
|
2016-01-30 06:48:39 -06:00
|
|
|
|
2020-06-25 15:41:36 -05:00
|
|
|
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
|
2019-04-07 12:44:10 -05:00
|
|
|
if is_relevant_item(cx, item) {
|
2018-12-29 18:09:24 -06:00
|
|
|
check_attrs(cx, item.span, item.ident.name, &item.attrs)
|
2015-08-11 13:22:20 -05:00
|
|
|
}
|
2019-09-27 10:16:06 -05:00
|
|
|
match item.kind {
|
2018-07-19 06:44:26 -05:00
|
|
|
ItemKind::ExternCrate(..) | ItemKind::Use(..) => {
|
2020-11-05 07:29:48 -06:00
|
|
|
let skip_unused_imports = item.attrs.iter().any(|attr| attr.has_name(sym::macro_use));
|
2018-07-19 06:44:26 -05:00
|
|
|
|
2019-12-22 08:56:34 -06:00
|
|
|
for attr in item.attrs {
|
2019-04-09 16:19:11 -05:00
|
|
|
if in_external_macro(cx.sess(), attr.span) {
|
|
|
|
return;
|
|
|
|
}
|
2018-12-29 10:29:50 -06:00
|
|
|
if let Some(lint_list) = &attr.meta_item_list() {
|
2019-03-26 04:55:03 -05:00
|
|
|
if let Some(ident) = attr.ident() {
|
|
|
|
match &*ident.as_str() {
|
2019-03-18 06:31:49 -05:00
|
|
|
"allow" | "warn" | "deny" | "forbid" => {
|
2020-09-10 10:47:07 -05:00
|
|
|
// permit `unused_imports`, `deprecated`, `unreachable_pub`,
|
|
|
|
// `clippy::wildcard_imports`, and `clippy::enum_glob_use` for `use` items
|
2019-03-18 06:31:49 -05:00
|
|
|
// and `unused_imports` for `extern crate` items with `macro_use`
|
|
|
|
for lint in lint_list {
|
2019-09-27 10:16:06 -05:00
|
|
|
match item.kind {
|
2019-03-18 06:31:49 -05:00
|
|
|
ItemKind::Use(..) => {
|
2019-05-17 16:53:54 -05:00
|
|
|
if is_word(lint, sym!(unused_imports))
|
2020-11-05 07:29:48 -06:00
|
|
|
|| is_word(lint, sym::deprecated)
|
2019-05-17 05:42:43 -05:00
|
|
|
|| is_word(lint, sym!(unreachable_pub))
|
2020-03-02 03:22:05 -06:00
|
|
|
|| is_word(lint, sym!(unused))
|
2020-09-10 10:47:07 -05:00
|
|
|
|| extract_clippy_lint(lint)
|
|
|
|
.map_or(false, |s| s == "wildcard_imports")
|
|
|
|
|| extract_clippy_lint(lint).map_or(false, |s| s == "enum_glob_use")
|
2019-05-14 03:06:21 -05:00
|
|
|
{
|
2019-03-18 06:31:49 -05:00
|
|
|
return;
|
|
|
|
}
|
2018-05-03 17:28:02 -05:00
|
|
|
},
|
2019-03-18 06:31:49 -05:00
|
|
|
ItemKind::ExternCrate(..) => {
|
2019-05-17 16:53:54 -05:00
|
|
|
if is_word(lint, sym!(unused_imports)) && skip_unused_imports {
|
2019-03-18 06:31:49 -05:00
|
|
|
return;
|
|
|
|
}
|
2019-05-17 16:53:54 -05:00
|
|
|
if is_word(lint, sym!(unused_extern_crates)) {
|
2019-03-18 06:31:49 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_ => {},
|
|
|
|
}
|
2016-08-17 04:36:04 -05:00
|
|
|
}
|
2020-02-04 09:07:09 -06:00
|
|
|
let line_span = first_line_of_span(cx, attr.span);
|
2019-03-18 06:31:49 -05:00
|
|
|
|
|
|
|
if let Some(mut sugg) = snippet_opt(cx, line_span) {
|
|
|
|
if sugg.contains("#[") {
|
|
|
|
span_lint_and_then(
|
|
|
|
cx,
|
|
|
|
USELESS_ATTRIBUTE,
|
|
|
|
line_span,
|
|
|
|
"useless lint attribute",
|
2020-04-17 01:08:00 -05:00
|
|
|
|diag| {
|
2019-03-18 06:31:49 -05:00
|
|
|
sugg = sugg.replacen("#[", "#![", 1);
|
2020-04-17 01:08:00 -05:00
|
|
|
diag.span_suggestion(
|
2019-03-18 06:31:49 -05:00
|
|
|
line_span,
|
|
|
|
"if you just forgot a `!`, use",
|
|
|
|
sugg,
|
2019-08-28 14:17:12 -05:00
|
|
|
Applicability::MaybeIncorrect,
|
2019-03-18 06:31:49 -05:00
|
|
|
);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_ => {},
|
|
|
|
}
|
2016-08-17 04:36:04 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_ => {},
|
|
|
|
}
|
2015-08-11 13:22:20 -05:00
|
|
|
}
|
|
|
|
|
2020-06-25 15:41:36 -05:00
|
|
|
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
|
2019-04-07 12:44:10 -05:00
|
|
|
if is_relevant_impl(cx, item) {
|
2018-06-28 08:46:58 -05:00
|
|
|
check_attrs(cx, item.span, item.ident.name, &item.attrs)
|
2015-08-11 13:22:20 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-25 15:41:36 -05:00
|
|
|
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
|
2019-04-07 12:44:10 -05:00
|
|
|
if is_relevant_trait(cx, item) {
|
2018-06-28 08:46:58 -05:00
|
|
|
check_attrs(cx, item.span, item.ident.name, &item.attrs)
|
2015-08-11 13:22:20 -05:00
|
|
|
}
|
|
|
|
}
|
2015-06-07 05:03:56 -05:00
|
|
|
}
|
|
|
|
|
2020-09-10 10:47:07 -05:00
|
|
|
/// Returns the lint name if it is clippy lint.
|
|
|
|
fn extract_clippy_lint(lint: &NestedMetaItem) -> Option<SymbolStr> {
|
|
|
|
if_chain! {
|
|
|
|
if let Some(meta_item) = lint.meta_item();
|
|
|
|
if meta_item.path.segments.len() > 1;
|
|
|
|
if let tool_name = meta_item.path.segments[0].ident;
|
|
|
|
if tool_name.as_str() == "clippy";
|
|
|
|
let lint_name = meta_item.path.segments.last().unwrap().ident.name;
|
|
|
|
then {
|
|
|
|
return Some(lint_name.as_str());
|
2020-07-14 07:59:59 -05:00
|
|
|
}
|
|
|
|
}
|
2020-09-10 10:47:07 -05:00
|
|
|
None
|
|
|
|
}
|
2020-07-14 07:59:59 -05:00
|
|
|
|
2020-09-10 10:47:07 -05:00
|
|
|
fn check_clippy_lint_names(cx: &LateContext<'_>, ident: &str, items: &[NestedMetaItem]) {
|
2020-07-14 07:59:59 -05:00
|
|
|
let lint_store = cx.lints();
|
|
|
|
for lint in items {
|
2020-09-10 10:47:07 -05:00
|
|
|
if let Some(lint_name) = extract_clippy_lint(lint) {
|
2020-11-05 07:29:48 -06:00
|
|
|
if let CheckLintNameResult::Tool(Err((None, _))) = lint_store.check_lint_name(&lint_name, Some(sym::clippy))
|
2020-07-14 07:59:59 -05:00
|
|
|
{
|
2018-09-10 10:09:15 -05:00
|
|
|
span_lint_and_then(
|
|
|
|
cx,
|
|
|
|
UNKNOWN_CLIPPY_LINTS,
|
2019-03-18 06:05:20 -05:00
|
|
|
lint.span(),
|
2020-07-14 07:59:59 -05:00
|
|
|
&format!("unknown clippy lint: clippy::{}", lint_name),
|
2020-04-17 01:08:00 -05:00
|
|
|
|diag| {
|
2020-07-14 07:59:59 -05:00
|
|
|
let name_lower = lint_name.to_lowercase();
|
|
|
|
let symbols = lint_store
|
|
|
|
.get_lints()
|
|
|
|
.iter()
|
|
|
|
.map(|l| Symbol::intern(&l.name_lower()))
|
|
|
|
.collect::<Vec<_>>();
|
2020-07-08 05:03:37 -05:00
|
|
|
let sugg = find_best_match_for_name(
|
Move lev_distance to rustc_ast, make non-generic
rustc_ast currently has a few dependencies on rustc_lexer. Ideally, an AST
would not have any dependency its lexer, for minimizing unnecessarily
design-time dependencies. Breaking this dependency would also have practical
benefits, since modifying rustc_lexer would not trigger a rebuild of rustc_ast.
This commit does not remove the rustc_ast --> rustc_lexer dependency,
but it does remove one of the sources of this dependency, which is the
code that handles fuzzy matching between symbol names for making suggestions
in diagnostics. Since that code depends only on Symbol, it is easy to move
it to rustc_span. It might even be best to move it to a separate crate,
since other tools such as Cargo use the same algorithm, and have simply
contain a duplicate of the code.
This changes the signature of find_best_match_for_name so that it is no
longer generic over its input. I checked the optimized binaries, and this
function was duplicated at nearly every call site, because most call sites
used short-lived iterator chains, generic over Map and such. But there's
no good reason for a function like this to be generic, since all it does
is immediately convert the generic input (the Iterator impl) to a concrete
Vec<Symbol>. This has all of the costs of generics (duplicated method bodies)
with no benefit.
Changing find_best_match_for_name to be non-generic removed about 10KB of
code from the optimized binary. I know it's a drop in the bucket, but we have
to start reducing binary size, and beginning to tame over-use of generics
is part of that.
2020-11-12 13:24:10 -06:00
|
|
|
&symbols,
|
2020-07-08 05:03:37 -05:00
|
|
|
Symbol::intern(&format!("clippy::{}", name_lower)),
|
|
|
|
None,
|
|
|
|
);
|
2020-07-14 07:59:59 -05:00
|
|
|
if lint_name.chars().any(char::is_uppercase)
|
|
|
|
&& lint_store.find_lints(&format!("clippy::{}", name_lower)).is_ok()
|
|
|
|
{
|
2020-04-17 01:08:00 -05:00
|
|
|
diag.span_suggestion(
|
2019-12-27 05:41:11 -06:00
|
|
|
lint.span(),
|
|
|
|
"lowercase the lint name",
|
|
|
|
format!("clippy::{}", name_lower),
|
|
|
|
Applicability::MachineApplicable,
|
|
|
|
);
|
|
|
|
} else if let Some(sugg) = sugg {
|
2020-04-17 01:08:00 -05:00
|
|
|
diag.span_suggestion(
|
2019-12-27 05:41:11 -06:00
|
|
|
lint.span(),
|
|
|
|
"did you mean",
|
|
|
|
sugg.to_string(),
|
|
|
|
Applicability::MachineApplicable,
|
|
|
|
);
|
2018-09-10 10:09:15 -05:00
|
|
|
}
|
2020-07-14 07:59:59 -05:00
|
|
|
},
|
|
|
|
);
|
|
|
|
} else if lint_name == "restriction" && ident != "allow" {
|
|
|
|
span_lint_and_help(
|
|
|
|
cx,
|
|
|
|
BLANKET_CLIPPY_RESTRICTION_LINTS,
|
|
|
|
lint.span(),
|
|
|
|
"restriction lints are not meant to be all enabled",
|
|
|
|
None,
|
|
|
|
"try enabling only the lints you really need",
|
2018-09-10 10:09:15 -05:00
|
|
|
);
|
|
|
|
}
|
2020-07-14 07:59:59 -05:00
|
|
|
}
|
2018-09-10 10:09:15 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-25 15:41:36 -05:00
|
|
|
fn is_relevant_item(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
|
2019-11-08 14:12:08 -06:00
|
|
|
if let ItemKind::Fn(_, _, eid) = item.kind {
|
2020-07-17 03:47:04 -05:00
|
|
|
is_relevant_expr(cx, cx.tcx.typeck_body(eid), &cx.tcx.hir().body(eid).value)
|
2016-01-03 22:26:12 -06:00
|
|
|
} else {
|
2018-01-23 14:32:06 -06:00
|
|
|
true
|
2016-01-03 22:26:12 -06:00
|
|
|
}
|
2015-06-07 05:03:56 -05:00
|
|
|
}
|
|
|
|
|
2020-06-25 15:41:36 -05:00
|
|
|
fn is_relevant_impl(cx: &LateContext<'_>, item: &ImplItem<'_>) -> bool {
|
2019-09-27 10:16:06 -05:00
|
|
|
match item.kind {
|
2020-07-17 03:47:04 -05:00
|
|
|
ImplItemKind::Fn(_, eid) => is_relevant_expr(cx, cx.tcx.typeck_body(eid), &cx.tcx.hir().body(eid).value),
|
2016-01-03 22:26:12 -06:00
|
|
|
_ => false,
|
2015-08-11 13:22:20 -05:00
|
|
|
}
|
2015-06-07 05:03:56 -05:00
|
|
|
}
|
|
|
|
|
2020-06-25 15:41:36 -05:00
|
|
|
fn is_relevant_trait(cx: &LateContext<'_>, item: &TraitItem<'_>) -> bool {
|
2019-09-27 10:16:06 -05:00
|
|
|
match item.kind {
|
2020-03-16 10:00:16 -05:00
|
|
|
TraitItemKind::Fn(_, TraitFn::Required(_)) => true,
|
|
|
|
TraitItemKind::Fn(_, TraitFn::Provided(eid)) => {
|
2020-07-17 03:47:04 -05:00
|
|
|
is_relevant_expr(cx, cx.tcx.typeck_body(eid), &cx.tcx.hir().body(eid).value)
|
2017-01-13 10:04:56 -06:00
|
|
|
},
|
2016-01-03 22:26:12 -06:00
|
|
|
_ => false,
|
2015-08-11 13:22:20 -05:00
|
|
|
}
|
2015-06-07 05:03:56 -05:00
|
|
|
}
|
|
|
|
|
2020-07-17 03:47:04 -05:00
|
|
|
fn is_relevant_block(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_>, block: &Block<'_>) -> bool {
|
2020-07-14 07:59:59 -05:00
|
|
|
block.stmts.first().map_or(
|
2020-07-17 03:47:04 -05:00
|
|
|
block
|
|
|
|
.expr
|
|
|
|
.as_ref()
|
|
|
|
.map_or(false, |e| is_relevant_expr(cx, typeck_results, e)),
|
2020-07-14 07:59:59 -05:00
|
|
|
|stmt| match &stmt.kind {
|
2019-01-20 04:21:30 -06:00
|
|
|
StmtKind::Local(_) => true,
|
2020-07-17 03:47:04 -05:00
|
|
|
StmtKind::Expr(expr) | StmtKind::Semi(expr) => is_relevant_expr(cx, typeck_results, expr),
|
2019-01-20 04:21:30 -06:00
|
|
|
_ => false,
|
2020-07-14 07:59:59 -05:00
|
|
|
},
|
|
|
|
)
|
2015-06-07 05:03:56 -05:00
|
|
|
}
|
|
|
|
|
2020-07-17 03:47:04 -05:00
|
|
|
fn is_relevant_expr(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_>, expr: &Expr<'_>) -> bool {
|
2019-09-27 10:16:06 -05:00
|
|
|
match &expr.kind {
|
2020-07-17 03:47:04 -05:00
|
|
|
ExprKind::Block(block, _) => is_relevant_block(cx, typeck_results, block),
|
|
|
|
ExprKind::Ret(Some(e)) => is_relevant_expr(cx, typeck_results, e),
|
2018-07-12 02:30:57 -05:00
|
|
|
ExprKind::Ret(None) | ExprKind::Break(_, None) => false,
|
2018-12-29 10:29:50 -06:00
|
|
|
ExprKind::Call(path_expr, _) => {
|
2019-09-27 10:16:06 -05:00
|
|
|
if let ExprKind::Path(qpath) = &path_expr.kind {
|
2020-07-17 03:47:04 -05:00
|
|
|
typeck_results
|
2020-07-14 07:59:59 -05:00
|
|
|
.qpath_res(qpath, path_expr.hir_id)
|
|
|
|
.opt_def_id()
|
2020-11-17 14:16:15 -06:00
|
|
|
.map_or(true, |fun_id| !match_panic_def_id(cx, fun_id))
|
2017-09-12 07:26:40 -05:00
|
|
|
} else {
|
|
|
|
true
|
|
|
|
}
|
2016-12-20 11:21:30 -06:00
|
|
|
},
|
2016-01-03 22:26:12 -06:00
|
|
|
_ => true,
|
2015-08-11 13:22:20 -05:00
|
|
|
}
|
2015-05-30 08:10:19 -05:00
|
|
|
}
|
|
|
|
|
2020-08-11 08:43:21 -05:00
|
|
|
fn check_attrs(cx: &LateContext<'_>, span: Span, name: Symbol, attrs: &[Attribute]) {
|
2019-08-19 11:30:32 -05:00
|
|
|
if span.from_expansion() {
|
2016-01-03 22:26:12 -06:00
|
|
|
return;
|
|
|
|
}
|
2015-08-11 13:22:20 -05:00
|
|
|
|
|
|
|
for attr in attrs {
|
2018-12-29 10:29:50 -06:00
|
|
|
if let Some(values) = attr.meta_item_list() {
|
2020-11-05 07:29:48 -06:00
|
|
|
if values.len() != 1 || !attr.has_name(sym::inline) {
|
2016-01-03 22:26:12 -06:00
|
|
|
continue;
|
|
|
|
}
|
2020-11-05 07:29:48 -06:00
|
|
|
if is_word(&values[0], sym::always) {
|
2017-08-09 02:30:56 -05:00
|
|
|
span_lint(
|
|
|
|
cx,
|
|
|
|
INLINE_ALWAYS,
|
|
|
|
attr.span,
|
|
|
|
&format!(
|
|
|
|
"you have declared `#[inline(always)]` on `{}`. This is usually a bad idea",
|
|
|
|
name
|
|
|
|
),
|
|
|
|
);
|
2015-08-11 13:22:20 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-05-30 08:10:19 -05:00
|
|
|
}
|
2016-01-08 19:05:43 -06:00
|
|
|
|
2020-06-25 15:41:36 -05:00
|
|
|
fn check_semver(cx: &LateContext<'_>, span: Span, lit: &Lit) {
|
2019-09-27 10:16:06 -05:00
|
|
|
if let LitKind::Str(is, _) = lit.kind {
|
2017-03-30 03:21:13 -05:00
|
|
|
if Version::parse(&is.as_str()).is_ok() {
|
2016-01-08 19:05:43 -06:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2017-08-09 02:30:56 -05:00
|
|
|
span_lint(
|
|
|
|
cx,
|
|
|
|
DEPRECATED_SEMVER,
|
|
|
|
span,
|
|
|
|
"the since field must contain a semver-compliant version",
|
|
|
|
);
|
2016-01-08 19:05:43 -06:00
|
|
|
}
|
2016-08-28 10:54:32 -05:00
|
|
|
|
2019-05-13 18:34:08 -05:00
|
|
|
fn is_word(nmi: &NestedMetaItem, expected: Symbol) -> bool {
|
2019-03-18 05:59:09 -05:00
|
|
|
if let NestedMetaItem::MetaItem(mi) = &nmi {
|
2020-08-02 05:17:20 -05:00
|
|
|
mi.is_word() && mi.has_name(expected)
|
2016-11-23 14:19:03 -06:00
|
|
|
} else {
|
|
|
|
false
|
2016-08-28 10:54:32 -05:00
|
|
|
}
|
|
|
|
}
|
2018-01-26 00:51:27 -06:00
|
|
|
|
2020-05-28 08:45:24 -05:00
|
|
|
declare_lint_pass!(EarlyAttributes => [
|
|
|
|
DEPRECATED_CFG_ATTR,
|
|
|
|
MISMATCHED_TARGET_OS,
|
|
|
|
EMPTY_LINE_AFTER_OUTER_ATTR,
|
|
|
|
]);
|
2018-09-18 04:35:53 -05:00
|
|
|
|
2020-04-22 16:01:25 -05:00
|
|
|
impl EarlyLintPass for EarlyAttributes {
|
2020-04-27 12:56:11 -05:00
|
|
|
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &rustc_ast::Item) {
|
2020-05-28 08:45:24 -05:00
|
|
|
check_empty_line_after_outer_attr(cx, item);
|
|
|
|
}
|
|
|
|
|
2018-09-18 04:35:53 -05:00
|
|
|
fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {
|
2020-04-22 16:01:25 -05:00
|
|
|
check_deprecated_cfg_attr(cx, attr);
|
|
|
|
check_mismatched_target_os(cx, attr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-27 12:56:11 -05:00
|
|
|
fn check_empty_line_after_outer_attr(cx: &EarlyContext<'_>, item: &rustc_ast::Item) {
|
2020-05-28 08:45:24 -05:00
|
|
|
for attr in &item.attrs {
|
2020-11-05 11:27:48 -06:00
|
|
|
let attr_item = if let AttrKind::Normal(ref attr, _) = attr.kind {
|
2020-05-28 08:45:24 -05:00
|
|
|
attr
|
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
|
|
|
|
if attr.style == AttrStyle::Outer {
|
|
|
|
if attr_item.args.inner_tokens().is_empty() || !is_present_in_source(cx, attr.span) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let begin_of_attr_to_item = Span::new(attr.span.lo(), item.span.lo(), item.span.ctxt());
|
|
|
|
let end_of_attr_to_item = Span::new(attr.span.hi(), item.span.lo(), item.span.ctxt());
|
|
|
|
|
|
|
|
if let Some(snippet) = snippet_opt(cx, end_of_attr_to_item) {
|
|
|
|
let lines = snippet.split('\n').collect::<Vec<_>>();
|
|
|
|
let lines = without_block_comments(lines);
|
|
|
|
|
|
|
|
if lines.iter().filter(|l| l.trim().is_empty()).count() > 2 {
|
|
|
|
span_lint(
|
|
|
|
cx,
|
|
|
|
EMPTY_LINE_AFTER_OUTER_ATTR,
|
|
|
|
begin_of_attr_to_item,
|
2020-08-11 08:43:21 -05:00
|
|
|
"found an empty line after an outer attribute. \
|
2020-05-28 08:45:24 -05:00
|
|
|
Perhaps you forgot to add a `!` to make it an inner attribute?",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-22 16:01:25 -05:00
|
|
|
fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute) {
|
|
|
|
if_chain! {
|
|
|
|
// check cfg_attr
|
2020-11-05 07:29:48 -06:00
|
|
|
if attr.has_name(sym::cfg_attr);
|
2020-04-22 16:01:25 -05:00
|
|
|
if let Some(items) = attr.meta_item_list();
|
|
|
|
if items.len() == 2;
|
|
|
|
// check for `rustfmt`
|
|
|
|
if let Some(feature_item) = items[0].meta_item();
|
2020-11-05 07:29:48 -06:00
|
|
|
if feature_item.has_name(sym::rustfmt);
|
2020-04-22 16:01:25 -05:00
|
|
|
// check for `rustfmt_skip` and `rustfmt::skip`
|
|
|
|
if let Some(skip_item) = &items[1].meta_item();
|
2020-08-02 05:17:20 -05:00
|
|
|
if skip_item.has_name(sym!(rustfmt_skip)) ||
|
2020-04-22 16:01:25 -05:00
|
|
|
skip_item.path.segments.last().expect("empty path in attribute").ident.name == sym!(skip);
|
|
|
|
// Only lint outer attributes, because custom inner attributes are unstable
|
|
|
|
// Tracking issue: https://github.com/rust-lang/rust/issues/54726
|
|
|
|
if let AttrStyle::Outer = attr.style;
|
|
|
|
then {
|
|
|
|
span_lint_and_sugg(
|
|
|
|
cx,
|
|
|
|
DEPRECATED_CFG_ATTR,
|
|
|
|
attr.span,
|
|
|
|
"`cfg_attr` is deprecated for rustfmt and got replaced by tool attributes",
|
|
|
|
"use",
|
|
|
|
"#[rustfmt::skip]".to_string(),
|
|
|
|
Applicability::MachineApplicable,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) {
|
2020-04-25 13:55:46 -05:00
|
|
|
fn find_os(name: &str) -> Option<&'static str> {
|
|
|
|
UNIX_SYSTEMS
|
|
|
|
.iter()
|
|
|
|
.chain(NON_UNIX_SYSTEMS.iter())
|
|
|
|
.find(|&&os| os == name)
|
|
|
|
.copied()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_unix(name: &str) -> bool {
|
|
|
|
UNIX_SYSTEMS.iter().any(|&os| os == name)
|
|
|
|
}
|
|
|
|
|
2020-04-22 16:01:25 -05:00
|
|
|
fn find_mismatched_target_os(items: &[NestedMetaItem]) -> Vec<(&str, Span)> {
|
|
|
|
let mut mismatched = Vec::new();
|
2020-04-25 13:55:46 -05:00
|
|
|
|
2020-04-22 16:01:25 -05:00
|
|
|
for item in items {
|
|
|
|
if let NestedMetaItem::MetaItem(meta) = item {
|
|
|
|
match &meta.kind {
|
|
|
|
MetaItemKind::List(list) => {
|
|
|
|
mismatched.extend(find_mismatched_target_os(&list));
|
|
|
|
},
|
|
|
|
MetaItemKind::Word => {
|
2020-04-25 13:55:46 -05:00
|
|
|
if_chain! {
|
|
|
|
if let Some(ident) = meta.ident();
|
|
|
|
if let Some(os) = find_os(&*ident.name.as_str());
|
|
|
|
then {
|
2020-04-22 16:01:25 -05:00
|
|
|
mismatched.push((os, ident.span));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_ => {},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-04-25 13:55:46 -05:00
|
|
|
|
2020-04-22 16:01:25 -05:00
|
|
|
mismatched
|
|
|
|
}
|
|
|
|
|
|
|
|
if_chain! {
|
2020-11-05 07:29:48 -06:00
|
|
|
if attr.has_name(sym::cfg);
|
2020-04-22 16:01:25 -05:00
|
|
|
if let Some(list) = attr.meta_item_list();
|
2020-04-25 13:55:46 -05:00
|
|
|
let mismatched = find_mismatched_target_os(&list);
|
2020-04-25 16:52:36 -05:00
|
|
|
if !mismatched.is_empty();
|
2020-04-22 16:01:25 -05:00
|
|
|
then {
|
2020-04-25 13:55:46 -05:00
|
|
|
let mess = "operating system used in target family position";
|
2020-04-22 16:01:25 -05:00
|
|
|
|
2020-04-25 16:52:36 -05:00
|
|
|
span_lint_and_then(cx, MISMATCHED_TARGET_OS, attr.span, &mess, |diag| {
|
2020-04-25 13:55:46 -05:00
|
|
|
// Avoid showing the unix suggestion multiple times in case
|
|
|
|
// we have more than one mismatch for unix-like systems
|
|
|
|
let mut unix_suggested = false;
|
|
|
|
|
|
|
|
for (os, span) in mismatched {
|
|
|
|
let sugg = format!("target_os = \"{}\"", os);
|
2020-04-22 16:01:25 -05:00
|
|
|
diag.span_suggestion(span, "try", sugg, Applicability::MaybeIncorrect);
|
2020-04-25 13:55:46 -05:00
|
|
|
|
|
|
|
if !unix_suggested && is_unix(os) {
|
|
|
|
diag.help("Did you mean `unix`?");
|
|
|
|
unix_suggested = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2018-09-18 04:35:53 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|