diff --git a/src/cargo/core/compiler/fingerprint/mod.rs b/src/cargo/core/compiler/fingerprint/mod.rs index 7401afebca4..a3523110bba 100644 --- a/src/cargo/core/compiler/fingerprint/mod.rs +++ b/src/cargo/core/compiler/fingerprint/mod.rs @@ -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, unit: &Unit) -> CargoResult { 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); } diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index bb46ae91f55..04e2ca87741 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -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 { diff --git a/src/cargo/core/manifest.rs b/src/cargo/core/manifest.rs index 9f77b1301fb..98498ead884 100644 --- a/src/cargo/core/manifest.rs +++ b/src/cargo/core/manifest.rs @@ -63,6 +63,7 @@ pub struct Manifest { default_run: Option, metabuild: Option>, resolve_behavior: Option, + lint_rustflags: Vec, } /// When parsing `Cargo.toml`, some warnings should silenced @@ -405,6 +406,7 @@ impl Manifest { original: Rc, metabuild: Option>, resolve_behavior: Option, + lint_rustflags: Vec, ) -> 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), diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 3b2bba09cfc..3cabee8cea9 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -355,6 +355,7 @@ pub struct TomlManifest { patch: Option>>, workspace: Option, badges: Option, + lints: Option, } #[derive(Deserialize, Serialize, Clone, Debug, Default)] @@ -1421,6 +1422,29 @@ impl<'de> de::Deserialize<'de> for MaybeWorkspaceBtreeMap { } } +type MaybeWorkspaceLints = MaybeWorkspace; + +impl<'de> de::Deserialize<'de> for MaybeWorkspaceLints { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + let value = serde_value::Value::deserialize(deserializer)?; + + if let Ok(w) = TomlWorkspaceField::deserialize( + serde_value::ValueDeserializer::::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::::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, dependencies: Option>, + lints: Option, // 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>, + #[serde(skip)] + lints: Option, + version: Option, authors: Option>, description: Option, @@ -1550,6 +1578,10 @@ impl InheritableFields { self.dependencies = deps; } + pub fn update_lints(&mut self, lints: Option) { + 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 { + 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 { 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::( + 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 = 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>( + lints: Option, + features: &Features, + config: &Config, + warnings: &mut Vec, +) -> CargoResult> { + 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) { + 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) -> CargoResult> { + 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 { + 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::>(); + 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; + +pub type TomlToolLints = BTreeMap; + +#[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", + } + } +} diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index bd610374747..08a84f3f62b 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -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 diff --git a/tests/testsuite/lints.rs b/tests/testsuite/lints.rs new file mode 100644 index 00000000000..b2aa0eee7c7 --- /dev/null +++ b/tests/testsuite/lints.rs @@ -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(); +} diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs index efc31f4933e..4308123ad2e 100644 --- a/tests/testsuite/main.rs +++ b/tests/testsuite/main.rs @@ -68,6 +68,7 @@ mod init; mod install; mod install_upgrade; mod jobserver; +mod lints; mod list_availables; mod local_registry; mod locate_project;