Skip to content
Open
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
76 changes: 76 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ pub struct GoMod {
pub exclude: Vec<ModuleDependency>,
pub replace: Vec<ModuleReplacement>,
pub retract: Vec<ModuleRetract>,
pub ignore: Vec<String>,
}
Comment on lines 53 to 57
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding public ignore: Vec<String> to GoMod makes GoMod { ... } construction non-exhaustive, so downstream crates will fail to compile unless they set it too — should we treat this as a breaking change or hide direct construction with a builder/#[non_exhaustive] + accessors?

Finding type: Breaking Changes | Severity: 🟠 Medium


Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In src/lib.rs around
lines 45-57, the GoMod struct adds a new public field `pub ignore: Vec<String>`, which
breaks downstream crates using `GoMod { ... }` exhaustive struct literals because they
now must include `ignore`. Refactor the API to restore source compatibility by either
marking GoMod with `#[non_exhaustive]` and making `ignore` non-public (or replacing it
with a private field plus a public accessor like `ignore()`), and/or introducing a
dedicated constructor/builder so external code doesn’t need to construct the struct
via a struct literal. Keep FromStr behavior the same (still parsing Ignore directives)
but update any internal/tests to use the new accessor where needed.


impl std::str::FromStr for GoMod {
Expand All @@ -73,6 +74,7 @@ impl std::str::FromStr for GoMod {
Directive::Exclude(d) => res.exclude.append(d),
Directive::Replace(d) => res.replace.append(d),
Directive::Retract(d) => res.retract.append(d),
Directive::Ignore(d) => res.ignore.append(d),
}
}

Expand Down Expand Up @@ -284,6 +286,80 @@ mod tests {
);
}

#[test]
fn test_ignore_single() {
let input = indoc! {r#"
module github.com/ignore-single

go 1.24

ignore ./testdata
"#};

let go_mod = GoMod::from_str(input).unwrap();

assert_eq!(go_mod.ignore, vec!["./testdata".to_string()]);
}

#[test]
fn test_ignore_multi() {
let input = indoc! {r#"
module github.com/ignore-multi

go 1.24

ignore (
./testdata
./vendor/temp
./node_modules
)
"#};

let go_mod = GoMod::from_str(input).unwrap();

assert_eq!(
go_mod.ignore,
vec![
"./testdata".to_string(),
"./vendor/temp".to_string(),
"./node_modules".to_string(),
]
);
}

#[test]
fn test_ignore_repeated_singles() {
let input = indoc! {r#"
module github.com/ignore-repeated

go 1.24

ignore ./testdata
ignore ./vendor/temp
"#};

let go_mod = GoMod::from_str(input).unwrap();

assert_eq!(
go_mod.ignore,
vec!["./testdata".to_string(), "./vendor/temp".to_string()]
);
}

#[test]
fn test_no_line_ending_after_ignore() {
let input = indoc! {r#"
module github.com/no-line-ending

ignore (
./testdata
)"#};

let go_mod = GoMod::from_str(input).unwrap();

assert_eq!(go_mod.ignore, vec!["./testdata".to_string()]);
}

#[test]
fn test_comments() {
let input = indoc! {r#"
Expand Down
37 changes: 37 additions & 0 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub(crate) enum Directive<'a> {
Exclude(Vec<ModuleDependency>),
Replace(Vec<ModuleReplacement>),
Retract(Vec<ModuleRetract>),
Ignore(Vec<String>),
}

pub(crate) fn gomod<'a>(input: &mut &'a str) -> Result<Vec<Directive<'a>>> {
Expand All @@ -44,6 +45,7 @@ fn directive<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
"exclude" => exclude,
"replace" => replace,
"retract" => retract,
"ignore" => ignore,
_ => fail,
)
.parse_next(input)
Expand Down Expand Up @@ -294,3 +296,38 @@ fn retract_multi(input: &mut &str) -> Result<Vec<ModuleRetract>> {

Ok(res.into_iter().flatten().collect::<Vec<ModuleRetract>>())
}

fn ignore<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
let res = preceded(
("ignore", space1),
dispatch! {peek(any);
'(' => ignore_multi,
_ => ignore_single,
},
)
.parse_next(input)?;
let _ = take_while(0.., CRLF).parse_next(input)?;

Ok(Directive::Ignore(res))
}

fn ignore_single(input: &mut &str) -> Result<Vec<String>> {
// terminate, if `)` is found
peek(not(')')).parse_next(input)?;

let path = take_till(1.., WHITESPACES).parse_next(input)?;

// remove any comments added to the same line
let _ = opt(comment).parse_next(input)?;

Ok(vec![path.to_string()])
}

fn ignore_multi(input: &mut &str) -> Result<Vec<String>> {
let _ = ("(", multispace1).parse_next(input)?;
let res: Vec<Vec<String>> =
repeat(1.., terminated(ignore_single, multispace0)).parse_next(input)?;
let _ = (")", multispace0).parse_next(input)?;
Comment on lines +326 to +330
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ignore_multi duplicates the ( ... ) block parsing shared by require_multi, replace_multi, etc. — should we extract a helper? Also, ignore_single calls take_till before opt(comment), so whole-line // comments inside ignore (...) get parsed as bogus path entries; should we detect and consume comment lines first, like the Go module parser does?

Finding types: Code Dedup and Conventions Logical Bugs | Severity: 🟠 Medium


Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
In src/parser.rs around lines 326-333, fix `ignore_multi()`/`ignore_single()` so
whole-line `// ...` comments inside `ignore (...)` blocks are consumed instead of being
parsed as path entries. In `ignore_single` (lines 314-324), detect and consume a comment
at the start of the line (after optional whitespace) and return an empty result, rather
than calling `take_till(..., WHITESPACES)` before `opt(comment)`. Ensure
`ignore_multi`'s `repeat(…, terminated(ignore_single, multispace0))` still skips
newlines/spaces after comment lines. Additionally, consider extracting the shared `(`
... `)` repetition and `<single>` parser pattern into a helper reused by
`require_multi`, `replace_multi`, `retract_multi`, `godebug_multi`, and `ignore_multi`.


Ok(res.into_iter().flatten().collect::<Vec<String>>())
}
11 changes: 11 additions & 0 deletions tests/data/ignore.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module github.com/example/ignore

go 1.24

ignore ./build

ignore (
./testdata
./vendor/temp
./node_modules
)
17 changes: 17 additions & 0 deletions tests/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,23 @@ fn test_parse_tool() {
);
}

#[test]
fn test_parse_ignore() {
let file_content = get_test_file_content("ignore.mod");
let gomod = file_content.parse::<GoMod>().unwrap();

assert_eq!(gomod.module, "github.com/example/ignore".to_string());
assert_eq!(
gomod.ignore,
vec![
"./build".to_string(),
"./testdata".to_string(),
"./vendor/temp".to_string(),
"./node_modules".to_string(),
]
);
}

#[test]
fn test_carriage_return() {
let file_content = get_test_file_content("compress.mod")
Expand Down