Skip to content

Commit caa26cc

Browse files
committed
feat: anchor target duplicate and file-path errors
Convert duplicate target errors (DuplicateDefaultTargetDefinition, DuplicateNamedTargetDefinition) in ManifestBuilder to typed ManifestSemanticError variants with span anchors. Refactor abs_canonical_path to accept a make_error closure, add anchor parameter to readme_for_package, and wire LicensePathInvalid / ReadmePathInvalid anchors through license-file and readme processing. commit-id:d18762ef
1 parent 2c843e0 commit caa26cc

3 files changed

Lines changed: 59 additions & 25 deletions

File tree

scarb/src/core/manifest/diagnostic_kinds.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,7 @@ impl DuplicateDefaultTargetDefinition {
464464
}
465465

466466
fn primary_anchor(&self) -> ManifestDiagnosticAnchor {
467-
ManifestDiagnosticAnchor::target(self.kind.clone(), Some(self.name.clone()))
467+
ManifestDiagnosticAnchor::target(self.kind.clone(), Some(self.name.to_string()))
468468
}
469469
}
470470

@@ -484,6 +484,6 @@ impl DuplicateNamedTargetDefinition {
484484
}
485485

486486
fn primary_anchor(&self) -> ManifestDiagnosticAnchor {
487-
ManifestDiagnosticAnchor::target(self.kind.clone(), Some(self.name.clone()))
487+
ManifestDiagnosticAnchor::target(self.kind.clone(), Some(self.name.to_string()))
488488
}
489489
}

