Skip to content

Integration batch b4: castwide/solargraph#1407 - #240

Closed
apiology wants to merge 34 commits into
2026-10-19from
2026-10-19-batch-b4
Closed

apiology wants to merge 34 commits into
2026-10-19from
2026-10-19-batch-b4

Conversation

@apiology

Copy link
Copy Markdown
Owner

Claude: CI for the castwide#1407 merge before it goes onto #213; closed once merged or superseded.

castwide and others added 30 commits September 28, 2026 23:57
…de#1379)

* RbsCollection

* Gem caching in External spec

* RbsCollection specs

* Stale code

* Linting
The no-YARD gem mapper lost pins that YARD used to produce:

* Mixins called on an explicit receiver. Only receiverless include/extend/prepend
  were mapped, so ActiveSupport's `Object.prepend(self)` (which is how
  `require_dependency` reaches every class) and
  `Integer.prepend ActiveSupport::NumericWithFormat` made all the methods those
  modules add to core classes invisible.

* ActiveSupport's `class_attribute`, whose accessors are created at run time.
  Rails documents them with `## :singleton-method:` comment directives that YARD
  turned into methods. `process_class_attribute` generates the same accessors and
  honors instance_accessor/instance_reader/instance_writer/instance_predicate.

Fixes several solargraph-rails definitions (require_dependency on ActiveJob,
Rails::Application, ActionDispatch::Routing::Mapper and ActiveRecord; ActionController
callbacks; ActiveRecord::Base.enumerate_columns_in_select_statements;
ActiveRecord::Base#signed_id_verifier_secret; Integer#to_formatted_s).
External now selects collection entries for the gems it loads, but the lookup
expected a collection directory whose version matches the installed gem. Two
kinds of signature sets were silently dropped:

* Gems whose signatures are published under a different version than the gem.
  gem_rbs_collection ships `actionpack/6.0` for every Rails 6 and 7 release and
  `devise/4.9` for Devise 4 and 5, so actionpack 7.0.10 and devise 5.0.4 got no
  signatures at all (this is what hid the ActionController callback DSL).
  RbsCollection#best_key now falls back to the closest version the collection
  provides for that gem, preferring the highest one that does not exceed it.

* Stdlib entries in the lockfile (`date/0`, `json/0`, ...). They are not
  associated with a gem in the bundle, and `rbs collection install` resolves
  them to the signatures that ship with the rbs gem instead of copying them into
  the collection directory, so they can never be matched by a gem lookup.
  RbsCollection#stdlib_names exposes them and External loads them with
  Collection::Stdlib, which restores e.g. `Date#xmlschema -> String`.

Also guards External#rbs_collection_lockfile against a nil directory, which
previously short-circuited before the collection was touched.
Gem mapping without YARD globbed only a gem's require_paths, so code
an engine autoloads from app/ was never mapped. turbo-rails defines
Turbo::Broadcastable in app/models/concerns/, which left methods like
ActiveRecord::Base#broadcast_append unresolved where YARD-based
mapping (default file set {lib,app}/**/*.rb) found them.

Map app/**/*.rb alongside the require paths. Among installed gems,
only engines ship .rb files under app/ (actionmailbox, actiontext,
activestorage, devise, importmap-rails, solid_*, turbo-rails).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhUSLUimEYJyCZv8NdQGKb
"Autoload" read as Ruby's Module#autoload; the directories matter
because Rails loads them through Zeitwerk, not require.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhUSLUimEYJyCZv8NdQGKb
Gem pins are mapped from gem source and serialized, so a plugin's
conventions shape what lands in the gem cache. The language server
caches gems by running `solargraph cache` in a subprocess, which never
loaded the workspace's plugins, so the cache it wrote could differ from
the one an in-process typecheck builds.

`cache` and `uncache` now require the plugins configured for the
directory, through a Workspace::Config#require_plugins that Workspace
also delegates to. Each loaded plugin and its gem version are recorded
in CacheDir, and gem_dir includes them, so loading or upgrading a
plugin rebuilds the gem cache instead of reusing pins made without it.

The @sg-ignore in CacheDir.add_plugin records a typecheck defect:
Hash#each_value.find types the block parameter as the value type
unioned with the hash itself.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhUSLUimEYJyCZv8NdQGKb
Mapping app/ for every gem was Rails knowledge hard-coded in
Collection::Gem, and it also mapped app/ in gems that are not engines.
Behaviour outside the core language belongs in conventions.

Add Convention::Base#gem_directories, empty by default, which
Collection::Gem maps alongside a gem's require paths when it maps the
gem from source. Convention::RailsEngine, registered like
ActiveSupportConcern, returns Rails::Engine::Configuration's default
autoload roots (app/* and app/*/concerns without assets and javascript,
plus test/mailers/previews) for a gem whose require paths subclass
Rails::Engine.

Paths an engine adds through config.autoload_paths or paths.add are
not read: they are Ruby expressions, usually built from __dir__, and
the default roots cover the engines measured.

The roots are globbed from an absolute path rather than with base:,
because Dir.glob(pattern, base: dir) infers as Array<String>, nil at
strong level (the block overload is not ruled out), while the same call
without base: infers Array<String>.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhUSLUimEYJyCZv8NdQGKb
Solargraph keeps only the generic Convention#gem_directories hook;
solargraph-rails' convention supplies engine autoload roots. The
RailsEngine convention, its spec and its engine-only fixtures go with
it, and the Collection::Gem spec now exercises the hook through a
convention registered for the example.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhUSLUimEYJyCZv8NdQGKb
"gem_directories" read as directories holding one subdirectory per
gem, like GEM_PATH. The hook returns paths inside one gem, relative to
its root, to map in addition to its require_paths.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhUSLUimEYJyCZv8NdQGKb
Metagem is too new to expose to plugins, and conventions need only the
gem root and its require paths. Keyword arguments plus ** let
Solargraph pass more later without breaking plugins.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhUSLUimEYJyCZv8NdQGKb
apiology and others added 4 commits October 17, 2026 16:37
Retarget onto master now that gem-without-yard landed via castwide#1378.
Resolve the Collection::Gem#without_yard conflict in favor of this
branch, which adds Convention.extra_source_paths to the globbed dirs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SEntZKsZwQHhzqcwG7MTqn
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SEntZKsZwQHhzqcwG7MTqn
…s when mapping gems

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SEntZKsZwQHhzqcwG7MTqn
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SEntZKsZwQHhzqcwG7MTqn
@apiology

Copy link
Copy Markdown
Owner Author

Claude: Closing: superseded by batch b4b, which merges castwide#1407 with its fix for the 28 solargraph-rspec failures here.

@apiology apiology closed this Oct 21, 2026
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