Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 68 additions & 15 deletions src/cargo/util/frontmatter.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use crate::CargoResult;

type Span = std::ops::Range<usize>;

#[derive(Debug)]
Expand All @@ -22,7 +20,7 @@ pub struct ScriptSource<'s> {
}

impl<'s> ScriptSource<'s> {
pub fn parse(raw: &'s str) -> CargoResult<Self> {
pub fn parse(raw: &'s str) -> Result<Self, FrontmatterError> {
use winnow::stream::FindSlice as _;
use winnow::stream::Location as _;
use winnow::stream::Offset as _;
Expand Down Expand Up @@ -61,24 +59,30 @@ impl<'s> ScriptSource<'s> {
.char_indices()
.find_map(|(i, c)| (c != FENCE_CHAR).then_some(i))
.unwrap_or_else(|| input.eof_offset());
let open_start = input.current_token_start();
let fence_pattern = input.next_slice(fence_length);
let open_end = input.current_token_start();
match fence_length {
0 => {
return Ok(source);
}
1 | 2 => {
// either not a frontmatter or invalid frontmatter opening
anyhow::bail!(
"found {fence_length} `{FENCE_CHAR}` in rust frontmatter, expected at least 3"
)
return Err(FrontmatterError::new(
format!(
"found {fence_length} `{FENCE_CHAR}` in rust frontmatter, expected at least 3"
),
open_start..open_end,
));
}
_ => {}
}
let open_start = input.current_token_start();
let fence_pattern = input.next_slice(fence_length);
let open_end = input.current_token_start();
source.open = Some(open_start..open_end);
let Some(info_nl) = input.find_slice("\n") else {
anyhow::bail!("no closing `{fence_pattern}` found for frontmatter");
return Err(FrontmatterError::new(
format!("no closing `{fence_pattern}` found for frontmatter"),
open_start..open_end,
));
};
let info = input.next_slice(info_nl.start);
let info = info.trim_matches(is_whitespace);
Expand All @@ -91,7 +95,10 @@ impl<'s> ScriptSource<'s> {
// Ends with a line that starts with a matching number of `-` only followed by whitespace
let nl_fence_pattern = format!("\n{fence_pattern}");
let Some(frontmatter_nl) = input.find_slice(nl_fence_pattern.as_str()) else {
anyhow::bail!("no closing `{fence_pattern}` found for frontmatter");
return Err(FrontmatterError::new(
format!("no closing `{fence_pattern}` found for frontmatter"),
open_start..open_end,
));
};
let frontmatter_start = input.current_token_start() + 1; // skip nl from infostring
let _ = input.next_slice(frontmatter_nl.start + 1);
Expand All @@ -111,14 +118,29 @@ impl<'s> ScriptSource<'s> {
let after_closing_fence = after_closing_fence.trim_matches(is_whitespace);
if !after_closing_fence.is_empty() {
// extra characters beyond the original fence pattern, even if they are extra `-`
anyhow::bail!("trailing characters found after frontmatter close");
return Err(FrontmatterError::new(
format!("trailing characters found after frontmatter close"),
close_end..content_start,
));
}

source.content = content_start..content_end;

let repeat = Self::parse(source.content())?;
if repeat.frontmatter.is_some() {
anyhow::bail!("only one frontmatter is supported");
if let Some(nl_end) = strip_ws_lines(input.as_ref()) {
let _ = input.next_slice(nl_end);
}
let fence_length = input
.as_ref()
.char_indices()
.find_map(|(i, c)| (c != FENCE_CHAR).then_some(i))
.unwrap_or_else(|| input.eof_offset());
if 0 < fence_length {
let fence_start = input.current_token_start();
let fence_end = fence_start + fence_length;
return Err(FrontmatterError::new(
format!("only one frontmatter is supported"),
fence_start..fence_end,
));
}

Ok(source)
Expand Down Expand Up @@ -232,6 +254,37 @@ fn is_whitespace(c: char) -> bool {
)
}

#[derive(Debug)]
pub struct FrontmatterError {
message: String,
span: Span,
}

impl FrontmatterError {
pub fn new(message: impl Into<String>, span: Span) -> Self {
Self {
message: message.into(),
span,
}
}

pub fn message(&self) -> &str {
self.message.as_str()
}

pub fn span(&self) -> Span {
self.span.clone()
}
}

impl std::fmt::Display for FrontmatterError {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.message.fmt(fmt)
}
}

impl std::error::Error for FrontmatterError {}

#[cfg(test)]
mod test {
use snapbox::assert_data_eq;
Expand Down
22 changes: 14 additions & 8 deletions src/cargo/util/toml/embedded.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
use cargo_util_schemas::manifest::PackageName;

use crate::CargoResult;
use crate::util::frontmatter::FrontmatterError;
use crate::util::frontmatter::ScriptSource;
use crate::util::restricted_names;

pub(super) fn expand_manifest(content: &str) -> CargoResult<String> {
pub(super) fn expand_manifest(content: &str) -> Result<String, FrontmatterError> {
let source = ScriptSource::parse(content)?;
if let Some(span) = source.frontmatter_span() {
match source.info() {
Some("cargo") | None => {}
Some(other) => {
if let Some(remainder) = other.strip_prefix("cargo,") {
anyhow::bail!(
"cargo does not support frontmatter infostring attributes like `{remainder}` at this time"
)
return Err(FrontmatterError::new(
format!(
"cargo does not support frontmatter infostring attributes like `{remainder}` at this time"
),
source.info_span().unwrap(),
));
} else {
anyhow::bail!(
"frontmatter infostring `{other}` is unsupported by cargo; specify `cargo` for embedding a manifest"
)
return Err(FrontmatterError::new(
format!(
"frontmatter infostring `{other}` is unsupported by cargo; specify `cargo` for embedding a manifest"
),
source.info_span().unwrap(),
));
}
}
}
Expand Down
41 changes: 33 additions & 8 deletions src/cargo/util/toml/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,11 @@ pub fn read_manifest(
let mut errors = Default::default();

let is_embedded = is_embedded(path);
let contents = read_toml_string(path, is_embedded, gctx)
.map_err(|err| ManifestError::new(err, path.into()))?;
let document =
parse_document(&contents).map_err(|e| emit_diagnostic(e.into(), &contents, path, gctx))?;
let contents = read_toml_string(path, is_embedded, gctx)?;
let document = parse_document(&contents)
.map_err(|e| emit_toml_diagnostic(e.into(), &contents, path, gctx))?;
let original_toml = deserialize_toml(&document)
.map_err(|e| emit_diagnostic(e.into(), &contents, path, gctx))?;
.map_err(|e| emit_toml_diagnostic(e.into(), &contents, path, gctx))?;

let mut manifest = (|| {
let empty = Vec::new();
Expand Down Expand Up @@ -152,12 +151,13 @@ pub fn read_manifest(

#[tracing::instrument(skip_all)]
fn read_toml_string(path: &Path, is_embedded: bool, gctx: &GlobalContext) -> CargoResult<String> {
let mut contents = paths::read(path)?;
let mut contents = paths::read(path).map_err(|err| ManifestError::new(err, path.into()))?;
if is_embedded {
if !gctx.cli_unstable().script {
anyhow::bail!("parsing `{}` requires `-Zscript`", path.display());
}
contents = embedded::expand_manifest(&contents)?;
contents = embedded::expand_manifest(&contents)
.map_err(|e| emit_frontmatter_diagnostic(e, &contents, path, gctx))?;
}
Ok(contents)
}
Expand Down Expand Up @@ -2777,7 +2777,32 @@ fn lints_to_rustflags(lints: &manifest::TomlLints) -> CargoResult<Vec<String>> {
Ok(rustflags)
}

fn emit_diagnostic(
fn emit_frontmatter_diagnostic(
e: crate::util::frontmatter::FrontmatterError,
contents: &str,
manifest_file: &Path,
gctx: &GlobalContext,
) -> anyhow::Error {
let span = e.span();

// Get the path to the manifest, relative to the cwd
let manifest_path = diff_paths(manifest_file, gctx.cwd())
.unwrap_or_else(|| manifest_file.to_path_buf())
.display()
.to_string();
let group = Group::with_title(Level::ERROR.primary_title(e.message())).element(
Snippet::source(contents)
.path(manifest_path)
.annotation(AnnotationKind::Primary.span(span)),
);

if let Err(err) = gctx.shell().print_report(&[group], true) {
return err.into();
}
return AlreadyPrintedError::new(e.into()).into();
}

fn emit_toml_diagnostic(
e: toml::de::Error,
contents: &str,
manifest_file: &Path,
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
[ERROR] frontmatter infostring `.toml` is unsupported by cargo; specify `cargo` for embedding a manifest
--> script:1:4
|
1 | ---.toml
| ^^^^^
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
[ERROR] frontmatter infostring `Cargo.toml` is unsupported by cargo; specify `cargo` for embedding a manifest
--> script:1:4
|
1 | ---Cargo.toml
| ^^^^^^^^^^
6 changes: 6 additions & 0 deletions tests/testsuite/script/rustc_fixtures/extra-after-end.stderr
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
[ERROR] trailing characters found after frontmatter close
--> script:2:4
|
2 | ---cargo
| ____^
3 | | //~^ ERROR: extra characters after frontmatter close are not allowed
| |_^
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
[ERROR] no closing `---` found for frontmatter
--> script:1:1
|
1 | ---cargo
| ^^^
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
[ERROR] frontmatter infostring `-toml` is unsupported by cargo; specify `cargo` for embedding a manifest
--> script:1:5
|
1 | --- -toml
| ^^^^^
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
[ERROR] frontmatter infostring `Cargo-toml` is unsupported by cargo; specify `cargo` for embedding a manifest
--> script:1:5
|
1 | --- Cargo-toml
| ^^^^^^^^^^
4 changes: 4 additions & 0 deletions tests/testsuite/script/rustc_fixtures/infostring-fail.stderr
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
[ERROR] cargo does not support frontmatter infostring attributes like `clippy` at this time
--> script:1:4
|
1 | ---cargo,clippy
| ^^^^^^^^^^^^
6 changes: 6 additions & 0 deletions tests/testsuite/script/rustc_fixtures/mismatch-1.stderr
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
[ERROR] trailing characters found after frontmatter close
--> script:3:4
|
3 | ----
| ____^
4 | |
| |_^
4 changes: 4 additions & 0 deletions tests/testsuite/script/rustc_fixtures/mismatch-2.stderr
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
[ERROR] no closing `----` found for frontmatter
--> script:1:1
|
1 | ----cargo
| ^^^^
4 changes: 4 additions & 0 deletions tests/testsuite/script/rustc_fixtures/multifrontmatter.stderr
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
[ERROR] only one frontmatter is supported
--> script:4:1
|
4 | ---
| ^^^
4 changes: 4 additions & 0 deletions tests/testsuite/script/rustc_fixtures/unclosed-1.stderr
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
[ERROR] no closing `----` found for frontmatter
--> script:1:1
|
1 | ----cargo
| ^^^^
4 changes: 4 additions & 0 deletions tests/testsuite/script/rustc_fixtures/unclosed-2.stderr
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
[ERROR] no closing `----` found for frontmatter
--> script:1:1
|
1 | ----cargo
| ^^^^
4 changes: 4 additions & 0 deletions tests/testsuite/script/rustc_fixtures/unclosed-3.stderr
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
[ERROR] no closing `----` found for frontmatter
--> script:1:1
|
1 | ----cargo
| ^^^^
4 changes: 4 additions & 0 deletions tests/testsuite/script/rustc_fixtures/unclosed-4.stderr
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
[ERROR] no closing `----` found for frontmatter
--> script:1:1
|
1 | ----cargo
| ^^^^
4 changes: 4 additions & 0 deletions tests/testsuite/script/rustc_fixtures/unclosed-5.stderr
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
[ERROR] no closing `----` found for frontmatter
--> script:1:1
|
1 | ----cargo
| ^^^^
Loading