Auto merge of #12148 - epage:lints, r=weihanglo

feat: `lints` feature

### What does this PR try to resolve?

Implement rust-lang/rfcs#3389 which shifts a subset of `.cargo/config.toml` functionality to `Cargo.toml` by adding a `[lints]` table.

This **should** cover all of the user-facing aspects of the RFC
- This doesn't reduce what flags we fingerprint
- This will fail if any lint name as `::` in it.  What to do in this case was in the RFC discussion but I couldn't find the thread to see what all was involved in that discussion
- This does not fail if a `[lints]` table is present or malformed unless nightly with the `lints` feature enabled
  - The idea is this will act like a `[lints]` table is present in an existing version of rust, ignore it
  - The intent is to not force an MSRV bump to use it.
  - When disabled, it will be a warning
  - When disabled, it will be stripped so we don't publish it

Tracking issue for this is #12115.

### How should we test and review this PR?

1. Look at this commit by commit to see it gradually build up
2. Look through the final set of test cases to make sure everything in the RFC is covered

I tried to write this in a way that will make it easy to strip out the special handling of this unstable feature, both in code and commit history

### Additional information

I'd love to bypass the need for `cargo-features = ["lints"]` so users today can test it on their existing projects but hesitated for now.  We can re-evaluate that later.

I broke out the `warn_for_feature` as an experiment towards us systemitizing this stabilization approach which we also used with #9732.  This works well when we can ignore the new information which isn't too often but sometimes happens.

This does involve a subtle change to `profile.rustflags` precedence but
its nightly and most likely people won't notice it?  The benefit is its
in a location more like the rest of the rustflags.
This commit is contained in:
bors 2023-05-22 19:54:39 +00:00
commit e5e68c4093
8 changed files with 1008 additions and 4 deletions

View File

@ -75,6 +75,7 @@
//! [`Lto`] flags | ✓ | ✓
//! config settings[^5] | ✓ |
//! is_std | | ✓
//! `[lints]` table[^6] | ✓ |
//!
//! [^1]: Build script and bin dependencies are not included.
//!
@ -86,6 +87,8 @@
//! [^5]: Config settings that are not otherwise captured anywhere else.
//! Currently, this is only `doc.extern-map`.
//!
//! [^6]: Via [`Manifest::lint_rustflags`][crate::core::Manifest::lint_rustflags]
//!
//! When deciding what should go in the Metadata vs the Fingerprint, consider
//! that some files (like dylibs) do not have a hash in their filename. Thus,
//! if a value changes, only the fingerprint will detect the change (consider,
@ -1414,6 +1417,7 @@ fn calculate_normal(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult<Finger
unit.mode,
cx.bcx.extra_args_for(unit),
cx.lto[unit],
unit.pkg.manifest().lint_rustflags(),
));
// Include metadata since it is exposed as environment variables.
let m = unit.pkg.manifest().metadata();

View File

