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
5 changes: 5 additions & 0 deletions .changeset/quoted-package-names.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
changesets: major
---

Strip a matching pair of single or double quotes from package names when parsing change files. Quoted package names now produce the unquoted package name in `Change::versioning`.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ The part of a changeset that applies to a single package and determines how that
Change files are Markdown files whose names _must_ end with `.md`. The content of the file must be as follows:

1. A line containing `---` (three dashes) on its own line.
2. Any number of lines containing `package: change type` pairs where `package` defines a package that this change impacts and `change type` is a [change type](#change-type). One pair per line. The first `:` is used to determine the separation between package and change type, so the package name may not contain a `:`.
2. Any number of lines containing `package: change type` pairs where `package` defines a package that this change impacts and `change type` is a [change type](#change-type). One pair per line. The first `:` is used to determine the separation between package and change type, so the package name may not contain a `:`. Package names may be enclosed in a matching pair of single or double quotes; the quotes are removed when parsing.
3. A line containing `---` (three dashes) on its own line.
4. The rest of the file can contain any valid Markdown text.

Expand Down
47 changes: 31 additions & 16 deletions src/change.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,24 +78,39 @@ impl Change {
if first_line.trim() != "---" {
return Err(ParsingError::MissingFrontMatter);
}
let versioning_iter = lines
.clone()
.take_while(|line| line.trim() != "---")
.map(|line| {
let parts = line
.split_once(':')
.ok_or(ParsingError::InvalidFrontMatter)?;
let package_name = PackageName::from(parts.0.trim());
let change_type = ChangeType::from(parts.1.trim());
Ok((package_name, change_type))
})
.collect::<Result<Vec<(String, ChangeType)>, ParsingError>>()?;
let versioning = Versioning::try_from_iter(versioning_iter)?;
let mut lines = lines.skip(versioning.len());
let end_front_matter = lines.next().ok_or(ParsingError::InvalidFrontMatter)?;
if end_front_matter.trim() != "---" {
let mut versioning_iter = Vec::new();
let mut end_front_matter = false;
for line in lines.by_ref() {
if line.trim() == "---" {
end_front_matter = true;
break;
}
let parts = line
.split_once(':')
Comment thread
dbanty marked this conversation as resolved.
Resolved
.ok_or(ParsingError::InvalidFrontMatter)?;
let package_name = parts.0.trim();
let package_name = if let Some(inner) = package_name
.strip_prefix('\'')
.and_then(|name| name.strip_suffix('\''))
{
inner
} else if let Some(inner) = package_name
.strip_prefix('"')
.and_then(|name| name.strip_suffix('"'))
{
inner
} else {
package_name
};
versioning_iter.push((
PackageName::from(package_name),
ChangeType::from(parts.1.trim()),
));
}
if !end_front_matter {
return Err(ParsingError::InvalidFrontMatter);
}
let versioning = Versioning::try_from_iter(versioning_iter)?;
let summary = lines
.skip_while(|line| line.trim().is_empty())
.collect::<Vec<_>>()
Expand Down
37 changes: 37 additions & 0 deletions tests/change.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,40 @@ fn load_change() {
Versioning::from(("my_package", ChangeType::Minor))
);
}

#[test]
fn load_quoted_package_names() {
for (key, expected) in [
(" '@scope/package' ", "@scope/package"),
("\"@scope/package\"", "@scope/package"),
("'unmatched", "'unmatched"),
("'package\"", "'package\""),
("package'embedded", "package'embedded"),
("'embedded'quote'", "embedded'quote"),
] {
let content = format!("---\n{key}: patch\n---\n\nSummary\n");
let change = Change::from_file_name_and_content("test.md", &content).unwrap();
assert!(
change
.versioning
.iter()
.any(|(name, kind)| { name == expected && *kind == ChangeType::Patch })
);
assert_eq!(change.summary, "Summary");
}
}

#[test]
fn normalized_duplicate_keys_keep_last_value_and_summary() {
Comment thread
dbanty marked this conversation as resolved.
Resolved
let change = Change::from_file_name_and_content(
"test.md",
"---\n'package': patch\npackage: major\n---\n\nSummary\n",
)
.unwrap();
assert_eq!(change.versioning.len(), 1);
assert_eq!(
change.versioning.iter().next().unwrap(),
(&String::from("package"), &ChangeType::Major)
);
assert_eq!(change.summary, "Summary");
}
20 changes: 20 additions & 0 deletions tests/change_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,23 @@ fn load_changeset() {
second_release.changes
);
}

#[test]
fn quoted_and_unquoted_package_names_share_a_release() {
let dir = tempdir().unwrap();
std::fs::write(
dir.path().join("quoted.md"),
"---\n'@scope/package': minor\n---\n\nQuoted summary\n",
)
.unwrap();
std::fs::write(
dir.path().join("unquoted.md"),
"---\n@scope/package: patch\n---\n\nUnquoted summary\n",
)
.unwrap();

let releases: Vec<Release> = ChangeSet::from_directory(&dir).unwrap().into();
assert_eq!(releases.len(), 1);
assert_eq!(releases[0].package_name, "@scope/package");
assert_eq!(releases[0].changes.len(), 2);
}
Loading