Repository: Enselic/cargo-public-api (rust)

Issue:
cannot enable features for crate with diff

```bash
$ git clone https://github.com/Enselic/cargo-public-api.git
$ cd cargo-public-api
$ cargo run -p cargo-public-api -- --all-features -p public-api diff latest
    Finished dev [unoptimized + debuginfo] target(s) in 0.05s
     Running `target/debug/cargo-public-api --all-features --package public-api diff latest`
Resolved `diff latest` to `diff 0.26.0`
error: cannot specify features for packages outside of workspace
Error: Failed to build rustdoc JSON. Stderr: See above
```


Pull request description:
correctly add features when using cache
resolves #282

continuation of #285 

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 696f83d..e06f786 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -76,7 +76,7 @@ dependencies = [
 
 [[package]]
 name = "cargo-public-api"
-version = "0.27.1"
+version = "0.27.0"
 dependencies = [
  "anyhow",
  "assert_cmd",
@@ -97,8 +97,11 @@ dependencies = [
  "rustdoc-json",
  "rustup-toolchain",
  "semver",
+ "serde",
+ "serde_json",
  "tempfile",
  "thiserror",
+ "toml",
 ]
 
 [[package]]
@@ -170,9 +173,9 @@ dependencies = [
 
 [[package]]
 name = "crates-index"
-version = "0.18.12"
+version = "0.18.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b655ece026c6f8088979ec131e922243801f0b52d2e8ecae805f56519511e69c"
+checksum = "599f67b56f40863598cb30450427049935d05de2e36c61d33c050f04d7ec8cf2"
 dependencies = [
  "git2",
  "hex",
@@ -449,9 +452,9 @@ checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
 
 [[package]]
 name = "libgit2-sys"
-version = "0.14.2+1.5.1"
+version = "0.14.0+1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f3d95f6b51075fe9810a7ae22c7095f12b98005ab364d8544797a825ce946a4"
+checksum = "47a00859c70c8a4f7218e6d1cc32875c4b55f6799445b842b0d8ed5e4c3d959b"
 dependencies = [
  "cc",
  "libc",
@@ -625,7 +628,7 @@ dependencies = [
 
 [[package]]
 name = "public-api"
-version = "0.27.1"
+version = "0.27.0"
 dependencies = [
  "anyhow",
  "assert_cmd",
@@ -904,9 +907,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
 
 [[package]]
 name = "toml"
-version = "0.5.11"
+version = "0.5.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
+checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f"
 dependencies = [
  "indexmap",
  "serde",
diff --git a/Cargo.toml b/Cargo.toml
index 7c7b0d1..176ca64 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,5 +1,5 @@
 [workspace.package]
-version = "0.27.1"
+version = "0.27.0"
 
 [workspace]
 members = [
@@ -46,6 +46,7 @@ exclude = [
     "test-apis/example_api-v0.1.0",
     "test-apis/example_api-v0.1.1",
     "test-apis/example_api-v0.2.0",
+    "test-apis/example_api-v0.2.1",
     "test-apis/example_api-v0.3.0",
 
     # To test that we pass --cap-lints when building rustdoc JSON
diff --git a/README.md b/README.md
index a757c96..cf2c667 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 # cargo-public-api
 
-List and diff the public API of Rust library crates between releases and commits. Detect breaking API changes and semver violations via CI or a CLI. Relies on and automatically builds [rustdoc JSON](https://github.com/rust-lang/rust/issues/76578), for which a recent version of the Rust nightly toolchain must be installed.
+List and diff the public API of Rust library crates between releases and commits. Detect breaking API changes and semver violations. Relies on and automatically builds [rustdoc JSON](https://github.com/rust-lang/rust/issues/76578), for which a recent version of the Rust nightly toolchain must be installed.
 
 # Installation
 
diff --git a/cargo-public-api/Cargo.toml b/cargo-public-api/Cargo.toml
index f09c476..92c27bc 100644
--- a/cargo-public-api/Cargo.toml
+++ b/cargo-public-api/Cargo.toml
@@ -3,7 +3,7 @@ edition = "2021"
 name = "cargo-public-api"
 version.workspace = true
 default-run = "cargo-public-api"
-description = "List and diff the public API of Rust library crates between releases and commits. Detect breaking API changes and semver violations via CI or a CLI."
+description = "List and diff the public API of Rust library crates between releases and commits. Detect breaking API changes and semver violations."
 homepage = "https://github.com/Enselic/cargo-public-api"
 documentation = "https://docs.rs/cargo-public-api"
 readme = "../README.md"
@@ -27,13 +27,16 @@ dirs = "4.0.0"
 home = "0.5.4"
 semver = "1.0.16"
 thiserror = "1.0.38"
+cargo_metadata = "0.15.2"
+toml = "0.5.10"
+serde = { version = "1.0.152", features = ["derive"] }
 
 [dependencies.clap]
 version = "4.0.32"
 features = ["derive", "wrap_help"]
 
 [dependencies.crates-index]
-version = "0.18.12"
+version = "0.18.11"
 default-features = false
 optional = true
 
@@ -43,7 +46,7 @@ version = "0.8.0"
 
 [dependencies.public-api]
 path = "../public-api"
-version = "0.27.1"
+version = "0.27.0"
 
 [dev-dependencies.rustup-toolchain]
 path = "../rustup-toolchain"
@@ -60,3 +63,4 @@ pretty_assertions = "1.3.0"
 remove_dir_all = "0.5.3"
 tempfile = "3.3.0"
 cargo_metadata = "0.15.2"
+serde_json = "1.0.91"
diff --git a/cargo-public-api/src/api_source.rs b/cargo-public-api/src/api_source.rs
index 75f754f..d072449 100644
--- a/cargo-public-api/src/api_source.rs
+++ b/cargo-public-api/src/api_source.rs
@@ -6,7 +6,7 @@ use std::path::{Path, PathBuf};
 
 use public_api::{Options, PublicApi, MINIMUM_NIGHTLY_VERSION};
 
-use crate::{git_utils, Args, Subcommand};
+use crate::{git_utils, Args};
 
 /// Represents some place from which a public API can be obtained.
 /// Examples: a published crate, a git commit, an existing file.
@@ -159,10 +159,6 @@ pub fn builder_from_args(args: &Args) -> rustdoc_json::Builder {
     }
     if let Some(cap_lints) = &args.cap_lints {
         builder = builder.cap_lints(Some(cap_lints));
-    } else if let Some(Subcommand::Diff(_)) = args.subcommand {
-        // Suppress any build warning by default when diffing, because it
-        // typically is undesirable to fix lints in historic versions of a crate
-        builder = builder.cap_lints(Some("allow"));
     }
     builder
 }
diff --git a/cargo-public-api/src/published_crate.rs b/cargo-public-api/src/published_crate.rs
index 6f6cb3d..17eb877 100644
--- a/cargo-public-api/src/published_crate.rs
+++ b/cargo-public-api/src/published_crate.rs
@@ -31,7 +31,18 @@ pub fn build_rustdoc_json(version: impl Into<String>, args: &Args) -> Result<Pat
     };
 
     write_file("lib.rs", "// empty lib")?;
-    let manifest = write_file("Cargo.toml", &manifest_for(&spec))?;
+    let (manifest, needs_resolved_package) = manifest_simple(args, &spec)?;
+    let manifest = write_file("Cargo.toml", &manifest)?;
+
+    if needs_resolved_package {
+        let mut metadata = cargo_metadata::MetadataCommand::new();
+        metadata.manifest_path(&manifest);
+        let metadata = metadata.exec()?;
+        if let Some(package) = metadata.packages.iter().find(|p| p.name == spec.name) {
+            // XXX if metadata doesn't find a package, rustdoc will tell us why, so we can continue
+            write_file("Cargo.toml", &manifest_with_info(args, &spec, package)?)?;
+        };
+    }
 
     // Since we used `crate::builder_from_args(args)` above it means that if
     // `args.target_dir` is set, both the dummy crate and the real crate will
@@ -39,7 +50,10 @@ pub fn build_rustdoc_json(version: impl Into<String>, args: &Args) -> Result<Pat
     // won't work. So always clear the target dir before we use the builder.
     let builder = crate::api_source::builder_from_args(args)
         .clear_target_dir()
-        .manifest_path(manifest)
+        .all_features(false)
+        .features(Vec::<&str>::new())
+        .no_default_features(false)
+        .manifest_path(&manifest)
         .package(&spec.name);
     crate::api_source::build_rustdoc_json(builder)
 }
@@ -92,20 +106,81 @@ fn build_dir(args: &Args, spec: &PackageSpec) -> PathBuf {
     build_dir
 }
 
-fn manifest_for(spec: &PackageSpec) -> String {
-    format!(
-        "\
-        [package]\n\
-        name = \"crate-downloader\"\n\
-        version = \"0.1.0\"\n\
-        edition = \"2021\"\n\
-        [lib]\n\
-        path = \"lib.rs\"\n\
-        [dependencies]\n\
-        {} = \"={}\"\n
-        ",
-        spec.name, spec.version
-    )
+/// Create the manifest for a package given cargo cli arguments. Returns a boolean to signify if [`manifest_with_info`] needs to be called
+fn manifest_simple(args: &Args, spec: &PackageSpec) -> Result<(String, bool)> {
+    let setup = toml::toml! {
+        [package]
+        name = "crate-downloader"
+        version = "0.1.0"
+        edition = "2021"
+        [lib]
+        path = "lib.rs"
+    };
+
+    let Args {
+        features,
+        no_default_features,
+        all_features,
+        ..
+    } = args;
+
+    Ok((
+        format!(
+            "{setup}\n[dependencies.{}]\n{}",
+            spec.name,
+            toml::to_string(&cargo_manifest::DependencyDetail {
+                version: Some(format!("={}", spec.version)),
+                default_features: no_default_features.then(|| false),
+                features: if features.is_empty() {
+                    None
+                } else {
+                    Some(features.clone())
+                },
+                ..Default::default()
+            })?
+        ),
+        *all_features,
+    ))
+}
+
+fn manifest_with_info(
+    args: &Args,
+    spec: &PackageSpec,
+    package: &cargo_metadata::Package,
+) -> Result<String> {
+    let setup = toml::toml! {
+        [package]
+        name = "crate-downloader"
+        version = "0.1.0"
+        edition = "2021"
+        [lib]
+        path = "lib.rs"
+    };
+    let features = package
+        .features
+        .keys()
+        .map(Clone::clone)
+        .collect::<Vec<_>>();
+    let dep = match args {
+        Args {
+            all_features: true, ..
+        } => format!(
+            "[dependencies.{}]\n{}",
+            spec.name,
+            toml::to_string(&cargo_manifest::DependencyDetail {
+                version: Some(format!("={}", spec.version)),
+                features: if features.is_empty() {
+                    None
+                } else {
+                    Some(features)
+                },
+                ..Default::default()
+            })?
+        ),
+        _ => return Ok(manifest_simple(args, spec)?.0),
+    };
+
+    Ok(format!("{setup}\n{dep}"))
 }
 
 #[derive(Debug, PartialEq, Eq)]
@@ -119,3 +194,84 @@ impl PackageSpec {
         PathBuf::from(format!("{}-{}", self.name, self.version))
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use clap::Parser as _;
+
+    #[test]
+    fn manifest_simple() {
+        let manifest = super::manifest_simple(
+            &Args::try_parse_from(["test", "-p", "example-api", "diff", "0.1.1"].iter()).unwrap(),
+            &PackageSpec {
+                name: "example-api".to_owned(),
+                version: "0.1.1".to_owned(),
+            },
+        )
+        .unwrap()
+        .0;
+        assert_eq!(manifest, "[package]\nname = \"crate-downloader\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[lib]\npath = \"lib.rs\"\n\n[dependencies.example-api]\nversion = \"=0.1.1\"\n");
+    }
+
+    #[test]
+    fn manifest_with_info() {
+        let package = serde_json::from_str(
+            r#"{
+        "name": "bin",
+        "version": "0.1.0",
+        "id": "bin 0.1.0 (path+file://)",
+        "license": null,
+        "license_file": null,
+        "description": null,
+        "source": null,
+        "dependencies": [],
+        "targets":
+        [
+          {
+            "kind":
+            [
+              "lib"
+            ],
+            "crate_types":
+            [
+              "lib"
+            ],
+            "name": "lib",
+            "src_path": "/src/lib.rs",
+            "edition": "2021",
+            "doc": true,
+            "doctest": false,
+            "test": true
+          }
+        ],
+        "features": {},
+        "manifest_path": "/Cargo.toml",
+        "metadata": null,
+        "publish": null,
+        "authors": [],
+        "categories": [],
+        "keywords": [],
+        "readme": null,
+        "repository": null,
+        "homepage": null,
+        "documentation": null,
+        "edition": "2021",
+        "links": null,
+        "default_run": null,
+        "rust_version": null
+      }"#,
+        )
+        .unwrap();
+        let manifest = super::manifest_with_info(
+            &Args::try_parse_from(["test", "-p", "example-api", "diff", "0.1.1"].iter()).unwrap(),
+            &PackageSpec {
+                name: "example-api".to_owned(),
+                version: "0.1.1".to_owned(),
+            },
+            &package,
+        )
+        .unwrap();
+        assert_eq!(manifest, "[package]\nname = \"crate-downloader\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[lib]\npath = \"lib.rs\"\n\n[dependencies.example-api]\nversion = \"=0.1.1\"\n");
+    }
+}
diff --git a/docs/long-help.txt b/docs/long-help.txt
index 37506d8..7ee54e9 100644
--- a/docs/long-help.txt
+++ b/docs/long-help.txt
@@ -1,5 +1,5 @@
 List and diff the public API of Rust library crates between releases and commits. Detect breaking
-API changes and semver violations via CI or a CLI.
+API changes and semver violations.
 
 Usage: cargo-public-api [OPTIONS] [COMMAND]
 
diff --git a/docs/short-help.txt b/docs/short-help.txt
index 95d1191..e3e495b 100644
--- a/docs/short-help.txt
+++ b/docs/short-help.txt
@@ -1,5 +1,5 @@
 List and diff the public API of Rust library crates between releases and commits. Detect breaking
-API changes and semver violations via CI or a CLI.
+API changes and semver violations.
 
 Usage: cargo-public-api [OPTIONS] [COMMAND]
 
diff --git a/rustdoc-json/Cargo.toml b/rustdoc-json/Cargo.toml
index 2b8af6d..94aeed4 100644
--- a/rustdoc-json/Cargo.toml
+++ b/rustdoc-json/Cargo.toml
@@ -14,7 +14,7 @@ cargo_metadata = "0.15.2"
 cargo-manifest = "0.7.1"
 serde = { version = "1.0.152", features = ["derive"] }
 thiserror = "1.0.38"
-toml = "0.5.11"
+toml = "0.5.10"
 
 [dev-dependencies]
 assert_cmd = "2.0.8"
@@ -27,4 +27,4 @@ default-features = false
 
 [dev-dependencies.public-api]
 path = "../public-api"
-version = "0.27.1"
+version = "0.27.0"
diff --git a/rustup-toolchain/Cargo.toml b/rustup-toolchain/Cargo.toml
index 45a1858..b3c5b00 100644
--- a/rustup-toolchain/Cargo.toml
+++ b/rustup-toolchain/Cargo.toml
@@ -21,4 +21,4 @@ version = "0.8.0"
 
 [dev-dependencies.public-api]
 path = "../public-api"
-version = "0.27.1"
+version = "0.27.0"


Test patch:
diff --git a/cargo-public-api/tests/cargo-public-api-bin-tests.rs b/cargo-public-api/tests/cargo-public-api-bin-tests.rs
index e22eb10..8188b0a 100644
--- a/cargo-public-api/tests/cargo-public-api-bin-tests.rs
+++ b/cargo-public-api/tests/cargo-public-api-bin-tests.rs
@@ -16,7 +16,6 @@ use std::{
 
 use assert_cmd::assert::Assert;
 use assert_cmd::Command;
-use predicates::prelude::PredicateBooleanExt;
 use predicates::str::contains;
 
 use public_api::MINIMUM_NIGHTLY_VERSION;
@@ -649,21 +648,6 @@ fn document_private_items() {
         .success();
 }
 
-#[test]
-fn cap_lints_allow_by_default_when_diffing() {
-    let mut cmd = TestCmd::new().with_test_repo_variant(TestRepoVariant::LintError);
-    cmd.arg("diff");
-    cmd.arg("v0.1.0..v0.1.1");
-
-    // If `missing_docs` is printed, it must mean that the lint was not capped.
-    // So require it to be absent in the output, since by default we do not want
-    // to show lint errors when diffing. Because the user typically can't do
-    // anything about it.
-    cmd.assert()
-        .stderr(contains("missing_docs").not())
-        .success();
-}
-
 #[test]
 fn diff_against_published_version() {
     let mut cmd = TestCmd::new().with_test_repo();
@@ -704,6 +688,19 @@ fn diff_published_explicit_package() {
         .success();
 }
 
+#[test]
+fn diff_published_with_all_features() {
+    let mut cmd = TestCmd::new().with_test_repo();
+    cmd.arg("-p");
+    cmd.arg("example_api");
+    cmd.arg("--all-features");
+    cmd.arg("diff");
+    cmd.arg("0.2.1");
+    cmd.assert()
+        .stdout_or_update("./expected-output/diff_published_with_features.txt")
+        .success();
+}
+
 #[test]
 fn list_public_items_from_json_file() {
     // Create independent build dir so all tests can run in parallel
@@ -790,6 +787,12 @@ fn long_help_wraps() {
     }
 }
 
+/// Helper to initialize a test crate git repo. Each test gets its own git repo
+/// to use so that tests can run in parallel.
+fn initialize_test_repo(dest: impl AsRef<Path>) {
+    create_test_git_repo::create_test_git_repo(dest, "../test-apis");
+}
+
 fn create_test_repo_with_dirty_git_tree() -> TestRepo {
     let test_repo = TestRepo::new();
 
@@ -921,31 +924,10 @@ struct TestRepo {
     path: PathBuf,
 }
 
-#[derive(Default)]
-enum TestRepoVariant {
-    #[default]
-    ExampleApi,
-    LintError,
-}
-
 impl TestRepo {
     fn new() -> Self {
-        Self::new_with_variant(TestRepoVariant::default())
-    }
-
-    #[allow(clippy::needless_pass_by_value)]
-    fn new_with_variant(variant: TestRepoVariant) -> Self {
         let tempdir = tempfile::tempdir().unwrap();
-        let dirs_and_tags: &[(&str, &str)] = match variant {
-            TestRepoVariant::ExampleApi => &[
-                ("example_api-v0.1.0", "v0.1.0"),
-                ("example_api-v0.1.1", "v0.1.1"),
-                ("example_api-v0.2.0", "v0.2.0"),
-                ("example_api-v0.3.0", "v0.3.0"),
-            ],
-            TestRepoVariant::LintError => &[("lint_error", "v0.1.0"), ("lint_error", "v0.1.1")],
-        };
-        create_test_git_repo::create_test_git_repo(tempdir.path(), dirs_and_tags);
+        initialize_test_repo(tempdir.path());
 
         Self {
             path: tempdir.into_path(),
@@ -1105,14 +1087,10 @@ impl TestCmd {
         }
     }
 
-    fn with_test_repo(self) -> Self {
-        Self::with_test_repo_variant(self, TestRepoVariant::default())
-    }
-
     /// Create a test repo (unique for the current test) and set its dir as the
     /// current dir.
-    fn with_test_repo_variant(mut self, variant: TestRepoVariant) -> Self {
-        let test_repo = TestRepo::new_with_variant(variant);
+    fn with_test_repo(mut self) -> Self {
+        let test_repo = TestRepo::new();
         self.cmd.current_dir(test_repo.path());
         self.test_repo = Some(test_repo);
 
diff --git a/cargo-public-api/tests/create_test_git_repo/mod.rs b/cargo-public-api/tests/create_test_git_repo/mod.rs
index 21651d3..52e2544 100644
--- a/cargo-public-api/tests/create_test_git_repo/mod.rs
+++ b/cargo-public-api/tests/create_test_git_repo/mod.rs
@@ -17,7 +17,7 @@ use std::{
 ///
 /// `test_api_dir` - The `test-apis` source dir. Used to find the path to the
 /// `example_api`s.
-pub fn create_test_git_repo(dest_dir: impl AsRef<Path>, dirs_and_tags: &[(&str, &str)]) {
+pub fn create_test_git_repo(dest_dir: impl AsRef<Path>, test_apis_dir: impl AsRef<Path>) {
     // Make sure the dest exists
     fs::create_dir_all(&dest_dir).unwrap();
 
@@ -37,13 +37,10 @@ pub fn create_test_git_repo(dest_dir: impl AsRef<Path>, dirs_and_tags: &[(&str,
     run(git().args(["config", "user.name", "Cargo Public"]));
 
     // Now go through all directories and create git commits and tags from them
-    for dir_and_tag in dirs_and_tags {
-        let dir_name = dir_and_tag.0;
-        let tag_name = dir_and_tag.1;
-
+    for version in ["v0.1.0", "v0.1.1", "v0.2.0", "v0.3.0"] {
         let copy_to_dest = |name| {
-            let mut from = PathBuf::from("../test-apis");
-            from.push(dir_name);
+            let mut from = PathBuf::from(test_apis_dir.as_ref());
+            from.push(&format!("example_api-{version}"));
             from.push(name);
 
             let mut to = PathBuf::from(dest_dir.as_ref());
@@ -60,8 +57,8 @@ pub fn create_test_git_repo(dest_dir: impl AsRef<Path>, dirs_and_tags: &[(&str,
         copy_to_dest("src/lib.rs");
 
         run(git().args(["add", "."]));
-        run(git().args(["commit", "--quiet", "-m"]).arg(tag_name));
-        run(git().arg("tag").arg(tag_name));
+        run(git().args(["commit", "--quiet", "-m"]).arg(version));
+        run(git().arg("tag").arg(version));
     }
 }
 
diff --git a/cargo-public-api/tests/expected-output/diff_published_with_features.txt b/cargo-public-api/tests/expected-output/diff_published_with_features.txt
new file mode 100644
index 0000000..85abd4e
--- /dev/null
+++ b/cargo-public-api/tests/expected-output/diff_published_with_features.txt
@@ -0,0 +1,14 @@
+Removed items from the public API
+=================================
+-pub example_api::Struct::v1_2_field_gated: usize
+-pub fn example_api::function(v1_param: example_api::Struct, v2_param: usize)
+
+Changed items in the public API
+===============================
+-pub struct example_api::Struct
++#[non_exhaustive] pub struct example_api::Struct
+
+Added items to the public API
+=============================
+(none)
+
diff --git a/repo-tests/Cargo.toml b/repo-tests/Cargo.toml
index 5ef1c0f..e6970d3 100644
--- a/repo-tests/Cargo.toml
+++ b/repo-tests/Cargo.toml
@@ -7,5 +7,5 @@ license = "MIT"
 
 [dev-dependencies.public-api]
 path = "../public-api"
-version = "0.27.1"
+version = "0.27.0"
 
diff --git a/test-apis/example_api-v0.2.1/Cargo.toml b/test-apis/example_api-v0.2.1/Cargo.toml
new file mode 100644
index 0000000..135646c
--- /dev/null
+++ b/test-apis/example_api-v0.2.1/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+edition = "2021"
+name = "example_api"
+version = "0.2.1"
+description = "Example API used in the cargo-public-api test suite"
+homepage = "https://github.com/Enselic/cargo-public-api/tree/main/test-apis/example_api-v0.2.1"
+readme = "../example_api-v0.1.0/README.md"
+license = "MIT"
+repository = "https://github.com/Enselic/cargo-public-api/tree/main/test-apis/example_api-v0.2.1"
+[features]
+feature = []
diff --git a/test-apis/example_api-v0.2.1/src/lib.rs b/test-apis/example_api-v0.2.1/src/lib.rs
new file mode 100644
index 0000000..99194d3
--- /dev/null
+++ b/test-apis/example_api-v0.2.1/src/lib.rs
@@ -0,0 +1,17 @@
+// Allow stuff that prevents us from testing unidiomatic but valid public APIs
+#![allow(unused_variables, dead_code)]
+#![no_std] // Reduces rustdoc JSON size by 70%
+
+#[derive(Debug)]
+pub struct Struct {
+    pub v1_field: usize,
+    pub v2_field: usize,
+    #[cfg(feature = "feature")]
+    pub v1_2_field_gated: usize,
+}
+
+pub struct StructV2 {
+    pub field: usize,
+}
+
+pub fn function(v1_param: Struct, v2_param: usize) {}
diff --git a/test-apis/lint_error/Cargo.toml b/test-apis/lint_error/Cargo.toml
index 97f12d1..70e4394 100644
--- a/test-apis/lint_error/Cargo.toml
+++ b/test-apis/lint_error/Cargo.toml
@@ -5,4 +5,5 @@ version = "0.1.0"
 edition = "2021"
 
 [dependencies.example_api]
+path = "../example_api-v0.2.0"
 version = "0.2.0"