mirror of https://github.com/rust-lang/cargo
Compare commits
5 Commits
4f9c694569
...
0b21cd2145
Author | SHA1 | Date |
---|---|---|
Scott Schafer | 0b21cd2145 | |
Scott Schafer | 889f5ab15b | |
Scott Schafer | 9c797763e7 | |
Scott Schafer | ab8855487d | |
Scott Schafer | dc4cd7fe4d |
|
@ -343,7 +343,7 @@ impl FromStr for Edition {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum Status {
|
||||
Stable,
|
||||
Unstable,
|
||||
|
@ -387,11 +387,11 @@ macro_rules! features {
|
|||
$(
|
||||
$(#[$attr])*
|
||||
#[doc = concat!("\n\n\nSee <https://doc.rust-lang.org/nightly/cargo/", $docs, ">.")]
|
||||
pub fn $feature() -> &'static Feature {
|
||||
pub const fn $feature() -> &'static Feature {
|
||||
fn get(features: &Features) -> bool {
|
||||
stab!($stab) == Status::Stable || features.$feature
|
||||
}
|
||||
static FEAT: Feature = Feature {
|
||||
const FEAT: Feature = Feature {
|
||||
name: stringify!($feature),
|
||||
stability: stab!($stab),
|
||||
version: $version,
|
||||
|
@ -512,6 +512,7 @@ features! {
|
|||
}
|
||||
|
||||
/// Status and metadata for a single unstable feature.
|
||||
#[derive(Debug)]
|
||||
pub struct Feature {
|
||||
/// Feature name. This is valid Rust identifer so no dash only underscore.
|
||||
name: &'static str,
|
||||
|
|
|
@ -24,7 +24,9 @@ use crate::sources::{PathSource, CRATES_IO_INDEX, CRATES_IO_REGISTRY};
|
|||
use crate::util::edit_distance;
|
||||
use crate::util::errors::{CargoResult, ManifestError};
|
||||
use crate::util::interning::InternedString;
|
||||
use crate::util::lints::{check_im_a_teapot, check_implicit_features, unused_dependencies};
|
||||
use crate::util::lints::{
|
||||
check_im_a_teapot, check_implicit_features, unused_dependencies, verify_lints,
|
||||
};
|
||||
use crate::util::toml::{read_manifest, InheritableFields};
|
||||
use crate::util::{
|
||||
context::CargoResolverConfig, context::CargoResolverPrecedence, context::ConfigRelativePath,
|
||||
|
@ -1180,6 +1182,16 @@ impl<'gctx> Workspace<'gctx> {
|
|||
}
|
||||
|
||||
pub fn emit_lints(&self, pkg: &Package, path: &Path) -> CargoResult<()> {
|
||||
let ws_contents = match self.root_maybe() {
|
||||
MaybePackage::Package(pkg) => pkg.manifest().contents(),
|
||||
MaybePackage::Virtual(v) => v.contents(),
|
||||
};
|
||||
|
||||
let ws_document = match self.root_maybe() {
|
||||
MaybePackage::Package(pkg) => pkg.manifest().document(),
|
||||
MaybePackage::Virtual(v) => v.document(),
|
||||
};
|
||||
|
||||
let ws_lints = self
|
||||
.root_maybe()
|
||||
.workspace_config()
|
||||
|
@ -1212,6 +1224,16 @@ impl<'gctx> Workspace<'gctx> {
|
|||
.map(|(name, lint)| (name.replace('-', "_"), lint))
|
||||
.collect();
|
||||
|
||||
verify_lints(
|
||||
pkg,
|
||||
&path,
|
||||
&normalized_lints,
|
||||
&ws_cargo_lints,
|
||||
ws_contents,
|
||||
ws_document,
|
||||
self.root_manifest(),
|
||||
self.gctx,
|
||||
)?;
|
||||
check_im_a_teapot(
|
||||
pkg,
|
||||
&path,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::core::dependency::DepKind;
|
||||
use crate::core::FeatureValue::Dep;
|
||||
use crate::core::{Edition, FeatureValue, Package};
|
||||
use crate::core::{Edition, Feature, FeatureValue, Manifest, Package};
|
||||
use crate::util::interning::InternedString;
|
||||
use crate::{CargoResult, GlobalContext};
|
||||
use annotate_snippets::{Level, Renderer, Snippet};
|
||||
|
@ -12,11 +12,164 @@ use std::ops::Range;
|
|||
use std::path::Path;
|
||||
use toml_edit::ImDocument;
|
||||
|
||||
const LINT_GROUPS: &[LintGroup] = &[TEST_DUMMY_UNSTABLE];
|
||||
const LINTS: &[Lint] = &[IM_A_TEAPOT, IMPLICIT_FEATURES, UNUSED_OPTIONAL_DEPENDENCY];
|
||||
|
||||
pub fn verify_lints(
|
||||
pkg: &Package,
|
||||
path: &Path,
|
||||
pkg_lints: &TomlToolLints,
|
||||
ws_lints: &TomlToolLints,
|
||||
ws_contents: &str,
|
||||
ws_document: &ImDocument<String>,
|
||||
ws_path: &Path,
|
||||
gctx: &GlobalContext,
|
||||
) -> CargoResult<()> {
|
||||
let mut error_count = 0;
|
||||
let manifest = pkg.manifest();
|
||||
let manifest_path = rel_cwd_manifest_path(path, gctx);
|
||||
let ws_path = rel_cwd_manifest_path(ws_path, gctx);
|
||||
|
||||
let iter = LINTS
|
||||
.iter()
|
||||
.map(|l| (l.name, l.default_level, l.edition_lint_opts, l.feature_gate))
|
||||
.chain(
|
||||
LINT_GROUPS
|
||||
.iter()
|
||||
.map(|g| (g.name, g.default_level, g.edition_lint_opts, g.feature_gate)),
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
for lint_name in pkg_lints.keys().chain(ws_lints.keys()) {
|
||||
if let Some((name, default_level, edition_lint_opts, feature_gate)) =
|
||||
iter.iter().find(|(n, _, _, _)| n == &lint_name)
|
||||
{
|
||||
let (_, reason, _) = level_priority(
|
||||
name,
|
||||
*default_level,
|
||||
*edition_lint_opts,
|
||||
pkg_lints,
|
||||
ws_lints,
|
||||
manifest.edition(),
|
||||
);
|
||||
if let Some(feature_gate) = feature_gate {
|
||||
feature_gated_lint(
|
||||
name,
|
||||
feature_gate,
|
||||
reason,
|
||||
manifest,
|
||||
&manifest_path,
|
||||
ws_contents,
|
||||
ws_document,
|
||||
&ws_path,
|
||||
&mut error_count,
|
||||
gctx,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
if error_count > 0 {
|
||||
Err(anyhow::anyhow!(
|
||||
"encountered {error_count} errors(s) while verifying lints",
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn feature_gated_lint(
|
||||
lint_name: &str,
|
||||
feature_gate: &Feature,
|
||||
reason: LintLevelReason,
|
||||
manifest: &Manifest,
|
||||
manifest_path: &str,
|
||||
ws_contents: &str,
|
||||
ws_document: &ImDocument<String>,
|
||||
ws_path: &str,
|
||||
error_count: &mut usize,
|
||||
gctx: &GlobalContext,
|
||||
) -> CargoResult<()> {
|
||||
if !manifest.unstable_features().is_enabled(feature_gate) {
|
||||
let dash_name = lint_name.replace("_", "-");
|
||||
let title = "verification of lint failed";
|
||||
let label = "use of unstable lint that has not been enabled";
|
||||
let second_title = format!("`cargo::{}` was inherited", lint_name);
|
||||
let message = match reason {
|
||||
LintLevelReason::Package => {
|
||||
let span = get_span(
|
||||
manifest.document(),
|
||||
&["lints", "cargo", dash_name.as_str()],
|
||||
false,
|
||||
)
|
||||
.or(get_span(
|
||||
manifest.document(),
|
||||
&["lints", "cargo", lint_name],
|
||||
false,
|
||||
))
|
||||
.unwrap();
|
||||
Some(
|
||||
Level::Error.title(&title).snippet(
|
||||
Snippet::source(manifest.contents())
|
||||
.origin(&manifest_path)
|
||||
.annotation(Level::Error.span(span).label(label))
|
||||
.fold(true),
|
||||
),
|
||||
)
|
||||
}
|
||||
LintLevelReason::Workspace => {
|
||||
let lint_span = get_span(
|
||||
ws_document,
|
||||
&["workspace", "lints", "cargo", dash_name.as_str()],
|
||||
false,
|
||||
)
|
||||
.or(get_span(
|
||||
ws_document,
|
||||
&["workspace", "lints", "cargo", lint_name],
|
||||
false,
|
||||
))
|
||||
.unwrap();
|
||||
let inherit_span =
|
||||
get_span(manifest.document(), &["lints", "workspace"], false).unwrap();
|
||||
Some(
|
||||
Level::Error
|
||||
.title(&title)
|
||||
.snippet(
|
||||
Snippet::source(ws_contents)
|
||||
.origin(&ws_path)
|
||||
.annotation(Level::Error.span(lint_span).label(label))
|
||||
.fold(true),
|
||||
)
|
||||
.footer(
|
||||
Level::Note.title(&second_title).snippet(
|
||||
Snippet::source(manifest.contents())
|
||||
.origin(&manifest_path)
|
||||
.annotation(Level::Note.span(inherit_span))
|
||||
.fold(true),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(message) = message {
|
||||
*error_count += 1;
|
||||
let renderer = Renderer::styled().term_width(
|
||||
gctx.shell()
|
||||
.err_width()
|
||||
.diagnostic_terminal_width()
|
||||
.unwrap_or(annotate_snippets::renderer::DEFAULT_TERM_WIDTH),
|
||||
);
|
||||
writeln!(gctx.shell().err(), "{}", renderer.render(message))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_span(document: &ImDocument<String>, path: &[&str], get_value: bool) -> Option<Range<usize>> {
|
||||
let mut table = document.as_item().as_table_like().unwrap();
|
||||
let mut table = document.as_item().as_table_like()?;
|
||||
let mut iter = path.into_iter().peekable();
|
||||
while let Some(key) = iter.next() {
|
||||
let (key, item) = table.get_key_value(key).unwrap();
|
||||
let (key, item) = table.get_key_value(key)?;
|
||||
if iter.peek().is_none() {
|
||||
return if get_value {
|
||||
item.span()
|
||||
|
@ -66,6 +219,7 @@ pub struct LintGroup {
|
|||
pub default_level: LintLevel,
|
||||
pub desc: &'static str,
|
||||
pub edition_lint_opts: Option<(Edition, LintLevel)>,
|
||||
pub feature_gate: Option<&'static Feature>,
|
||||
}
|
||||
|
||||
const TEST_DUMMY_UNSTABLE: LintGroup = LintGroup {
|
||||
|
@ -73,6 +227,7 @@ const TEST_DUMMY_UNSTABLE: LintGroup = LintGroup {
|
|||
desc: "test_dummy_unstable is meant to only be used in tests",
|
||||
default_level: LintLevel::Allow,
|
||||
edition_lint_opts: None,
|
||||
feature_gate: Some(Feature::test_dummy_unstable()),
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
|
@ -82,6 +237,7 @@ pub struct Lint {
|
|||
pub groups: &'static [LintGroup],
|
||||
pub default_level: LintLevel,
|
||||
pub edition_lint_opts: Option<(Edition, LintLevel)>,
|
||||
pub feature_gate: Option<&'static Feature>,
|
||||
}
|
||||
|
||||
impl Lint {
|
||||
|
@ -164,7 +320,7 @@ impl From<TomlLintLevel> for LintLevel {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum LintLevelReason {
|
||||
Default,
|
||||
Edition(Edition),
|
||||
|
@ -228,6 +384,7 @@ const IM_A_TEAPOT: Lint = Lint {
|
|||
groups: &[TEST_DUMMY_UNSTABLE],
|
||||
default_level: LintLevel::Allow,
|
||||
edition_lint_opts: None,
|
||||
feature_gate: Some(Feature::test_dummy_unstable()),
|
||||
};
|
||||
|
||||
pub fn check_im_a_teapot(
|
||||
|
@ -239,7 +396,9 @@ pub fn check_im_a_teapot(
|
|||
gctx: &GlobalContext,
|
||||
) -> CargoResult<()> {
|
||||
let manifest = pkg.manifest();
|
||||
let manifest_path = rel_cwd_manifest_path(path, gctx);
|
||||
let (lint_level, reason) = IM_A_TEAPOT.level(pkg_lints, ws_lints, manifest.edition());
|
||||
|
||||
if lint_level == LintLevel::Allow {
|
||||
return Ok(());
|
||||
}
|
||||
|
@ -253,7 +412,6 @@ pub fn check_im_a_teapot(
|
|||
*error_count += 1;
|
||||
}
|
||||
let level = lint_level.to_diagnostic_level();
|
||||
let manifest_path = rel_cwd_manifest_path(path, gctx);
|
||||
let emitted_reason = format!(
|
||||
"`cargo::{}` is set to `{lint_level}` {reason}",
|
||||
IM_A_TEAPOT.name
|
||||
|
@ -300,6 +458,7 @@ const IMPLICIT_FEATURES: Lint = Lint {
|
|||
groups: &[],
|
||||
default_level: LintLevel::Allow,
|
||||
edition_lint_opts: None,
|
||||
feature_gate: None,
|
||||
};
|
||||
|
||||
pub fn check_implicit_features(
|
||||
|
@ -384,6 +543,7 @@ const UNUSED_OPTIONAL_DEPENDENCY: Lint = Lint {
|
|||
groups: &[],
|
||||
default_level: LintLevel::Warn,
|
||||
edition_lint_opts: None,
|
||||
feature_gate: None,
|
||||
};
|
||||
|
||||
pub fn unused_dependencies(
|
||||
|
|
|
@ -982,3 +982,104 @@ error: `im_a_teapot` is specified
|
|||
)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[cargo_test]
|
||||
fn check_feature_gated() {
|
||||
let p = project()
|
||||
.file(
|
||||
"Cargo.toml",
|
||||
r#"
|
||||
[package]
|
||||
name = "foo"
|
||||
version = "0.0.1"
|
||||
edition = "2015"
|
||||
authors = []
|
||||
|
||||
[lints.cargo]
|
||||
im-a-teapot = "warn"
|
||||
"#,
|
||||
)
|
||||
.file("src/lib.rs", "")
|
||||
.build();
|
||||
|
||||
p.cargo("check -Zcargo-lints")
|
||||
.masquerade_as_nightly_cargo(&["cargo-lints"])
|
||||
.with_status(101)
|
||||
.with_stderr(
|
||||
"\
|
||||
error: verification of lint failed
|
||||
--> Cargo.toml:9:1
|
||||
|
|
||||
9 | im-a-teapot = \"warn\"
|
||||
| ^^^^^^^^^^^ use of unstable lint that has not been enabled
|
||||
|
|
||||
error: encountered 1 errors(s) while verifying lints
|
||||
",
|
||||
)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[cargo_test]
|
||||
fn check_feature_gated_workspace() {
|
||||
let p = project()
|
||||
.file(
|
||||
"Cargo.toml",
|
||||
r#"
|
||||
[workspace]
|
||||
members = ["foo"]
|
||||
|
||||
[workspace.lints.cargo]
|
||||
im-a-teapot = { level = "warn", priority = 10 }
|
||||
test-dummy-unstable = { level = "forbid", priority = -1 }
|
||||
"#,
|
||||
)
|
||||
.file(
|
||||
"foo/Cargo.toml",
|
||||
r#"
|
||||
[package]
|
||||
name = "foo"
|
||||
version = "0.0.1"
|
||||
edition = "2015"
|
||||
authors = []
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
"#,
|
||||
)
|
||||
.file("foo/src/lib.rs", "")
|
||||
.build();
|
||||
|
||||
p.cargo("check -Zcargo-lints")
|
||||
.masquerade_as_nightly_cargo(&["cargo-lints"])
|
||||
.with_status(101)
|
||||
.with_stderr(
|
||||
"\
|
||||
error: verification of lint failed
|
||||
--> Cargo.toml:6:1
|
||||
|
|
||||
6 | im-a-teapot = { level = \"warn\", priority = 10 }
|
||||
| ^^^^^^^^^^^ use of unstable lint that has not been enabled
|
||||
|
|
||||
note: `cargo::im_a_teapot` was inherited
|
||||
--> foo/Cargo.toml:9:1
|
||||
|
|
||||
9 | workspace = true
|
||||
| ---------
|
||||
|
|
||||
error: verification of lint failed
|
||||
--> Cargo.toml:7:1
|
||||
|
|
||||
7 | test-dummy-unstable = { level = \"forbid\", priority = -1 }
|
||||
| ^^^^^^^^^^^^^^^^^^^ use of unstable lint that has not been enabled
|
||||
|
|
||||
note: `cargo::test_dummy_unstable` was inherited
|
||||
--> foo/Cargo.toml:9:1
|
||||
|
|
||||
9 | workspace = true
|
||||
| ---------
|
||||
|
|
||||
error: encountered 2 errors(s) while verifying lints
|
||||
",
|
||||
)
|
||||
.run();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue