Skip to content

fix: stale references and lost Gemfile pins after Pro migration (#3104)#3232

Open
justin808 wants to merge 30 commits into
mainfrom
codex/doctor-pro-module-specifiers
Open

fix: stale references and lost Gemfile pins after Pro migration (#3104)#3232
justin808 wants to merge 30 commits into
mainfrom
codex/doctor-pro-module-specifiers

Conversation

@justin808
Copy link
Copy Markdown
Member

@justin808 justin808 commented Apr 30, 2026

Summary

  • Rewrite jest.mock / vi.mock / requireActual / importActual / etc. helpers and TypeScript declare module 'react-on-rails' augmentations during rails generate react_on_rails:pro, alongside the existing import / require / dynamic-import handling. Test files and type stubs that reference the base package no longer get left behind pointing at 'react-on-rails' after the migration.
  • Preserve the user's Gemfile version pin (and other gem options like path:, git:, require:) when swapping react_on_rails for react_on_rails_pro. Previously the swap overwrote the user's pin with whichever version the running generator gem happened to be.
  • Widen react_on_rails:doctor to flag the same patterns the rewriter handles, plus side-effect imports (import 'react-on-rails';). The doctor's coverage is a superset of the rewriter's, so any base-package references that the rewriter can't reach (hand-written code added post-migration, third-party type stubs, codebases migrated manually) still surface as warnings.

Closes #3104.

Test Plan

  • bundle exec rspec react_on_rails/spec/lib/react_on_rails/doctor_spec.rb react_on_rails/spec/react_on_rails/generators/pro_generator_spec.rb
  • bundle exec rubocop react_on_rails/lib/react_on_rails/doctor.rb react_on_rails/lib/generators/react_on_rails/pro_generator.rb react_on_rails/lib/generators/react_on_rails/pro_setup.rb react_on_rails/spec/lib/react_on_rails/doctor_spec.rb react_on_rails/spec/react_on_rails/generators/pro_generator_spec.rb
  • End-to-end: create an OSS Rails app with gem "react_on_rails", "~> 16.0", plant jest.mock('react-on-rails'), declare module 'react-on-rails', and import 'react-on-rails'; references, run bundle exec rails generate react_on_rails:pro, confirm all references are rewritten and the version pin is preserved.

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 30, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Replaces the import-only scan in react_on_rails:doctor with a broader "base package reference" scan that detects CommonJS require, Jest/Vitest mock helpers, and TypeScript declare module blocks; updates messaging, tests, and CHANGELOG accordingly.

Changes

Base Package Reference Detection Enhancement

Layer / File(s) Summary
Callsite Update
react_on_rails/lib/react_on_rails/doctor.rb
check_pro_setup now calls check_base_package_references instead of check_base_package_imports.
Data / Patterns
react_on_rails/lib/react_on_rails/doctor.rb
Added BASE_PACKAGE_MOCK_PATTERN, BASE_PACKAGE_DECLARE_MODULE_PATTERN, and aggregated BASE_PACKAGE_REFERENCE_PATTERNS; retained prior BASE_PACKAGE_IMPORT_PATTERN and BASE_PACKAGE_REQUIRE_PATTERN.
Core Detection Logic
react_on_rails/lib/react_on_rails/doctor.rb
Removed check_base_package_imports; added check_base_package_references plus helpers files_with_base_package_references(source_path) and base_package_reference?(content) to glob and match JS/TS files (including .mjs/.cjs).
Reporting / Messages
react_on_rails/lib/react_on_rails/doctor.rb
Updated success/warning text and "Fix" guidance to reference “base package references” and provide Pro replacement examples; emits a dedicated warning if scanning fails.
Tests
react_on_rails/spec/lib/react_on_rails/doctor_spec.rb
Renamed test usages to check_base_package_references; expanded coverage to include Jest/Vitest mock helper patterns (jest.mock, unmock, doMock, dontMock, jest.requireActual, jest.requireMock, vi.importActual, importMock), TypeScript .d.ts module-augmentation cases, and .mjs/.cjs entry files; updated Pro-success scenarios and custom source_path contexts.
Changelog
CHANGELOG.md
Added an [Unreleased] → Fixed entry documenting detection of Jest/Vitest mock helpers and TypeScript declare module blocks for Pro migration checks.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hop through files both near and far,
Jest mocks, require, and .d.ts I spar.
I sniff base-package traces, one by one,
Pointing Pro paths till the job is done. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly addresses the main change: expanding react_on_rails:doctor to detect stale references to the base package after Pro migrations.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/doctor-pro-module-specifiers

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 30, 2026

Greptile Summary

This PR extends the check_base_package_imports doctor check to detect two additional stale-module-specifier patterns that survive a migration from react-on-rails to react-on-rails-pro: Jest/Vitest mock calls (jest.mock('react-on-rails', ...)) and TypeScript ambient module declarations (declare module 'react-on-rails' { … }). Both patterns are covered by well-formed regex constants and backed by new RSpec contexts that exercise the detection end-to-end.

Confidence Score: 4/5

Safe to merge; the only finding is a P2 wording improvement to the warning message.

No logic errors or regressions introduced. The two new regex patterns are correct and the tests validate them. The only concern is a P2: the existing warning message and fix suggestion are framed around import/require patterns and don't give accurate guidance for the newly-detected mock-call and declare module cases.

react_on_rails/lib/react_on_rails/doctor.rb — warning message wording should be updated to mention mock/declare-module patterns.

Important Files Changed

Filename Overview
react_on_rails/lib/react_on_rails/doctor.rb Adds two new regex constants and extends the file-content guard to detect jest.mock-style calls and TypeScript declare module augmentations that still reference the base react-on-rails package after a Pro migration; warning message wording not updated to cover the new patterns.
react_on_rails/spec/lib/react_on_rails/doctor_spec.rb Adds two new test contexts for the mock-call pattern and the declare module pattern; tests set up temp directories correctly and assert the expected warning is produced.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[check_base_package_imports] --> B[resolve_js_source_path]
    B --> C[Glob js/jsx/ts/tsx files]
    C --> D{File content matches?}
    D -->|import from react-on-rails| E[files_with_base_import << file]
    D -->|require react-on-rails| E
    D -->|jest.mock react-on-rails NEW| E
    D -->|declare module react-on-rails NEW| E
    D -->|no match| F[next file]
    E --> G{Any files collected?}
    G -->|yes| H[add_warning with file list]
    G -->|no| I[add_success]
Loading

Comments Outside Diff (1)

  1. react_on_rails/lib/react_on_rails/doctor.rb, line 2779-2789 (link)

    P2 Warning message doesn't cover newly-detected patterns

    The warning message and its fix suggestion are still written entirely in terms of ES module imports (import … from) and CommonJS requires (require(...)). The two new patterns — jest.mock('react-on-rails', ...) and declare module 'react-on-rails' { … } — will now be flagged, but the suggested fix ("Update imports to use 'react-on-rails-pro'") is misleading for those cases: a test-mock call should be changed to jest.mock('react-on-rails-pro', ...), and a declare module augmentation should target 'react-on-rails-pro' instead. Consider splitting the message or adding a note that covers these additional patterns.

Reviews (1): Last reviewed commit: "Add changelog for Pro doctor specifier s..." | Re-trigger Greptile

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Warning message misleads for mock and declaration matches
    • Generalized the doctor warning and spec coverage so mock and declaration matches are described as package references with applicable remediation guidance.

Create PR

Or push these changes by commenting:

@cursor push 2c7191062e
Preview (2c7191062e)
diff --git a/react_on_rails/lib/react_on_rails/doctor.rb b/react_on_rails/lib/react_on_rails/doctor.rb
--- a/react_on_rails/lib/react_on_rails/doctor.rb
+++ b/react_on_rails/lib/react_on_rails/doctor.rb
@@ -2747,9 +2747,9 @@
     end
 
     # The base 'react-on-rails' npm package is a transitive dependency of 'react-on-rails-pro',
-    # so `import ... from 'react-on-rails'` resolves silently — loading the base package instead
-    # of Pro. Components registered through the base package won't have Pro features (streaming,
-    # caching, RSC), and may cause "component not registered" errors at runtime.
+    # so references to 'react-on-rails' resolve silently — loading the base package instead of Pro.
+    # Components registered through the base package won't have Pro features (streaming, caching,
+    # RSC), and may cause "component not registered" errors at runtime.
     BASE_PACKAGE_IMPORT_PATTERN = %r{\bfrom\s+['"]react-on-rails(?:/[^'"]*)?['"]}
     BASE_PACKAGE_REQUIRE_PATTERN = %r{\brequire\s*\(\s*['"]react-on-rails(?:/[^'"]*)?['"]\s*\)}
     BASE_PACKAGE_MOCK_PATTERN = %r{\b\w+\.(?:mock|unmock|doMock|dontMock|requireActual|requireMock)\s*\(\s*['"]react-on-rails(?:/[^'"]*)?['"]} # rubocop:disable Layout/LineLength
@@ -2777,13 +2777,13 @@
         checker.add_success("✅ No base 'react-on-rails' imports found (Pro package used correctly)")
       else
         checker.add_warning(<<~MSG.strip)
-          ⚠️  Found imports from 'react-on-rails' instead of 'react-on-rails-pro':
+          ⚠️  Found references to 'react-on-rails' instead of 'react-on-rails-pro':
           #{files_with_base_import.map { |f| "  • #{f}" }.join("\n")}
 
-          The base package is a transitive dependency of Pro, so these imports resolve
+          The base package is a transitive dependency of Pro, so these references resolve
           silently but load the base version without Pro features.
 
-          Fix: Update imports to use 'react-on-rails-pro':
+          Fix: Update imports, require calls, mocks, and declarations to use 'react-on-rails-pro':
             import ReactOnRails from 'react-on-rails-pro';        // server
             import ReactOnRails from 'react-on-rails-pro/client';  // client
         MSG

diff --git a/react_on_rails/spec/lib/react_on_rails/doctor_spec.rb b/react_on_rails/spec/lib/react_on_rails/doctor_spec.rb
--- a/react_on_rails/spec/lib/react_on_rails/doctor_spec.rb
+++ b/react_on_rails/spec/lib/react_on_rails/doctor_spec.rb
@@ -2546,6 +2546,7 @@
         doctor.send(:check_base_package_imports)
         warning_msgs = checker.messages.select { |m| m[:type] == :warning }
         expect(warning_msgs.any? { |m| m[:content].include?("app.test.ts") }).to be true
+        expect(warning_msgs.any? { |m| m[:content].include?("Found references to 'react-on-rails'") }).to be true
       end
     end
 
@@ -2565,6 +2566,7 @@
         doctor.send(:check_base_package_imports)
         warning_msgs = checker.messages.select { |m| m[:type] == :warning }
         expect(warning_msgs.any? { |m| m[:content].include?("react-on-rails.d.ts") }).to be true
+        expect(warning_msgs.any? { |m| m[:content].include?("imports, require calls, mocks, and declarations") }).to be true
       end
     end

You can send follow-ups to the cloud agent here.

Comment thread react_on_rails/lib/react_on_rails/doctor.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/doctor.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/doctor.rb Outdated
Comment thread react_on_rails/spec/lib/react_on_rails/doctor_spec.rb
Comment thread react_on_rails/spec/lib/react_on_rails/doctor_spec.rb
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 30, 2026

Review: fix: detect stale Pro migration module specifiers

Overall this is a clean, well-scoped addition. The two new regex constants are logically correct and the test structure follows the existing pattern. A few things worth addressing before merge:


🔴 Warning message doesn't cover the new detection types

The existing warning text (unchanged in this PR) was written only for import/require:

⚠️  Found imports from 'react-on-rails' instead of 'react-on-rails-pro':
  • app/javascript/packs/app.test.ts

Fix: Update imports to use 'react-on-rails-pro':
  import ReactOnRails from 'react-on-rails-pro';        // server
  import ReactOnRails from 'react-on-rails-pro/client';  // client

When the trigger is a Jest mock or a declare module block the fix guidance is wrong — the user is told to change an import statement, but there is no import to change. They need to know to update the mock or the type augmentation instead. Suggested fix:

Fix: Replace all base-package references with 'react-on-rails-pro':
  import ReactOnRails from 'react-on-rails-pro';              // ESM import (server)
  import ReactOnRails from 'react-on-rails-pro/client';       // ESM import (client)
  jest.mock('react-on-rails-pro', ...);                       // test mock
  declare module 'react-on-rails-pro' { ... }                 // TypeScript augmentation

🟡 Test gaps

  1. No negative test for the mock patternjest.mock('react-on-rails-pro', ...) must not trigger a warning. The regex is correct, but a dedicated spec would make that guarantee explicit (see inline comment on line 2549).

  2. Subpath mock not coveredjest.mock('react-on-rails/client') is a natural counterpart to the import ... from 'react-on-rails/client' test that already exists.


🟢 Things that look good

  • Both new regex patterns correctly exclude react-on-rails-pro — the (?:/[^'"]*) optional subpath group requires a / prefix, so -pro is never a false match.
  • ^ in BASE_PACKAGE_DECLARE_MODULE_PATTERN is line-anchored in Ruby (not string-anchored), so multiline .d.ts content is handled correctly.
  • .d.ts files are covered implicitly by **/*.ts (they end in .ts); this works as intended, just non-obvious to a future reader.
  • The rubocop disables are accurate — Metrics/PerceivedComplexity is the right addition alongside the pre-existing CyclomaticComplexity disable.
  • Changelog entry is clear and links the issue correctly.

Comment thread react_on_rails/lib/react_on_rails/doctor.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/doctor.rb Outdated
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 30, 2026

Code Review

Overall: A focused, low-risk improvement to the Pro migration diagnostic. The two new regex patterns correctly catch categories of base-package references that were previously invisible to the scanner, and the specs cover both new paths. A few things worth addressing before merge:


Warning message is stale for the new pattern types

The existing warning text (⚠️ Found imports from 'react-on-rails'...) and the fix guidance (import ReactOnRails from 'react-on-rails-pro') were written for import/require cases only. Now that the scanner also catches Jest mocks and declare module blocks, the message can mislead users:

  • A test file with jest.mock('react-on-rails', ...) doesn't need a new import statement — it needs the mock target updated to 'react-on-rails-pro', or the mock deleted.
  • A .d.ts declaration file with declare module 'react-on-rails' needs the block deleted or retargeted, not an import added.

The success path also reads "No base 'react-on-rails' imports found" — technically wrong now that mocks and type declarations are in scope.

Suggested language update (in check_base_package_imports, around line 2778):

checker.add_success("✅ No base 'react-on-rails' references found (Pro package used correctly)")
# ...
checker.add_warning(<<~MSG.strip)
  ⚠️  Found references to 'react-on-rails' instead of 'react-on-rails-pro':
  #{files_with_base_import.map { |f| "  • #{f}" }.join("\n")}

  The base package is a transitive dependency of Pro, so these references resolve
  silently but load the base version without Pro features.

  Fix: replace base-package references with their Pro equivalents:
    import ReactOnRails from 'react-on-rails-pro';         // ES import (server)
    import ReactOnRails from 'react-on-rails-pro/client';  // ES import (client)
    jest.mock('react-on-rails-pro', ...)                   // Jest/Vitest mock
    // delete stale 'declare module' blocks in .d.ts files
MSG

declare module test file is not covered by the glob

The test creates app/javascript/types/react-on-rails.d.ts, and *.ts does match .d.ts files (glob * matches .), so the test works today. Worth a brief note in the test or a comment on the constant that .d.ts files are intentionally included via the *.ts glob — this isn't obvious to future readers and could be accidentally broken if someone restricts the glob to exclude type-declaration files.


No negative regression test for declare module 'react-on-rails-pro'

There's a negative test for imports from 'react-on-rails-pro' but not one for declare module 'react-on-rails-pro' { ... }. Given that the DECLARE_MODULE_PATTERN regex uses a lookahead-free negative approach (the package name must match exactly), a quick sanity spec confirming declare module 'react-on-rails-pro' does not trigger would be a cheap safety net.


Minor: requireActual semantics differ from mock

jest.requireActual('react-on-rails') is used to bypass a mock and explicitly load the real base package — intentional usage, not always a migration leftover. The current implementation flags it (which is defensible as "any base-package reference is suspect"), but if a real user hits a false positive here, the warning could be confusing. At minimum, consider documenting the decision in the constant's comment.

Comment thread react_on_rails/lib/react_on_rails/doctor.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/doctor.rb Outdated
Comment thread react_on_rails/spec/lib/react_on_rails/doctor_spec.rb
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 4, 2026

Code Review

Summary

This PR extends react_on_rails:doctor's Pro migration detection to catch two new categories of stale react-on-rails references: Jest/Vitest mock helpers and TypeScript declare module augmentations. The implementation is clean and the test coverage is solid. A few observations below.


Regex correctness ✅

Both new patterns correctly exclude react-on-rails-pro matches:

  • react-on-rails(?:/[^'"]*)?['"] ends with a quote, so react-on-rails-pro (which has -pro after the package name) won't match.
  • BASE_PACKAGE_DECLARE_MODULE_PATTERN uses ^ which is a Ruby line anchor — correct for multi-line file content.

File extension coverage ✅

.d.ts files are correctly picked up by **/*.ts glob since react-on-rails.d.ts ends in .ts.

Naming inconsistency ⚠️

The method is still named check_base_package_imports and called that way at line 2719, even though every internal variable was renamed to *reference*. See inline comment.

unmock/dontMock in pattern ⚠️

Including these in BASE_PACKAGE_MOCK_PATTERN is defensible for migration diagnostics, but the warning message doesn't distinguish them from mock calls, which could be confusing. See inline comment.

Missing Vitest test case

vi.mock('react-on-rails') works because \w+ matches vi, but there's no spec exercising this path. See inline comment for a suggested test case.

No other issues found

  • No security implications — reads files under the project source path only.
  • Performance is unchanged — same O(n files) scan, just additional .match? calls per file (fast due to early exit on first match).
  • The updated user-facing message and fix guidance are clear and accurate.
  • Negative tests (Pro mock / Pro declare module → success) correctly validate the happy path.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@react_on_rails/lib/react_on_rails/doctor.rb`:
- Around line 2755-2770: The BASE_PACKAGE_MOCK_PATTERN used in
check_base_package_imports is missing Vitest helpers; update the pattern
(BASE_PACKAGE_MOCK_PATTERN) to also match importActual/importMock calls and the
vi.importActual/vi.importMock forms so the doctor flags "Jest/Vitest mock"
usages like importActual('react-on-rails...') and
vi.importMock('react-on-rails...'); modify the regex to include both
importActual/importMock and optional vi. prefix variants while keeping existing
Jest variants (mock/unmock/doMock/dontMock/requireActual/requireMock).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 90e0860f-8a55-42d6-9b9a-5b52a866d790

📥 Commits

Reviewing files that changed from the base of the PR and between f25cde3 and fec385e.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • react_on_rails/lib/react_on_rails/doctor.rb
  • react_on_rails/spec/lib/react_on_rails/doctor_spec.rb

Comment thread react_on_rails/lib/react_on_rails/doctor.rb Outdated
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 4, 2026

Code Review — PR #3232: detect stale Pro migration module specifiers

Overall: Clean, well-scoped change. The new patterns close a real gap (jest/vitest mocks and TS declare module blocks are common Pro migration leftovers), the test matrix is comprehensive for the happy-path cases, and the stress-test.md reformatting is pure noise-free style.

A few things worth addressing before merge:


🐛 Potential issue — BASE_PACKAGE_MOCK_METHODS defined as Regexp but only used as .source

BASE_PACKAGE_MOCK_METHODS = /mock|unmock|doMock|dontMock|requireActual|requireMock|importActual|importMock/
BASE_PACKAGE_MOCK_PATTERN =
  %r{\b(?:\w+\.)?(?:#{BASE_PACKAGE_MOCK_METHODS.source})\s*\(\s*['"]react-on-rails(?:/[^'"]*)?['"]}

BASE_PACKAGE_MOCK_METHODS is a Regexp object but the only use of it is .source to embed the raw string in another pattern. This is subtle: a future maintainer might call .match? against it directly (thinking it's already a complete pattern), or add flags to it (e.g. IGNORECASE) expecting them to carry over into BASE_PACKAGE_MOCK_PATTERN — neither would work. A plain string constant makes the intent unambiguous:

BASE_PACKAGE_MOCK_METHOD_NAMES = "mock|unmock|doMock|dontMock|requireActual|requireMock|importActual|importMock"
BASE_PACKAGE_MOCK_PATTERN =
  %r{\b(?:\w+\.)?(?:#{BASE_PACKAGE_MOCK_METHOD_NAMES})\s*\(\s*['"]react-on-rails(?:/[^'"]*)?['"]}

🧪 Test coverage gap — 5 of 8 listed mock methods have no test

The BASE_PACKAGE_MOCK_METHODS alternation names 8 methods, but the spec only exercises 3:

  • mock (jest.mock)
  • importActual (vi.importActual)
  • importMock (standalone)
  • unmock, doMock, dontMock, requireActual, requireMock

These are real Jest APIs that appear in stale Pro migration artifacts (e.g. jest.requireActual('react-on-rails') is a common pattern). Worth adding at least one spec for a method from each "missing" group, or adding a comment acknowledging the gap is intentional.


🧪 Weak assertion in TypeScript augmentation spec

At spec/lib/react_on_rails/doctor_spec.rb:2627, the test checks:

expect(warning_msgs.any? { |m| m[:content].include?("TypeScript augmentation") }).to be true

But "TypeScript augmentation" appears in the fix-hint block of every warning (regardless of which pattern triggered), so this assertion would pass even if BASE_PACKAGE_DECLARE_MODULE_PATTERN was completely broken and another pattern happened to fire. The file-name check on the line above is the only assertion that validates the TS-specific detection. Consider dropping the second assertion (it doesn't add signal) or replacing it with something like m[:content].include?("declare module").


Minor — warning always lists all three fix hints

The warning message unconditionally shows all three fix examples (ES import, Jest mock, TypeScript augmentation) regardless of which pattern fired. For a jest.mock violation, showing "declare module 'react-on-rails-pro' { ... } // TypeScript augmentation" is slightly confusing. Low priority, but worth considering filtering hints based on what matched.


Pre-existing — .mjs / .cts / .mts extensions not scanned

js_extensions = %w[js jsx ts tsx] doesn't cover modern ESM/CTS variants. Vitest mock files frequently use .mts. Not introduced by this PR, but relevant now that mock patterns are in scope.

Comment thread react_on_rails/lib/react_on_rails/doctor.rb Outdated
Comment thread react_on_rails/spec/lib/react_on_rails/doctor_spec.rb Outdated
justin808 added 3 commits May 4, 2026 17:19
* origin/main:
  fix(pro-dummy): make manual node-renderer validation reliable (#3200)
  [codex] Add Markdown Prettier CI check (#3242)

# Conflicts:
#	.claude/commands/stress-test.md
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 5, 2026

Code Review - PR 3232

Overall: Clean, well-scoped improvement. The refactoring from an imperative each-loop to flat_map+select reads better, and the decomposition into helper methods is a clear win for testability. Test coverage is thorough with positive and negative cases for every new pattern.

What the PR does

Extends react_on_rails:doctor Pro-migration check to catch two additional forms of stale react-on-rails base-package references: Jest/Vitest mock helpers and TypeScript declare module augmentations in .ts/.d.ts files.

Specific notes

BASE_PACKAGE_MOCK_METHOD_NAMES should be frozen. It is only consumed at class-load time when BASE_PACKAGE_MOCK_PATTERN is built via string interpolation, so leaving it mutable is inconsistent — BASE_PACKAGE_REFERENCE_PATTERNS is explicitly .freezed. Either freeze it, or inline the string directly into the regex literal and remove the intermediate constant entirely since it has no other use.

BASE_PACKAGE_MOCK_PATTERN is intentionally broad. The regex matches any function named mock etc. regardless of receiver (not just jest/vi). False positives are practically impossible given how Jest/Vitest-specific the name list is (requireActual, dontMock, importActual), but a short inline comment noting the intentional broadness would help future readers.

No duplicates from flat_map — confirmed correct. Extensions are mutually exclusive so a file can never appear in two glob results.

.d.ts coverage is correct. Ruby Dir.glob matches foo.d.ts for **/*.ts. The clarifying comment in the code is appreciated.

Minor nit: vi.mock is missing from the fix example in the warning message. The message lists jest.mock(react-on-rails-pro, ...) as the recommended replacement but does not show the Vitest equivalent. Since the PR explicitly adds Vitest support, adding vi.mock('react-on-rails-pro', ...) to the suggestion block would make it complete for Vitest users.

Summary

Implementation is correct and tests are solid. Two actionable items before merge: (1) freeze BASE_PACKAGE_MOCK_METHOD_NAMES, and (2) add vi.mock to the warning fix example.

Comment thread react_on_rails/lib/react_on_rails/doctor.rb Outdated
The version-pin detector at build_pro_gem_replacement_line had an
ambiguous alternation: `(?:\s*#.*\n|\s+)*` let both branches consume
whitespace at the start, so on input like `,                ` (comma
+ many spaces, no terminating quote) the engine backtracked through
exponentially many ways to distribute the spaces between the branches.
CodeQL flagged this as a ReDoS path.

Restructure so whitespace-eating happens outside the comment loop and
nothing else competes for the same characters:

    /\A\s*,\s*(?:#[^\n]*\n\s*)*["']/

`\s*` and `#[^\n]*\n\s*` cannot match the same starting character (one
needs `#`, the other needs whitespace then nothing required), so the
alternation is unambiguous and backtracking stays linear. All existing
positive cases (single pin, multi-constraint, pin+options, comment
between comma and pin) still match; negative cases (no version,
options-only, bare gem name) still don't match; the attack input
(comma + arbitrary whitespace, no quote) now fails fast.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 14, 2026

Code Review — PR #3232: Fix stale references and lost Gemfile pins after Pro migration

Overview

This PR fixes two concrete pain points in the Pro migration path (#3104):

  1. Gemfile pin preservation — the old generator overwrote the user's version constraint with the running gem's version; now it preserves whatever the user had.
  2. Expanded JS rewriting — Jest/Vitest mock helpers (jest.mock, vi.importActual, etc.) and TypeScript declare module augmentations are now rewritten alongside static/dynamic imports.
  3. Doctor parityreact_on_rails:doctor now flags the same (plus more) patterns, keeping it a strict superset of the rewriter.

The design is sound and the test coverage is extensive. Notes below are mostly polish.


What's Good

  • File.binread + force_encoding + valid_encoding? in base_package_reference_file? is the correct defensive pattern for scanning arbitrary JS files (binary assets, non-UTF-8 strings).
  • .sort on the result list in files_with_base_package_references makes warning output deterministic — good for reproducible CI logs.
  • Doctor is a superset of the rewriter by design, so hand-written post-migration code and third-party stubs are still surfaced. This invariant is documented in the PR description.
  • defer_pro_gem_install_to_gemfile_swap cleanly short-circuits bundle add when the base gem is already in the Gemfile, avoiding an unnecessary external process.
  • The Gemfile replacement logic is dramatically simpler: the old loop-based version-stripping is replaced by a single match? + conditional, which is much easier to reason about.
  • mjs/cjs extensions added to the scan — correct for modern JS toolchains.

Issues

1. rescue StandardError in base_react_on_rails_gem_in_gemfile? is too broad

rescue StandardError catches programmer errors (e.g., NoMethodError, ArgumentError) in addition to I/O failures, silently masking bugs. The only expected failures here are filesystem errors; rescue SystemCallError, IOError would be more precise (and consistent with the rescue SystemCallError used in base_package_reference_file? just a few dozen lines away in the same PR).

2. rewrite_react_on_rails_module_specifiers is growing — Rubocop disable is a smell

The method now handles five distinct rewrite passes (static import, dynamic/require, side-effect import, mock calls, declare module). The rubocop:disable Metrics/MethodLength, Metrics/AbcSize was added rather than extracted. Consider extracting the five regex patterns to module-level constants (like BASE_PACKAGE_REFERENCE_PATTERNS in the doctor) so the method body is just the five sequential gsub calls — the patterns themselves are the complexity, not the dispatch logic.

3. Minor gap: no test for gem "react_on_rails", require: false (option-only, no version pin)

All added tests cover either a version pin present ("~> 16.0"), a source option only (path:, git:), or the bare gem name. The case gem "react_on_rails", require: false (keyword option but no version) is not tested. The implementation handles it correctly (has_user_version_pin is false → default version is inserted), but a test would document that intent explicitly.

4. TODO on mark_pro_gem_installed! is accurate — worth a follow-up issue

The TODO calling out the CQS smell is well-written and the risk is real (callers can't distinguish "really installed" from "deferred to Gemfile swap"). This PR introduces a second caller of mark_pro_gem_installed! via defer_pro_gem_install_to_gemfile_swap, making the smell more pressing. Suggest opening a tracking issue rather than relying on the inline comment alone.


Minor

  • Doc fix (/tree//blob/): correct and unrelated churn, fine to include.
  • Lychee exclusions: straightforward, no concerns.
  • Changelog entry: comprehensive, matches project conventions.

Verdict

Approve with the above notes. The two functional bugs fixed (stale mock references, lost version pins) are user-visible regressions in the migration path, and the fixes are correct. The issues above are polish items, not blockers.

Comment thread react_on_rails/lib/generators/react_on_rails/pro_setup.rb Outdated
Comment thread react_on_rails/lib/generators/react_on_rails/pro_generator.rb Outdated
Comment thread react_on_rails/lib/generators/react_on_rails/pro_generator.rb
Comment thread react_on_rails/lib/generators/react_on_rails/generator_helper.rb
Comment thread react_on_rails/lib/generators/react_on_rails/generator_helper.rb
Comment thread react_on_rails/lib/generators/react_on_rails/pro_generator.rb
Comment thread react_on_rails/lib/generators/react_on_rails/pro_generator.rb
Comment thread react_on_rails/lib/react_on_rails/doctor.rb
Comment thread react_on_rails/lib/generators/react_on_rails/pro_setup.rb
ihabadham and others added 2 commits May 15, 2026 01:14
The broad `rescue StandardError` here swallowed programmer errors
(NoMethodError, ArgumentError, etc.) alongside the expected filesystem
errors. A programmer bug here would silently return false, skipping
the defer path and letting `bundle add` overwrite the user's Gemfile
pin — a regression the existing tests don't catch because they stub
filesystem state cleanly.

Narrowing to `SystemCallError, IOError` keeps the intended graceful
handling for race-condition deletes / permission failures while
letting actual bugs surface as crashes during development.

Matches the convention in `base_package_reference_file?`
(`doctor.rb:2960`), which was narrowed to `rescue SystemCallError`
in commit 88805bf in response to the same review feedback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tants

The five `react-on-rails` specifier regexes lived as local variables
inside `rewrite_react_on_rails_module_specifiers`, which forced two
rubocop disables (`Metrics/MethodLength`, `Metrics/AbcSize`) once
two new patterns landed. Hoisting them to module-level frozen
constants and dispatching through a reduce over a
`BASE_PACKAGE_REWRITE_PATTERNS` array removes both disables, makes
each pattern individually testable, and matches the convention
already established in `doctor.rb` (`BASE_PACKAGE_REFERENCE_PATTERNS`).

No behavior change — same five regexes, same `react-on-rails-pro`
substitution, same surrounding `rewrite_non_comment_lines` and
`rewrite_outside_inline_template_literals` wrappers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@shakacode shakacode deleted a comment from claude Bot May 14, 2026
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 15, 2026

Code Review — PR #3232

Overview

This PR fixes two genuine post-migration pain points: stale JS/TS references to the base react-on-rails package after running the Pro generator, and the generator silently overwriting the user's Gemfile version pin with its own running-gem version. Both bugs would cause silent misbehaviour that's hard to diagnose.

What's Good

  • Extracting six regex patterns to module-level constants and reducing over them is a clean refactor of what was a repetitive serial gsub chain.
  • File.binread + force_encoding("UTF-8") + valid_encoding? is the correct idiom for scanning arbitrary JS source files; the previous File.read would crash on binary artefacts in the JS tree.
  • Preserving the user's Gemfile pin (including multi-constraint and keyword options like path:, git:, require:) is a meaningful UX improvement — the old behaviour silently diverged from what the user wrote.
  • Test coverage is thorough: ~350 lines of new RSpec cover every variant of mock helper, declare-module, dynamic import, ESM/CJS extension, and edge cases (missing file, invalid UTF-8, disappearing file mid-scan).
  • sort on the scanned file list gives deterministic warning output.
  • The comment clarifying that Ruby's ^ is multi-line (unlike \A) on BASE_PACKAGE_SIDE_EFFECT_IMPORT_PATTERN is exactly the kind of note that prevents future regressions.

Issues

Medium — Parallel pattern sets with no sync enforcement

pro_generator.rb and doctor.rb now each maintain their own list of base-package patterns (BASE_PACKAGE_REWRITE_PATTERNS / BASE_PACKAGE_REFERENCE_PATTERNS). The PR intentionally makes the doctor a superset of the rewriter, but there is no code-level enforcement: if a new JS pattern is needed in the future, both files must be updated independently. A missed update would mean the doctor warns about something the generator silently leaves behind, or vice versa. A shared module (even a small ReactOnRails::BasePackagePatterns concern) would let the generator derive its rewrite-capable subset from the canonical list.

Medium — 9-line TODO block in production code (generator_helper.rb:150-158)

The comment accurately describes a CQS smell and proposes a concrete refactor, but a 9-line design note in a method header usually means the item should be a tracked issue, not inline prose. The current placement will be seen on every code read but has no path to being actioned.

Low — defer_pro_gem_install_to_gemfile_swap semantics vs. name

The method calls mark_pro_gem_installed! before the Gemfile has been rewritten or bundle install run. The name suggests it schedules something to happen later, but it immediately lies about installation state and returns. This amplifies the same CQS concern the TODO documents.

Low — Bare importActual/importMock rewrite scope

In the doctor, bare importActual/importMock are explicitly documented as accepted false-positive risk for detection. The same bare forms appear in MOCK_CALL_PATTERN in the rewriter, where a false positive means mutating the user's file. The (?<!["' + "" + ])lookbehind guards against in-string matches but not dotted receivers — e.g.server.importActual('react-on-rails')` would be rewritten. A brief comment mirroring the doctor's accepted-risk note would make the intentional trade-off explicit.

Minor

  • One specify call in pro_generator_spec.rb is inconsistent with surrounding it blocks.
  • Constants in pro_generator.rb (MOCK_CALL_PATTERN, DECLARE_MODULE_PATTERN) lack the BASE_PACKAGE_ prefix their doctor counterparts have, making their scope slightly less obvious at a glance.

Verdict

The implementation is solid and the test coverage is genuinely comprehensive. The medium items above are design-level concerns worth addressing (ideally the shared-pattern approach before merging, the TODO as a follow-up issue), but they do not block the core functionality from working correctly.

Comment thread react_on_rails/lib/generators/react_on_rails/generator_helper.rb
Comment thread react_on_rails/lib/generators/react_on_rails/pro_generator.rb
Comment thread react_on_rails/lib/generators/react_on_rails/pro_setup.rb
Comment thread react_on_rails/lib/generators/react_on_rails/pro_generator.rb Outdated
…e branch

`importActual` / `importMock` are only ever `vi.importActual` /
`vi.importMock` in Vitest (and `jest.requireActual` / `jest.requireMock`
in Jest). There is no `import { importActual } from 'vitest'` — they are
not standalone named exports. The bare `(?:importActual|importMock)`
alternative in MOCK_CALL_PATTERN therefore had no legitimate use case:
every bare or dotted-receiver match (e.g. `server.importActual(
'react-on-rails')`) is unrelated user code, and rewriting it silently
mutates the source file to reference a package the user never intended.

The real Vitest/Jest module-loading helpers are all covered by the
`(?:jest|vi)\.` branch, where the string argument is unambiguously a
module specifier. Removing the bare branch eliminates the file-mutation
false positive while losing no real coverage.

The companion spec is flipped from asserting bare calls are rewritten
(they were never a real Vitest form) to asserting they are left intact,
including the `server.importActual(...)` dotted-receiver case.

The doctor's BASE_PACKAGE_MOCK_PATTERN deliberately keeps the broader
match: an extra detection warning is harmless, and a liberal doctor that
is a strict superset of the conservative rewriter is the intended
invariant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 15, 2026

Open Issue Deep Dive (2026-03-22)

Snapshot

  • Open issues reviewed: 85
  • Open issues already linked to an open PR: 10
  • Open issues without an open PR at review time: 75
  • Wave 1 (PR Open issue deep dive: snapshot + wave 1 active plan #2810): 34 issues
  • Wave 2 (stacked follow-up PR): 20 issues
  • Wave 3 (stacked follow-up PR): 21 issues
  • Triage comments posted at snapshot time: 85/85

Existing Open PR Coverage

Wave Definitions

  • wave-1: active / near-term items (P1/P2, release-critical, or newly created issues with concrete implementation scope)
  • wave-2: medium backlog (primarily P3 created in recent cycles)
  • wave-3: long-tail backlog / parked items

Format Notes

  • Context excerpts in wave files are intentionally truncated with ....
  • Triage note captures inferred scope, blocker history, pending verification, and other non-quoted analyst context.

Execution Notes

  • Every issue received a triage comment with domain, current PR coverage, and a concrete next-step question.
  • New PR stack references all issues that had no open PR at review time.

Comment on lines +2885 to +2896
# Match known Jest/Vitest helpers. Aliased or nested receivers are intentionally
# out of scope to avoid warning on arbitrary application methods named `mock`.
# The bare importActual/importMock forms cover Vitest's destructured-import style:
# import { importActual, importMock } from 'vitest'
# Accepted false-positive risk: user-defined importActual/importMock helpers
# that take a 'react-on-rails' string also match.
BASE_PACKAGE_MOCK_PATTERN = %r{
\b(?:
(?:jest|vi)\.
(?:mock|unmock|doMock|doUnmock|dontMock|requireActual|requireMock|importActual|importMock)
|
(?:importActual|importMock)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The comment here contradicts the generator spec. This says bare importActual/importMock "cover Vitest's destructured-import style: import { importActual, importMock } from 'vitest'", but pro_generator_spec.rb explicitly says the opposite: "importActual/importMock are only vi.importActual / vi.importMock in Vitest; there is no import { importActual } from 'vitest'."

Vitest does not export standalone importActual/importMock functions — they are methods on the vi object. The bare forms will produce false positives for user-defined functions with those names, which is fine as an accepted trade-off, but the stated rationale ("destructured-import style") is incorrect. Suggest updating to:

Suggested change
# Match known Jest/Vitest helpers. Aliased or nested receivers are intentionally
# out of scope to avoid warning on arbitrary application methods named `mock`.
# The bare importActual/importMock forms cover Vitest's destructured-import style:
# import { importActual, importMock } from 'vitest'
# Accepted false-positive risk: user-defined importActual/importMock helpers
# that take a 'react-on-rails' string also match.
BASE_PACKAGE_MOCK_PATTERN = %r{
\b(?:
(?:jest|vi)\.
(?:mock|unmock|doMock|doUnmock|dontMock|requireActual|requireMock|importActual|importMock)
|
(?:importActual|importMock)
# Match known Jest/Vitest helpers. Aliased or nested receivers are intentionally
# out of scope to avoid warning on arbitrary application methods named `mock`.
# The bare importActual/importMock forms are included as a precaution for
# user-land helpers that shadow the Vitest api — accepted false-positive risk.

Comment on lines 914 to +924
def build_pro_gem_replacement_line(indentation:, quote:, suffix:, parenthesized_gem_call: false)
normalized_suffix = suffix || "\n"
normalized_suffix = "#{normalized_suffix}\n" unless normalized_suffix.end_with?("\n")
version_arg_pattern = /\A(?<prefix>\s*,(?:\s*#.*\n|\s++)*)["'][^"']*["'](?<trailing_comma>\s*,)?/
loop do
updated_suffix = normalized_suffix.sub(version_arg_pattern) do
if Regexp.last_match[:trailing_comma]
Regexp.last_match[:prefix].sub(/\n[ \t]*\z/, "")
else
""
end
end
break if updated_suffix == normalized_suffix

normalized_suffix = updated_suffix
end
normalized_suffix = normalized_suffix.sub(/\A,\s*(?:#[^\n]*)?\n\z/, "\n")
normalized_suffix = normalized_suffix.sub(/\A,[ \t]{2,}/, ", ")
has_user_version_pin = normalized_suffix.match?(/\A\s*,\s*(?:#[^\n]*\n\s*)*["']/)
version_arg = has_user_version_pin ? "" : ", #{quote}#{pro_gem_version_requirement}#{quote}"

normalized_suffix = "\n" if normalized_suffix.match?(/\A,\s*\n\z/)
normalized_suffix = normalized_suffix.sub(/\)(\s*(?:#.*)?\n)\z/, '\1') if parenthesized_gem_call

"#{indentation}gem #{quote}react_on_rails_pro#{quote}, " \
"#{quote}#{pro_gem_version_requirement}#{quote}#{normalized_suffix}"
"#{indentation}gem #{quote}react_on_rails_pro#{quote}#{version_arg}#{normalized_suffix}"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The two operations on normalized_suffix are order-dependent in a non-obvious way:

  1. has_user_version_pin is computed from the original suffix (correctly, before cleanup)
  2. The trailing-comma cleanup (normalized_suffix = "\n") then fires only when has_user_version_pin is false (because ,\n alone will never match the pin regex)

This is correct as written, but if the two blocks were ever swapped the trailing-comma cleanup would fire first, turning ,\n into \n, and the pin check would then fail to find a comma — inserting an unwanted default version argument. A one-line comment noting the dependency would protect against future regression:

Suggested change
def build_pro_gem_replacement_line(indentation:, quote:, suffix:, parenthesized_gem_call: false)
normalized_suffix = suffix || "\n"
normalized_suffix = "#{normalized_suffix}\n" unless normalized_suffix.end_with?("\n")
version_arg_pattern = /\A(?<prefix>\s*,(?:\s*#.*\n|\s++)*)["'][^"']*["'](?<trailing_comma>\s*,)?/
loop do
updated_suffix = normalized_suffix.sub(version_arg_pattern) do
if Regexp.last_match[:trailing_comma]
Regexp.last_match[:prefix].sub(/\n[ \t]*\z/, "")
else
""
end
end
break if updated_suffix == normalized_suffix
normalized_suffix = updated_suffix
end
normalized_suffix = normalized_suffix.sub(/\A,\s*(?:#[^\n]*)?\n\z/, "\n")
normalized_suffix = normalized_suffix.sub(/\A,[ \t]{2,}/, ", ")
has_user_version_pin = normalized_suffix.match?(/\A\s*,\s*(?:#[^\n]*\n\s*)*["']/)
version_arg = has_user_version_pin ? "" : ", #{quote}#{pro_gem_version_requirement}#{quote}"
normalized_suffix = "\n" if normalized_suffix.match?(/\A,\s*\n\z/)
normalized_suffix = normalized_suffix.sub(/\)(\s*(?:#.*)?\n)\z/, '\1') if parenthesized_gem_call
"#{indentation}gem #{quote}react_on_rails_pro#{quote}, " \
"#{quote}#{pro_gem_version_requirement}#{quote}#{normalized_suffix}"
"#{indentation}gem #{quote}react_on_rails_pro#{quote}#{version_arg}#{normalized_suffix}"
# NOTE: has_user_version_pin must be checked before the trailing-comma cleanup below,
# because the cleanup mutates normalized_suffix and would break pin detection.
has_user_version_pin = normalized_suffix.match?(/\A\s*,\s*(?:#[^\n]*\n\s*)*["']/)
version_arg = has_user_version_pin ? "" : ", #{quote}#{pro_gem_version_requirement}#{quote}"
normalized_suffix = "\n" if normalized_suffix.match?(/\A,\s*\n\z/)
normalized_suffix = normalized_suffix.sub(/\)(\s*(?:#.*)?\n)\z/, '\1') if parenthesized_gem_call
"#{indentation}gem #{quote}react_on_rails_pro#{quote}#{version_arg}#{normalized_suffix}"

@ihabadham
Copy link
Copy Markdown
Collaborator

Address-review summary

Scan scope: full PR history (prior checkpoint was removed). Reviewed the post-quota-reset claude-review pass on HEAD 8e5ac75cd / 2700df9e4.

Mattered

  • pro_generator.rb:375 bare importActual/importMock false positive (@claude) — Fixed in 2700df9e4. Verified against Vitest API docs that importActual/importMock exist only as vi. methods (no import { importActual } from 'vitest'); the bare branch had no legitimate use case and risked mutating unrelated code like server.importActual('react-on-rails'). Removed the bare branch; real jest./vi. helpers stay covered; spec flipped to a negative test.
  • pro_setup.rb:540 method naming / CQS (@claude) — Declined rename. Behavior is correct; the optimistic-flag CQS smell is documented in the TODO and tracked as a post-merge issue. No code change.
  • pro_generator.rb:397 shared pattern module (@claude) — Declined for this PR. Doctor patterns (detection, no captures) and rewriter patterns (capture groups for substitution) are structurally different; a shared canonical module is an out-of-scope cross-file refactor. The doctor ⊇ rewriter divergence is intentional.

Skipped

  • generator_helper.rb:158 TODO → GitHub issue (@claude) — Repeat of prior-round feedback; disposition unchanged. Issue filed post-merge; TODO already names the concrete refactor.

All four threads replied to and resolved. Zero unresolved threads.

Next default scan starts after this comment. Say check all reviews to rescan the full PR.

def missing_pro_gem?(force: false)
return false unless force || use_pro?
return false if pro_gem_installed?
return false if defer_pro_gem_install_to_gemfile_swap
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The method comment above (lines 55-57) still documents only 2 early-return paths. The new defer_pro_gem_install_to_gemfile_swap path added here is unlisted, which makes the docstring misleading. Worth updating the comment to document all three early returns.

Comment on lines +2943 to +2944
def files_with_base_package_references(source_path)
js_extensions = %w[js jsx ts tsx mjs cjs]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The doctor scans js jsx ts tsx mjs cjs but js_files_for_import_update in the generator also scans vue and svelte. If a user has Vue or Svelte components that import react-on-rails, the generator will rewrite them — but after migration the doctor will silently give a clean bill of health even though those files could also have stale imports added later.

This gap is small in practice (Vue/Svelte with react-on-rails is unusual), but aligning the two extension lists — or adding a comment here explaining the intentional difference — would prevent future confusion:

Suggested change
def files_with_base_package_references(source_path)
js_extensions = %w[js jsx ts tsx mjs cjs]
def files_with_base_package_references(source_path)
# Note: vue and svelte are excluded here (unlike js_files_for_import_update in the generator)
# because react-on-rails usage in those file types is rare enough that the pattern noise
# outweighs the benefit. Expand this list if Vue/Svelte support becomes common.
js_extensions = %w[js jsx ts tsx mjs cjs]

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 15, 2026

Code Review Summary

Overall: Solid, well-tested fix for Issue 3104. The approach is sound and test coverage (~350 new lines) is thorough. See inline comments for specific issues.

Strengths:

  • Promoting the 5 regex patterns to module-level constants and collapsing the 3 sequential gsub calls into a reduce are both clear improvements
  • File.binread + force_encoding("UTF-8") + valid_encoding? in base_package_reference_file? is the correct way to handle potentially-binary files
  • Doctor/rewriter asymmetry (doctor is a superset) is a sound design and is well-documented in the PR summary
  • The .sort on file lists gives deterministic output

Inline comments posted on 4 issues:

  1. doctor.rb:2885-2896 -- The comment describing bare importActual/importMock as "Vitest's destructured-import style" contradicts the generator spec, which says no such style exists. The rationale in the comment is wrong (the bare forms exist as a false-positive trade-off, not because Vitest exports them as standalone functions).

  2. pro_generator.rb:914-924 -- has_user_version_pin must be evaluated before the trailing-comma cleanup mutates normalized_suffix. The logic is correct but the order-dependency is load-bearing and silent. A short comment would protect future maintainers.

  3. pro_setup.rb:61 -- missing_pro_gem? docstring still only documents 2 early-return paths; the new defer_pro_gem_install_to_gemfile_swap path is unlisted.

  4. doctor.rb:2943-2944 -- Generator scans .vue/.svelte, doctor does not. After migration the doctor silently passes on stale base-package imports in those file types. Suggest either aligning the extension lists or adding a comment explaining the intentional difference.

Correctness of key logic confirmed:

  • has_user_version_pin regex handles all Gemfile shapes correctly (inline version, multiline, options-only, bare trailing comma)
  • base_react_on_rails_gem_in_gemfile? regex correctly excludes react_on_rails_pro via lookahead
  • All patterns are linear-time; no regex DoS risk

ihabadham added a commit that referenced this pull request May 15, 2026
…on main (#3301)

## Summary

Two markdown files were merged to `main` without passing the repo-wide
checks, which turns `markdown-format-check` and the `build` job's format
step **red on every open PR** (both run `prettier --check` over the
entire repo, not just changed files):

- `internal/planning/json-render-body-migration-plan.md` (from #3299) —
prettier formatting only.
- `reports/ror-memory-leakage-investigation.md` (from #3296) — prettier
formatting **and** a relative link to
`../../docs/pro/js-memory-leaks.md`, which is one level too high and
resolves outside the repo. The target exists at repo-root
`docs/pro/js-memory-leaks.md`; corrected to
`../docs/pro/js-memory-leaks.md`.

Formatting changes are `prettier --write` output only (no content
edits). The link change is a single relative-path correction; verified
the target file exists and the `markdown-links` (lychee) pre-commit hook
now passes.

## Why this is its own PR

These files are unrelated to any feature work, but their CI failures
block unrelated PRs (e.g. #3232). Fixing them on `main` directly via a
focused PR unblocks everything in flight.

## Test plan

- `pnpm exec prettier --check
internal/planning/json-render-body-migration-plan.md
reports/ror-memory-leakage-investigation.md` → clean
- `markdown-links` lefthook hook passes (corrected link resolves)
- No content changes beyond the one relative-path fix

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Documentation**
* Updated planning documentation with status updates for related pull
requests and issue tracking
* Enhanced investigation report with improved structure and readability
through reformatted content into consistent table formats

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/shakacode/react_on_rails/pull/3301)

<!-- review_stack_entry_end -->

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

react_on_rails:pro auto-upgrade: import rewriter misses jest.mock and declare module; Gemfile swap overwrites version pins

3 participants