Skip to content

refactor: introduce formal RSpock AST with parse-transform pipeline#5

Merged
JPDuchesne merged 4 commits intomasterfrom
jpd/ast-refactor
Feb 27, 2026
Merged

refactor: introduce formal RSpock AST with parse-transform pipeline#5
JPDuchesne merged 4 commits intomasterfrom
jpd/ast-refactor

Conversation

@JPDuchesne
Copy link
Copy Markdown
Contributor

@JPDuchesne JPDuchesne commented Feb 27, 2026

Summary

  • Restructure blocks into RSpock::AST::Parser namespace — Move block accumulators (Block, GivenBlock, WhenBlock, etc.) under Parser, convert InteractionParser from module to class, eliminate StartBlock/EndBlock in favor of declarative can_start?/can_end? on each block, and rename source_map to block_registry.

  • Introduce formal RSpock AST node hierarchy — Create RSpock::AST::Node subclasses (TestNode, DefNode, BodyNode, WhereNode, InteractionNode, etc.) backed by a type registry and NodeBuilder module, extract TestMethodParser to own the parse phase, and make TestMethodTransformation stateless (operating on TestNode → Ruby AST). TestNode uses positional children [DefNode, BodyNode, WhereNode?] for clean structural separation.

  • Add interaction transformations and block identity verification — Implement InteractionToMochaMockTransformation and InteractionToBlockIdentityAssertionTransformation operating on InteractionNode, add RSpock::Helpers::BlockCapture for runtime block capture, delete legacy InteractionTransformation (which violated the AbstractTransformation contract) and InteractionBlockAssertionTransformation (redundant abstraction), and port all missing test coverage.

  • Block forwarding verification in interactions — When testing code that forwards a block to a collaborator (e.g. a method wrapping a UI frame or transaction), there was previously no way to verify that the correct block was actually passed through. This PR introduces &var syntax in interactions to assert block identity:

    test "forwards the block to the collaborator" do
      Given
      my_proc = -> { "work" }
    
      When
      subject.do_work(&my_proc)
    
      Then
      1 * @collaborator.perform(&my_proc)
    end

    This transforms into a Mocha expectation plus a BlockCapture that intercepts the block at runtime, followed by an assert_same to verify the exact proc was forwarded — not just that a block was passed, but that the right one was. Inline blocks (do...end / { }) are explicitly rejected with a clear error, since there is no external reference to compare identity against.

Test plan

  • Full test suite passes (182 tests, 266 assertions, 0 failures, 0 errors)
  • Verify CI passes on all supported Ruby versions

…ctionParser to class

Move all block classes (Given, When, Then, Expect, Cleanup, Where) from
RSpock::AST to RSpock::AST::Parser, making them parser-phase constructs
rather than top-level AST components. Tests moved accordingly.

Convert InteractionParser from a module with class methods to a proper
class with instance methods for better encapsulation.

Introduce can_start?/can_end? on Block subclasses as declarative boundary
checks, eliminating the need for StartBlock/EndBlock sentinel constructs.
Clean up Block base class by removing dead code (unshift, node_container).

Include NodeBuilder in Block and InteractionParser so they produce
RSpock AST nodes transparently via the s() helper override.

Made-with: Cursor
…arser

Create a formal RSpock AST with typed node subclasses (TestNode, DefNode,
GivenNode, WhenNode, ThenNode, ExpectNode, CleanupNode, WhereNode,
InteractionNode) under RSpock::AST::Node, each with named accessors for
their positional children. Override the s() helper via NodeBuilder to
transparently construct the correct subclass for rspock_* types.

Extract parsing logic from TestMethodTransformation into a dedicated
TestMethodParser class, establishing a clean parse-then-transform pipeline.
TestMethodTransformation now receives a fully formed RSpock AST (TestNode)
and focuses solely on transformation.

Rename source_map to block_registry (DEFAULT_BLOCK_REGISTRY) to accurately
reflect its role as a keyword-to-block-class mapping, not source mapping.

Made-with: Cursor
Split interaction handling into focused, single-responsibility transformers:
- InteractionToMochaMockTransformation: converts interaction nodes to Mocha
  expects/stubs with cardinality, args, and return value support
- InteractionToBlockIdentityAssertionTransformation: generates assert_same
  checks for &var block forwarding verification
- InteractionBlockAssertionTransformation: detects interaction nodes within
  Then/Expect blocks and delegates to the above transformers

Add BlockCapture helper module for capturing blocks passed to both mocked
and real objects, enabling verification that the correct block was forwarded
to collaborators.

Support >> operator in interactions for stubbing return values, with
top-level-only transformation to avoid rewriting nested >> calls.

Document interaction syntax, block verification, and >> stubbing in README.

Made-with: Cursor
…tions

Restructure TestNode to use positional children [DefNode, BodyNode,
WhereNode?] for cleaner separation of test definition, body blocks,
and parameterization. Delete legacy InteractionTransformation (which
violated the AbstractTransformation contract by returning a non-Node
Result struct) and InteractionBlockAssertionTransformation (redundant
abstraction), porting their missing test coverage to the RSpock
AST-based transformations.

Made-with: Cursor
@JPDuchesne JPDuchesne merged commit 5d8262b into master Feb 27, 2026
3 checks passed
@codecov-commenter
Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.94068% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...ck/ast/interaction_to_mocha_mock_transformation.rb 96.55% 2 Missing ⚠️
lib/rspock/ast/parser/interaction_parser.rb 96.29% 2 Missing ⚠️
lib/rspock/ast/parser/test_method_parser.rb 98.07% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants