Skip to content

feat: Ruby module/include (MixIn) support with cross-sprite sync#296

Merged
takaokouji merged 14 commits intodevelopfrom
feature/module-include
Mar 14, 2026
Merged

feat: Ruby module/include (MixIn) support with cross-sprite sync#296
takaokouji merged 14 commits intodevelopfrom
feature/module-include

Conversation

@takaokouji
Copy link

@takaokouji takaokouji commented Mar 13, 2026

Summary

Ruby の module / include (MixIn) をサポートし、複数スプライトでメソッド定義を共有できるようにする。Ruby version 2 専用。

Closes #295

Implementation Steps

  • Phase 1: visitModuleNode + include → Blocks
  • Phase 2: Blocks → Ruby(単一ターゲット)
  • Phase 3: モジュール同期
  • Phase 4: ファイル出力の重複排除
  • Phase 5: エラーハンドリング + v1 切替防止
  • Phase Final: Integration Tests

Changes Made

Phase 1: Ruby → Blocks (Converter)

  • visitModuleNode: Parse module definitions, store method ASTs in context
  • visitClassNode: Handle include ModuleName, expand module methods as procedures_definition blocks with @ruby:module_source:ModuleName comments
  • Class comment includes include=ModuleName for ordering
  • Error handling: v1, nested modules, undefined modules, non-method in module

Phase 2: Blocks → Ruby (Generator)

  • procedure.js: Detect @ruby:module_source comment, store code in _moduleMethodCodes, suppress from main output
  • finish(): Generate module...end blocks before class definition
  • _wrapWithClass(): Parse include= from class comment, generate include statements inside class

Phase 3: Module Sync

  • module-sync.js: Cross-sprite module sync after Ruby→Blocks conversion
  • Detects modules in source target, finds other targets with same module, replaces module code
  • Fix: Removed withSpriteNew: true from generateTargetCode to prevent costume validation errors during reconversion

Phase 4: File Output Deduplication

  • finishTargets(): Regex-based module extraction and deduplication across targets

Phase 5: Error Handling + v1 Switch Prevention

  • module_function and extend error detection in converter
  • Prevent switching to Ruby v1 when v2 features (module/class) are in use
  • Added cannotSwitchToV1 alert message

Test Coverage

  • 11 unit tests for converter (module.test.js)
  • 7 unit tests for generator (module.test.js)
  • 12 unit tests for module-sync (module-sync.test.js)
  • 5 integration tests for round-trip and sync (ruby-module.test.js)
  • All existing tests pass

DoD Verification (Playwright MCP)

Test Status
1 sprite with module method ✅ PASS
2 sprites with module ✅ PASS
3 sprites with module ✅ PASS
Add method → sync to other sprite ✅ PASS
Add args to method → sync ⚠️ Expected: call sites need manual update
Delete method → sync removes definition & calls ✅ PASS

takaokouji and others added 3 commits March 14, 2026 00:53
Add visitModuleNode to parse Ruby module definitions and store method
ASTs in context. Modify visitClassNode to handle include statements,
expanding module methods as procedures_definition blocks with
@ruby:module_source:ModuleName comments. Add error handling for v1,
nested modules, undefined modules, and non-method statements in modules.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
In procedure.js, detect @ruby:module_source:ModuleName comments on
procedures_definition blocks, store the generated code separately in
_moduleMethodCodes, and suppress from main output. In finish(), generate
module...end blocks before the class definition. In _wrapWithClass(),
parse include=ModuleName from class comment and generate include
statements inside the class body.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Create module-sync.js with utilities to detect module changes,
find affected sprites, and sync module definitions by regenerating
Ruby code, replacing the module definition, and re-converting.
Integrate sync into ruby-tab.jsx after code-to-blocks conversion
(v2 only).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link

takaokouji and others added 11 commits March 14, 2026 01:07
In finishTargets(), extract all module...end blocks from the combined
multi-target code, keep unique ones, and place them once before the
class definitions. This prevents duplicate module definitions when
multiple sprites include the same module.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add module_function and extend error detection in converter.
Prevent switching to Ruby v1 when v2 features (module/class) are in use.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The generateTargetCode function in module-sync.js was using
withSpriteNew: true, which caused the generated Ruby to include
set_costumes/set_sounds calls that fail validation during
reconversion. Removing this option generates clean class definitions
that can be successfully re-converted.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Round-trip tests: single method, multiple methods, multiple modules,
  no-argument method
- Sync test: verifies adding a method to a module in one sprite
  propagates the new method definition to another sprite

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ude on stage

- When a sprite uses `include Mod` without defining the module locally,
  the converter automatically searches other sprites for `@ruby:module_source:Mod`
  comments, generates Ruby from that sprite, extracts the module definition,
  parses it, and makes it available for include expansion.
- Module definitions and include statements are now blocked on Stage targets
  since stage and sprite have different available methods.
- Added Japanese locale translations for all module-related error messages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…d modules

When using the execute button on the Ruby tab, the editor content was not
updated to reflect auto-imported modules. This adds a call to
updateRubyCodeTargetState after block application so the editor immediately
shows the full module definition obtained from other sprites.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…andler

The updateRubyCodeTargetState call was placed after the block ID lookup,
which could be skipped via early return when the cursor line has no
executable block (e.g. class declaration). Moving it to right after
converter.apply() ensures the editor always reflects auto-imported modules.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Redux-based updateRubyCodeTargetState approach did not reliably update
the Monaco editor content within the execute callback because
@monaco-editor/react value prop changes may not be applied in the same
render cycle. Instead, directly call editorRef.current.setValue() with
the regenerated Ruby code from RubyGenerator.targetToCode().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When clicking "カーソル行を実行" and a module is auto-imported,
setValue() resets the cursor to line 1. This fix remembers the
cursor line content before setValue and restores the cursor to the
matching line in the regenerated code, then scrolls it to center.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add furigana annotations: module→モジュール作成, end→作成終了, include→取り込む
- Update smalruby-language-spec.md to document module/include (Version 2)
- Update Gemini system prompt to include module/include methods and examples
- Update furigana-mapping.md with new entries
- Add 4 unit tests for module/include furigana

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Gemini was ignoring the system prompt and incorrectly stating that
module/include is not supported. Added stronger emphasis and a sample
program to ensure correct code generation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@takaokouji takaokouji merged commit f8ef321 into develop Mar 14, 2026
13 of 14 checks passed
@takaokouji takaokouji deleted the feature/module-include branch March 14, 2026 05:31
github-actions bot pushed a commit that referenced this pull request Mar 14, 2026
…e-include

feat: Ruby module/include (MixIn) support with cross-sprite sync
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.

feat: Ruby module/include (MixIn) support with cross-sprite sync

1 participant