scarb/src/core/manifest/mod.rs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -119,18 +119,21 @@ impl ManifestBuilder {
119119
for target in targets {
120120
if !used.insert((target.kind.as_str(), target.name.as_str())) {
121121
if target.name == summary.package_id.name.as_str() {
122-
bail!(
123-
"manifest contains duplicate target definitions `{}`, \
124-
consider explicitly naming targets with the `name` field",
125-
target.kind
122+
return Err(ManifestSemanticError::from(
123+
DuplicateDefaultTargetDefinition::new(
124+
target.kind.clone(),
125+
target.name.clone(),
126+
),
126127
)
128+
.into());
127129
} else {
128-
bail!(
129-
"manifest contains duplicate target definitions `{} ({})`, \
130-
use different target names to resolve the conflict",
131-
target.kind,
132-
target.name
133-
)
130+
return Err(
131+
ManifestSemanticError::from(DuplicateNamedTargetDefinition::new(
132+
target.kind.clone(),
133+
target.name.clone(),
134+
))
135+
.into(),
136+
);
134137
}
135138
}
136139
}

scarb/src/core/manifest/toml_manifest.rs

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ use crate::core::manifest::target_defaults::{
99
use crate::core::manifest::{
1010
DependencyGitPathAmbiguous, DependencyGitRefWithoutGit, DependencyGitReferenceAmbiguous,
1111
DependencyGitRegistryAmbiguous, DependencySourceMissing, DependencyWorkspaceNotFound,
12-
ManifestDependency, ManifestDependencyTable, ManifestDiagnosticAnchor, ManifestMetadata,
13-
ManifestSemanticError, PatchNotInWorkspaceRoot, PatchSourceConflict, PatchSourceInvalidUrl,
14-
ProfileCairoConflict, ProfileInheritanceInvalid, ProfileNameInvalid, Summary, Target,
12+
LicensePathInvalid, ManifestDependency, ManifestDependencyTable, ManifestDiagnosticAnchor,
13+
ManifestMetadata, ManifestSemanticError, PatchNotInWorkspaceRoot, PatchSourceConflict,
14+
PatchSourceInvalidUrl, ProfileCairoConflict, ProfileInheritanceInvalid, ProfileNameInvalid,
15+
ReadmePathInvalid, Summary, Target,
1516
};
1617
use crate::core::package::PackageId;
1718
use crate::core::registry::{DEFAULT_REGISTRY_INDEX, DEFAULT_REGISTRY_INDEX_PATCH_SOURCE};
@@ -339,7 +340,12 @@ impl PackageInheritableFields {
339340
get_field!(edition, Edition);
340341

341342
pub fn readme(&self, workspace_root: &Utf8Path, package_root: &Utf8Path) -> Result<PathOrBool> {
342-
let Ok(Some(readme)) = readme_for_package(workspace_root, self.readme.as_ref()) else {
343+
let readme = readme_for_package(
344+
workspace_root,
345+
self.readme.as_ref(),
346+
Some(ManifestDiagnosticAnchor::workspace_package_field("readme")),
347+
)?;
348+
let Some(readme) = readme else {
343349
bail!("`workspace.package.readme` was not defined");
344350
};
345351
diff_utf8_paths(
@@ -1275,13 +1281,25 @@ impl TomlManifest {
12751281
.clone()
12761282
.map(|mw| match mw {
12771283
MaybeWorkspace::Defined(license_rel_path) => {
1278-
abs_canonical_path("license", manifest_path, &license_rel_path)
1284+
let anchor = ManifestDiagnosticAnchor::package_field("license-file");
1285+
abs_canonical_path(manifest_path, &license_rel_path, |path| {
1286+
ManifestSemanticError::from(LicensePathInvalid::new(path, Some(anchor)))
1287+
.into()
1288+
})
12791289
}
12801290
MaybeWorkspace::Workspace(_) => mw.resolve("license_file", || {
1291+
let anchor =
1292+
ManifestDiagnosticAnchor::workspace_package_field("license-file");
12811293
abs_canonical_path(
1282-
"license",
12831294
workspace_manifest_path,
12841295
&inheritable_package.license_file()?,
1296+
|path| {
1297+
ManifestSemanticError::from(LicensePathInvalid::new(
1298+
path,
1299+
Some(anchor),
1300+
))
1301+
.into()
1302+
},
12851303
)
12861304
}),
12871305
})
@@ -1298,6 +1316,7 @@ impl TomlManifest {
12981316
})
12991317
.transpose()?
13001318
.as_ref(),
1319+
Some(ManifestDiagnosticAnchor::package_field("readme")),
13011320
)?,
13021321
repository: package
13031322
.repository
@@ -1884,6 +1903,7 @@ fn merge_profile(target: &TomlProfile, source: &TomlProfile) -> Result<TomlProfi
18841903
pub fn readme_for_package(
18851904
package_root: &Utf8Path,
18861905
readme: Option<&PathOrBool>,
1906+
anchor: Option<ManifestDiagnosticAnchor>,
18871907
) -> Result<Option<Utf8PathBuf>> {
18881908
let file_name = match readme {
18891909
None => default_readme_from_package_root(package_root.parent().unwrap()),
@@ -1896,16 +1916,27 @@ pub fn readme_for_package(
18961916
};
18971917

18981918
file_name
1899-
.map(|file_name| abs_canonical_path("readme", package_root, file_name))
1919+
.map(|file_name| {
1920+
abs_canonical_path(package_root, file_name, |path| match anchor.clone() {
1921+
Some(a) => {
1922+
ManifestSemanticError::from(ReadmePathInvalid::new(path, Some(a))).into()
1923+
}
1924+
None => anyhow!("failed to find readme at {path}"),
1925+
})
1926+
})
19001927
.transpose()
19011928
}
19021929

1903-
/// Creates the absolute canonical path of the file and checks if it exists
1904-
fn abs_canonical_path(file_label: &str, prefix: &Utf8Path, path: &Utf8Path) -> Result<Utf8PathBuf> {
1905-
let path = prefix.parent().unwrap().join(path);
1906-
let path = fsx::canonicalize_utf8(&path)
1907-
.with_context(|| format!("failed to find {file_label} at {path}"))?;
1908-
Ok(path)
1930+
/// Creates the absolute canonical path of the file and checks if it exists.
1931+
///
1932+
/// `make_error` is called with the resolved (non-canonical) path when the file is not found.
1933+
fn abs_canonical_path(
1934+
prefix: &Utf8Path,
1935+
path: &Utf8Path,
1936+
make_error: impl FnOnce(Utf8PathBuf) -> anyhow::Error,
1937+
) -> Result<Utf8PathBuf> {
1938+
let full_path = prefix.parent().unwrap().join(path);
1939+
fsx::canonicalize_utf8(&full_path).map_err(|_| make_error(full_path))
19091940
}
19101941

19111942
const DEFAULT_README_FILES: &[&str] = &["README.md", "README.txt", "README"];

0 commit comments

Comments
 (0)