Skip to content

Commit f1264cd

Browse files
Fix gem source-block detection for multi-line and brace openers (#1509) (#1520)
* Start fix for #1509 Assisted-by: Claude Code:claude-opus-5-5 * Keep gem source pins for multi-line and {} blocks Hosted rollback and remove dropped the `!` source pin from a gem declared inside a `source(...)` block whose arguments span lines, or inside a `source("...") { ... }` brace block. Bundler 4.0.19+ then refused every frozen install of the restored Gemfile.lock, and the brace form also came back with the gem at the wrong indent. The Gemfile block scanner now keeps a statement open while a `(` or `[` is unclosed, remembers that the statement began with `source` until its `do` or `{`, and tracks `{ }` blocks alongside `do ... end`. Fixes #1509 Assisted-by: Claude Code:claude-opus-5-5 * Tighten gem source-block brace and paren rules A brace block now counts as the source's own block only right after the `source(...)` argument list, so `source ENV.fetch(...) { ... } do` keeps its `!` pin. Hash braces keep a statement open like parens, a stray `}` no longer closes a `do` block, and `?(`-style character literals are no longer read as brackets. A restored first gem in a multi-line `source(` block no longer gets a blank line after the opener. Only the end of an unfinished line is carried forward, so a long open bracket no longer costs quadratic time. Refs #1509 Assisted-by: Claude Code:claude-opus-5-5 * Keep keyword blocks inside brackets in gem scan An `if` at the start of a line inside an open `(`, `[` or hash was read as a modifier, so it opened nothing while its `end` still closed the enclosing `source` block, and the gem lost its `!` pin on rollback or remove. A stabby lambda's `-> {` was read as a hash. A line that leaves only a bracket open now starts a fresh expression on the next line, `-> {` opens a block, and an open bracket carries only a `source` statement on to its block, so a bracket the scanner misses elsewhere no longer hides every later source block. Refs #1509 Assisted-by: Claude Code:claude-opus-5-5 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 313d532 commit f1264cd

2 files changed

Lines changed: 250 additions & 37 deletions

File tree

‎crates/socket-patch-cli/tests/e2e_redirect_gem_build.rs‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,12 @@ enum Driver {
440440
/// constructs (`v = if … end`, `w = case … end`) before the gem
441441
/// (#1476): their `end`s must not close the user's source block.
442442
ScanVexSourceBlockAssignment,
443+
/// [`Driver::ScanVexSourceBlock`] whose `source(` opener spans lines
444+
/// before its `do` (#1509).
445+
ScanVexSourceBlockMultiLineOpener,
446+
/// [`Driver::ScanVexSourceBlock`] as a `source(…) { … }` brace block
447+
/// (#1509): bundler pins it with `!` too.
448+
ScanVexSourceBlockBraces,
443449
/// A heredoc option continues beyond the declaration's physical line.
444450
ScanVexHeredocDeclaration,
445451
/// A double-quoted interpolation can itself contain a heredoc opener.
@@ -533,6 +539,12 @@ impl Driver {
533539
Driver::ScanVexSourceBlockAssignment => {
534540
"scan --mode hosted (gem in a source block after `v = if … end`)"
535541
}
542+
Driver::ScanVexSourceBlockMultiLineOpener => {
543+
"scan --mode hosted (gem in a multi-line `source(…) do` block)"
544+
}
545+
Driver::ScanVexSourceBlockBraces => {
546+
"scan --mode hosted (gem in a `source(…) { }` block)"
547+
}
536548
Driver::ScanVexSemicolonJoinedDeclaration => {
537549
"scan --mode hosted (two `;`-joined gem declarations)"
538550
}
@@ -838,6 +850,16 @@ async fn redirect_scanned_project(
838850
else \"b\"\n end\n gem \"{DEP}\", \"{DEP_VERSION}\"\nend\n",
839851
up = server.uri()
840852
),
853+
Driver::ScanVexSourceBlockMultiLineOpener => format!(
854+
"source \"{up}/upstream\"\n\nsource(\n \"{up}/upstream\"\n) do\n \
855+
gem \"{DEP}\", \"{DEP_VERSION}\"\nend\n",
856+
up = server.uri()
857+
),
858+
Driver::ScanVexSourceBlockBraces => format!(
859+
"source \"{up}/upstream\"\n\nsource(\"{up}/upstream\") {{\n \
860+
gem \"{DEP}\", \"{DEP_VERSION}\"\n}}\n",
861+
up = server.uri()
862+
),
841863
Driver::ScanVexEvalGemfile => {
842864
std::fs::write(proj.join("Gemfile.common"), format!("gem \"{DEP}\"\n")).unwrap();
843865
format!(
@@ -1098,6 +1120,8 @@ async fn redirect_scanned_project(
10981120
| Driver::ScanVexGroupBlock
10991121
| Driver::ScanVexSourceBlock
11001122
| Driver::ScanVexSourceBlockAssignment
1123+
| Driver::ScanVexSourceBlockMultiLineOpener
1124+
| Driver::ScanVexSourceBlockBraces
11011125
| Driver::ScanVexCustomGitSource
11021126
| Driver::ScanVexMultiLineDeclaration
11031127
| Driver::ScanVexConditionalDeclaration
@@ -1303,6 +1327,8 @@ async fn redirect_scanned_project(
13031327
| Driver::ScanVexGroupBlock
13041328
| Driver::ScanVexSourceBlock
13051329
| Driver::ScanVexSourceBlockAssignment
1330+
| Driver::ScanVexSourceBlockMultiLineOpener
1331+
| Driver::ScanVexSourceBlockBraces
13061332
| Driver::ScanVexTrailingSemicolonDeclaration => {
13071333
assert_eq!(env["vex"]["statements"], 1, "vex block: {env}");
13081334
assert!(
@@ -2373,6 +2399,8 @@ async fn gem_hosted_group_block_pin_survives_a_refused_vendored_takeover() {
23732399
/// Converged without CHECKSUMS so the restore needs no rubygems.org sha.
23742400
/// #1476: the same holds when assignment-form `v = if … end` /
23752401
/// `w = case … end` constructs precede the gem inside the block.
2402+
/// #1509: and when the block's `source(` opener spans lines, or the block
2403+
/// is a `source(…) { … }` brace block.
23762404
#[tokio::test(flavor = "multi_thread")]
23772405
#[ignore = "host capstone: shells out to a real ruby/gem/bundler (>= 2.2); \
23782406
the unpinned `test` job skips it, an e2e job with a pinned toolchain runs it via --ignored"]
@@ -2382,9 +2410,15 @@ async fn gem_hosted_unwind_keeps_a_source_block_bang() {
23822410
(Driver::ScanVexSourceBlock, "remove"),
23832411
(Driver::ScanVexSourceBlockAssignment, "rollback"),
23842412
(Driver::ScanVexSourceBlockAssignment, "remove"),
2413+
(Driver::ScanVexSourceBlockMultiLineOpener, "rollback"),
2414+
(Driver::ScanVexSourceBlockMultiLineOpener, "remove"),
2415+
(Driver::ScanVexSourceBlockBraces, "rollback"),
2416+
(Driver::ScanVexSourceBlockBraces, "remove"),
23852417
] {
23862418
let label = match driver {
23872419
Driver::ScanVexSourceBlockAssignment => "source-block-assignment",
2420+
Driver::ScanVexSourceBlockMultiLineOpener => "source-block-multi-line-opener",
2421+
Driver::ScanVexSourceBlockBraces => "source-block-braces",
23882422
_ => "source-block",
23892423
};
23902424
let Some(fx) = redirect_scanned_project(

0 commit comments

Comments
 (0)