@ -762,6 +762,7 @@ fn rustdoc(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult<Work> {
add_error_format_and_color(cx, &mut rustdoc);
add_allow_features(cx, &mut rustdoc);
rustdoc.args(unit.pkg.manifest().lint_rustflags());
if let Some(args) = cx.bcx.extra_args_for(unit) {
rustdoc.args(args);
}
@ -1040,10 +1041,6 @@ fn build_base_args(
cmd.arg("-C").arg(&format!("opt-level={}", opt_level));
}
if !rustflags.is_empty() {
cmd.args(&rustflags);
}
if *panic != PanicStrategy::Unwind {
cmd.arg("-C").arg(format!("panic={}", panic));
}
@ -1078,6 +1075,10 @@ fn build_base_args(
cmd.arg("-C").arg(format!("debuginfo={}", debuginfo));
}
cmd.args(unit.pkg.manifest().lint_rustflags());
if !rustflags.is_empty() {
cmd.args(&rustflags);
}
if let Some(args) = cx.bcx.extra_args_for(unit) {
cmd.args(args);
}

View File

@ -483,6 +483,9 @@ features! {
// Allow specifying rustflags directly in a profile
(stable, workspace_inheritance, "1.64", "reference/unstable.html#workspace-inheritance"),
// Allow specifying rustflags directly in a profile
(unstable, lints, "", "reference/unstable.html#lints"),
}
pub struct Feature {

View File

@ -63,6 +63,7 @@ pub struct Manifest {
default_run: Option<String>,
metabuild: Option<Vec<String>>,
resolve_behavior: Option<ResolveBehavior>,
lint_rustflags: Vec<String>,
}
/// When parsing `Cargo.toml`, some warnings should silenced
@ -405,6 +406,7 @@ impl Manifest {
original: Rc<TomlManifest>,
metabuild: Option<Vec<String>>,
resolve_behavior: Option<ResolveBehavior>,
lint_rustflags: Vec<String>,
) -> Manifest {
Manifest {
summary,
@ -430,6 +432,7 @@ impl Manifest {
default_run,
metabuild,
resolve_behavior,
lint_rustflags,
}
}
@ -514,6 +517,11 @@ impl Manifest {
self.resolve_behavior
}
/// `RUSTFLAGS` from the `[lints]` table
pub fn lint_rustflags(&self) -> &[String] {
self.lint_rustflags.as_slice()
}
pub fn map_source(self, to_replace: SourceId, replace_with: SourceId) -> Manifest {
Manifest {
summary: self.summary.map_source(to_replace, replace_with),

View File

@ -355,6 +355,7 @@ pub struct TomlManifest {
patch: Option<BTreeMap<String, BTreeMap<String, TomlDependency>>>,
workspace: Option<TomlWorkspace>,
badges: Option<MaybeWorkspaceBtreeMap>,
lints: Option<toml::Value>,
}
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
@ -1421,6 +1422,29 @@ impl<'de> de::Deserialize<'de> for MaybeWorkspaceBtreeMap {
}
}
type MaybeWorkspaceLints = MaybeWorkspace<TomlLints, TomlWorkspaceField>;
impl<'de> de::Deserialize<'de> for MaybeWorkspaceLints {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
let value = serde_value::Value::deserialize(deserializer)?;
if let Ok(w) = TomlWorkspaceField::deserialize(
serde_value::ValueDeserializer::<D::Error>::new(value.clone()),
) {
return if w.workspace() {
Ok(MaybeWorkspace::Workspace(w))
} else {
Err(de::Error::custom("`workspace` cannot be false"))
};
}
TomlLints::deserialize(serde_value::ValueDeserializer::<D::Error>::new(value))
.map(MaybeWorkspace::Defined)
}
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct TomlWorkspaceField {
#[serde(deserialize_with = "bool_no_false")]
@ -1507,6 +1531,7 @@ pub struct TomlWorkspace {
// Properties that can be inherited by members.
package: Option<InheritableFields>,
dependencies: Option<BTreeMap<String, TomlDependency>>,
lints: Option<toml::Value>,
// Note that this field must come last due to the way toml serialization
// works which requires tables to be emitted after all values.
@ -1520,6 +1545,9 @@ pub struct InheritableFields {
// and we don't want it present when serializing
#[serde(skip)]
dependencies: Option<BTreeMap<String, TomlDependency>>,
#[serde(skip)]
lints: Option<TomlLints>,
version: Option<semver::Version>,
authors: Option<Vec<String>>,
description: Option<String>,
@ -1550,6 +1578,10 @@ impl InheritableFields {
self.dependencies = deps;
}
pub fn update_lints(&mut self, lints: Option<TomlLints>) {
self.lints = lints;
}
pub fn update_ws_path(&mut self, ws_root: PathBuf) {
self.ws_root = ws_root;
}
@ -1561,6 +1593,12 @@ impl InheritableFields {
)
}
pub fn lints(&self) -> CargoResult<TomlLints> {
self.lints
.clone()
.map_or(Err(anyhow!("`workspace.lints` was not defined")), |d| Ok(d))
}
pub fn get_dependency(&self, name: &str, package_root: &Path) -> CargoResult<TomlDependency> {
self.dependencies.clone().map_or(
Err(anyhow!("`workspace.dependencies` was not defined")),
@ -1878,6 +1916,7 @@ impl TomlManifest {
workspace: None,
badges: self.badges.clone(),
cargo_features: self.cargo_features.clone(),
lints: self.lints.clone(),
});
fn map_deps(
@ -2006,6 +2045,14 @@ impl TomlManifest {
let mut inheritable = toml_config.package.clone().unwrap_or_default();
inheritable.update_ws_path(package_root.to_path_buf());
inheritable.update_deps(toml_config.dependencies.clone());
let lints = parse_unstable_lints(
toml_config.lints.clone(),
&features,
config,
&mut warnings,
)?;
let lints = verify_lints(lints)?;
inheritable.update_lints(lints);
if let Some(ws_deps) = &inheritable.dependencies {
for (name, dep) in ws_deps {
unused_dep_keys(
@ -2269,6 +2316,18 @@ impl TomlManifest {
&inherit_cell,
)?;
let lints = parse_unstable_lints::<MaybeWorkspaceLints>(
me.lints.clone(),
&features,
config,
cx.warnings,
)?
.map(|mw| mw.resolve("lints", || inherit()?.lints()))
.transpose()?;
let lints = verify_lints(lints)?;
let default = TomlLints::default();
let rustflags = lints_to_rustflags(lints.as_ref().unwrap_or(&default));
let mut target: BTreeMap<String, TomlPlatform> = BTreeMap::new();
for (name, platform) in me.target.iter().flatten() {
cx.platform = {
@ -2566,6 +2625,8 @@ impl TomlManifest {
.badges
.as_ref()
.map(|_| MaybeWorkspace::Defined(metadata.badges.clone())),
lints: lints
.map(|lints| toml::Value::try_from(MaybeWorkspaceLints::Defined(lints)).unwrap()),
};
let mut manifest = Manifest::new(
summary,
@ -2590,6 +2651,7 @@ impl TomlManifest {
Rc::new(resolved_toml),
package.metabuild.clone().map(|sov| sov.0),
resolve_behavior,
rustflags,
);
if package.license_file.is_some() && package.license.is_some() {
manifest.warnings_mut().add_warning(
@ -2695,6 +2757,14 @@ impl TomlManifest {
let mut inheritable = toml_config.package.clone().unwrap_or_default();
inheritable.update_ws_path(root.to_path_buf());
inheritable.update_deps(toml_config.dependencies.clone());
let lints = parse_unstable_lints(
toml_config.lints.clone(),
&features,
config,
&mut warnings,
)?;
let lints = verify_lints(lints)?;
inheritable.update_lints(lints);
let ws_root_config = WorkspaceRootConfig::new(
root,
&toml_config.members,
@ -2839,6 +2909,105 @@ impl TomlManifest {
}
}
fn parse_unstable_lints<T: Deserialize<'static>>(
lints: Option<toml::Value>,
features: &Features,
config: &Config,
warnings: &mut Vec<String>,
) -> CargoResult<Option<T>> {
let Some(lints) = lints else { return Ok(None); };
if !features.is_enabled(Feature::lints()) {
warn_for_feature("lints", config, warnings);
return Ok(None);
}
lints.try_into().map(Some).map_err(|err| err.into())
}
fn warn_for_feature(name: &str, config: &Config, warnings: &mut Vec<String>) {
use std::fmt::Write as _;
let mut message = String::new();
let _ = write!(
message,
"feature `{name}` is not supported on this version of Cargo and will be ignored"
);
if config.nightly_features_allowed {
let _ = write!(
message,
"
consider adding `cargo-features = [\"{name}\"]` to the manifest"
);
} else {
let _ = write!(
message,
"
this Cargo does not support nightly features, but if you
switch to nightly channel you can add
`cargo-features = [\"{name}\"]` to enable this feature",
);
}
warnings.push(message);
}
fn verify_lints(lints: Option<TomlLints>) -> CargoResult<Option<TomlLints>> {
let Some(lints) = lints else { return Ok(None); };
for (tool, lints) in &lints {
let supported = ["rust", "clippy", "rustdoc"];
if !supported.contains(&tool.as_str()) {
let supported = supported.join(", ");
anyhow::bail!("unsupported `{tool}` in `[lints]`, must be one of {supported}")
}
for name in lints.keys() {
if let Some((prefix, suffix)) = name.split_once("::") {
if tool == prefix {
anyhow::bail!(
"`lints.{tool}.{name}` is not valid lint name; try `lints.{prefix}.{suffix}`"
)
} else if tool == "rust" && supported.contains(&prefix) {
anyhow::bail!(
"`lints.{tool}.{name}` is not valid lint name; try `lints.{prefix}.{suffix}`"
)
} else {
anyhow::bail!("`lints.{tool}.{name}` is not a valid lint name")
}
}
}
}
Ok(Some(lints))
}
fn lints_to_rustflags(lints: &TomlLints) -> Vec<String> {
let mut rustflags = lints
.iter()
.flat_map(|(tool, lints)| {
lints.iter().map(move |(name, config)| {
let flag = config.level().flag();
let option = if tool == "rust" {
format!("{flag}={name}")
} else {
format!("{flag}={tool}::{name}")
};
(
config.priority(),
// Since the most common group will be `all`, put it last so people are more
// likely to notice that they need to use `priority`.
std::cmp::Reverse(name),
option,
)
})
})
.collect::<Vec<_>>();
rustflags.sort();
rustflags.into_iter().map(|(_, _, option)| option).collect()
}
fn unused_dep_keys(
dep_name: &str,
kind: &str,
@ -3396,3 +3565,58 @@ impl fmt::Debug for PathValue {
self.0.fmt(f)
}
}
pub type TomlLints = BTreeMap<String, TomlToolLints>;
pub type TomlToolLints = BTreeMap<String, TomlLint>;
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum TomlLint {
Level(TomlLintLevel),
Config(TomlLintConfig),
}
impl TomlLint {
fn level(&self) -> TomlLintLevel {
match self {
Self::Level(level) => *level,
Self::Config(config) => config.level,
}
}
fn priority(&self) -> i8 {
match self {
Self::Level(_) => 0,
Self::Config(config) => config.priority,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct TomlLintConfig {
level: TomlLintLevel,
#[serde(default)]
priority: i8,
}
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
#[serde(rename_all = "kebab-case")]
pub enum TomlLintLevel {
Forbid,
Deny,
Warn,
Allow,
}
impl TomlLintLevel {
fn flag(&self) -> &'static str {
match self {
Self::Forbid => "--forbid",
Self::Deny => "--deny",
Self::Warn => "--warn",
Self::Allow => "--allow",
}
}
}

View File

@ -93,6 +93,7 @@ Each new feature described below should explain how to use it.
* [codegen-backend](#codegen-backend) --- Select the codegen backend used by rustc.
* [per-package-target](#per-package-target) --- Sets the `--target` to use for each individual package.
* [artifact dependencies](#artifact-dependencies) --- Allow build artifacts to be included into other build artifacts and build them for different targets.
* [`[lints]`](#lints) --- Configure lint levels for various linter tools.
* Information and metadata
* [Build-plan](#build-plan) --- Emits JSON information on which commands will be run.
* [unit-graph](#unit-graph) --- Emits JSON for Cargo's internal graph structure.
@ -1391,6 +1392,104 @@ Valid operations are the following:
* When the unstable feature is on, fetching/cloning a git repository is always a shallow fetch. This roughly equals to `git fetch --depth 1` everywhere.
* Even with the presence of `Cargo.lock` or specifying a commit `{ rev = "…" }`, gitoxide is still smart enough to shallow fetch without unshallowing the existing repository.
### `[lints]`
* Tracking Issue: [#12115](https://github.com/rust-lang/cargo/issues/12115)
A new `lints` table would be added to configure lints:
```toml
cargo-features = ["lints"]
[lints.rust]
unsafe = "forbid"
```
and `cargo` would pass these along as flags to `rustc`, `clippy`, or other lint tools.
This would work with
[RFC 2906 `workspace-deduplicate`](https://rust-lang.github.io/rfcs/2906-cargo-workspace-deduplicate.html):
```toml
cargo-features = ["lints"]
[lints]
workspace = true
[workspace.lints.rust]
unsafe = "forbid"
```
#### Documentation Updates
##### The `lints` section
*as a new ["Manifest Format" entry](./manifest.html#the-manifest-format)*
Override the default level of lints from different tools by assigning them to a new level in a
table, for example:
```toml
[lints.rust]
unsafe = "forbid"
```
This is short-hand for:
```toml
[lints.rust]
unsafe = { level = "forbid", priority = 0 }
```
`level` corresponds to the lint levels in `rustc`:
- `forbid`
- `deny`
- `warn`
- `allow`
`priority` is a signed integer that controls which lints or lint groups override other lint groups:
- lower (particularly negative) numbers have lower priority, being overridden
by higher numbers, and show up first on the command-line to tools like
`rustc`
To know which table under `[lints]` a particular lint belongs under, it is the part before `::` in the lint
name. If there isn't a `::`, then the tool is `rust`. For example a warning
about `unsafe` would be `lints.rust.unsafe` but a lint about
`clippy::enum_glob_use` would be `lints.clippy.enum_glob_use`.
For example:
```toml
[lints.rust]
unsafe = "forbid"
[lints.clippy]
enum_glob_use = "deny"
```
##### The `lints` table
*as a new [`[workspace]` entry](./workspaces.html#the-workspace-section)*
The `workspace.lints` table is where you define lint configuration to be inherited by members of a workspace.
Specifying a workspace lint configuration is similar to package lints.
Example:
```toml
# [PROJECT_DIR]/Cargo.toml
[workspace]
members = ["crates/*"]
[workspace.lints.rust]
unsafe = "forbid"
```
```toml
# [PROJECT_DIR]/crates/bar/Cargo.toml
[package]
name = "bar"
version = "0.1.0"
[lints]
workspace = true
```
## Stabilized and removed features
### Compile progress

664
tests/testsuite/lints.rs Normal file
View File

@ -0,0 +1,664 @@
//! Tests for `[lints]`
use cargo_test_support::project;
use cargo_test_support::registry::Package;
#[cargo_test]
fn package_requires_option() {
let foo = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
[lints.rust]
unsafe_code = "forbid"
"#,
)
.file("src/lib.rs", "")
.build();
foo.cargo("check")
.with_stderr(
"\
warning: feature `lints` is not supported on this version of Cargo and will be ignored
this Cargo does not support nightly features, but if you
switch to nightly channel you can add
`cargo-features = [\"lints\"]` to enable this feature
[CHECKING] [..]
[FINISHED] [..]
",
)
.run();
}
#[cargo_test]
fn workspace_requires_option() {
let foo = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
[workspace.lints.rust]
unsafe_code = "forbid"
"#,
)
.file("src/lib.rs", "")
.build();
foo.cargo("check")
.with_stderr(
"\
warning: [CWD]/Cargo.toml: feature `lints` is not supported on this version of Cargo and will be ignored
this Cargo does not support nightly features, but if you
switch to nightly channel you can add
`cargo-features = [\"lints\"]` to enable this feature
[CHECKING] [..]
[FINISHED] [..]
",
)
.run();
}
#[cargo_test]
fn dependency_warning_ignored() {
let foo = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
[dependencies]
bar.path = "../bar"
"#,
)
.file("src/lib.rs", "")
.build();
let _bar = project()
.at("bar")
.file(
"Cargo.toml",
r#"
[package]
name = "bar"
version = "0.0.1"
authors = []
[lints.rust]
unsafe_code = "forbid"
"#,
)
.file("src/lib.rs", "")
.build();
foo.cargo("check")
.with_stderr(
"\
[CHECKING] [..]
[CHECKING] [..]
[FINISHED] [..]
",
)
.run();
}
#[cargo_test]
fn malformed_on_stable() {
let foo = project()
.file(
"Cargo.toml",
r#"
lints = 20
[package]
name = "foo"
version = "0.0.1"
authors = []
"#,
)
.file("src/lib.rs", "")
.build();
foo.cargo("check")
.with_stderr(
"\
warning: feature `lints` is not supported on this version of Cargo and will be ignored
this Cargo does not support nightly features, but if you
switch to nightly channel you can add
`cargo-features = [\"lints\"]` to enable this feature
[CHECKING] [..]
[FINISHED] [..]
",
)
.run();
}
#[cargo_test]
fn malformed_on_nightly() {
let foo = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["lints"]
lints = 20
[package]
name = "foo"
version = "0.0.1"
authors = []
"#,
)
.file("src/lib.rs", "")
.build();
foo.cargo("check")
.masquerade_as_nightly_cargo(&["lints"])
.with_status(101)
.with_stderr(
"\
error: failed to parse manifest[..]
Caused by:
invalid type: integer `20`, expected a map
",
)
.run();
}
#[cargo_test]
fn fail_on_invalid_tool() {
let foo = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["lints"]
[package]
name = "foo"
version = "0.0.1"
authors = []
[workspace.lints.super-awesome-linter]
unsafe_code = "forbid"
"#,
)
.file("src/lib.rs", "")
.build();
foo.cargo("check")
.masquerade_as_nightly_cargo(&["lints"])
.with_status(101)
.with_stderr(
"\
[..]
Caused by:
unsupported `super-awesome-linter` in `[lints]`, must be one of rust, clippy, rustdoc
",
)
.run();
}
#[cargo_test]
fn fail_on_tool_injection() {
let foo = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["lints"]
[package]
name = "foo"
version = "0.0.1"
authors = []
[workspace.lints.rust]
"clippy::cyclomatic_complexity" = "warn"
"#,
)
.file("src/lib.rs", "")
.build();
foo.cargo("check")
.masquerade_as_nightly_cargo(&["lints"])
.with_status(101)
.with_stderr(
"\
[..]
Caused by:
`lints.rust.clippy::cyclomatic_complexity` is not valid lint name; try `lints.clippy.cyclomatic_complexity`
",
)
.run();
}
#[cargo_test]
fn fail_on_redundant_tool() {
let foo = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["lints"]
[package]
name = "foo"
version = "0.0.1"
authors = []
[workspace.lints.rust]
"rust::unsafe_code" = "forbid"
"#,
)
.file("src/lib.rs", "")
.build();
foo.cargo("check")
.masquerade_as_nightly_cargo(&["lints"])
.with_status(101)
.with_stderr(
"\
[..]
Caused by:
`lints.rust.rust::unsafe_code` is not valid lint name; try `lints.rust.unsafe_code`
",
)
.run();
}
#[cargo_test]
fn fail_on_conflicting_tool() {
let foo = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["lints"]
[package]
name = "foo"
version = "0.0.1"
authors = []
[workspace.lints.rust]
"super-awesome-tool::unsafe_code" = "forbid"
"#,
)
.file("src/lib.rs", "")
.build();
foo.cargo("check")
.masquerade_as_nightly_cargo(&["lints"])
.with_status(101)
.with_stderr(
"\
[..]
Caused by:
`lints.rust.super-awesome-tool::unsafe_code` is not a valid lint name
",
)
.run();
}
#[cargo_test]
fn package_lint_deny() {
let foo = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["lints"]
[package]
name = "foo"
version = "0.0.1"
authors = []
[lints.rust]
"unsafe_code" = "deny"
"#,
)
.file(
"src/lib.rs",
"
pub fn foo(num: i32) -> u32 {
unsafe { std::mem::transmute(num) }
}
",
)
.build();
foo.cargo("check")
.masquerade_as_nightly_cargo(&["lints"])
.with_status(101)
.with_stderr_contains(
"\
error: usage of an `unsafe` block
",
)
.run();
}
#[cargo_test]
fn workspace_lint_deny() {
let foo = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["lints"]
[package]
name = "foo"
version = "0.0.1"
authors = []
[lints]
workspace = true
[workspace.lints.rust]
"unsafe_code" = "deny"
"#,
)
.file(
"src/lib.rs",
"
pub fn foo(num: i32) -> u32 {
unsafe { std::mem::transmute(num) }
}
",
)
.build();
foo.cargo("check")
.masquerade_as_nightly_cargo(&["lints"])
.with_status(101)
.with_stderr_contains(
"\
error: usage of an `unsafe` block
",
)
.run();
}
#[cargo_test]
fn attribute_has_precedence() {
let foo = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["lints"]
[package]
name = "foo"
version = "0.0.1"
authors = []
[lints.rust]
"unsafe_code" = "deny"
"#,
)
.file(
"src/lib.rs",
"
#![allow(unsafe_code)]
pub fn foo(num: i32) -> u32 {
unsafe { std::mem::transmute(num) }
}
",
)
.build();
foo.cargo("check")
.arg("-v") // Show order of rustflags on failure
.masquerade_as_nightly_cargo(&["lints"])
.run();
}
#[cargo_test]
fn rustflags_has_precedence() {
let foo = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["lints"]
[package]
name = "foo"
version = "0.0.1"
authors = []
[lints.rust]
"unsafe_code" = "deny"
"#,
)
.file(
"src/lib.rs",
"
pub fn foo(num: i32) -> u32 {
unsafe { std::mem::transmute(num) }
}
",
)
.build();
foo.cargo("check")
.arg("-v") // Show order of rustflags on failure
.env("RUSTFLAGS", "-Aunsafe_code")
.masquerade_as_nightly_cargo(&["lints"])
.run();
}
#[cargo_test]
fn profile_rustflags_has_precedence() {
let foo = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["lints", "profile-rustflags"]
[package]
name = "foo"
version = "0.0.1"
[lints.rust]
"unsafe_code" = "deny"
[profile.dev]
rustflags = ["-A", "unsafe_code"]
"#,
)
.file(
"src/lib.rs",
"
pub fn foo(num: i32) -> u32 {
unsafe { std::mem::transmute(num) }
}
",
)
.build();
foo.cargo("check")
.arg("-v") // Show order of rustflags on failure
.masquerade_as_nightly_cargo(&["lints", "profile-rustflags"])
.run();
}
#[cargo_test]
fn build_rustflags_has_precedence() {
let foo = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["lints", "profile-rustflags"]
[package]
name = "foo"
version = "0.0.1"
[lints.rust]
"unsafe_code" = "deny"
"#,
)
.file(
".cargo/config.toml",
r#"
[build]
rustflags = ["-A", "unsafe_code"]
"#,
)
.file(
"src/lib.rs",
"
pub fn foo(num: i32) -> u32 {
unsafe { std::mem::transmute(num) }
}
",
)
.build();
foo.cargo("check")
.arg("-v") // Show order of rustflags on failure
.masquerade_as_nightly_cargo(&["lints", "profile-rustflags"])
.run();
}
#[cargo_test]
fn without_priority() {
Package::new("reg-dep", "1.0.0").publish();
let foo = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["lints"]
[package]
name = "foo"
version = "0.0.1"
edition = "2018"
authors = []
[dependencies]
reg-dep = "1.0.0"
[lints.rust]
"rust-2018-idioms" = "deny"
"unused-extern-crates" = "allow"
"#,
)
.file(
"src/lib.rs",
"
extern crate reg_dep;
pub fn foo() -> u32 {
2
}
",
)
.build();
foo.cargo("check")
.masquerade_as_nightly_cargo(&["lints"])
.with_status(101)
.with_stderr_contains(
"\
error: unused extern crate
",
)
.run();
}
#[cargo_test]
fn with_priority() {
Package::new("reg-dep", "1.0.0").publish();
let foo = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["lints"]
[package]
name = "foo"
version = "0.0.1"
edition = "2018"
authors = []
[dependencies]
reg-dep = "1.0.0"
[lints.rust]
"rust-2018-idioms" = { level = "deny", priority = -1 }
"unused-extern-crates" = "allow"
"#,
)
.file(
"src/lib.rs",
"
extern crate reg_dep;
pub fn foo() -> u32 {
2
}
",
)
.build();
foo.cargo("check")
.masquerade_as_nightly_cargo(&["lints"])
.run();
}
#[cargo_test]
fn rustdoc_lint() {
let foo = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["lints"]
[package]
name = "foo"
version = "0.0.1"
authors = []
[lints.rustdoc]
broken_intra_doc_links = "deny"
"#,
)
.file(
"src/lib.rs",
"
/// [`bar`] doesn't exist
pub fn foo() -> u32 {
}
",
)
.build();
foo.cargo("doc")
.masquerade_as_nightly_cargo(&["lints"])
.with_status(101)
.with_stderr_contains(
"\
error: unresolved link to `bar`
",
)
.run();
}

View File

@ -68,6 +68,7 @@ mod init;
mod install;
mod install_upgrade;
mod jobserver;
mod lints;
mod list_availables;
mod local_registry;
mod locate_project;