├── ci └── auto_yard │ ├── lib │ └── .keep │ ├── .gitignore │ ├── plugins.rb │ └── auto_yard.gemspec ├── spec ├── rails8 │ ├── log │ │ └── .keep │ ├── tmp │ │ ├── .keep │ │ ├── pids │ │ │ └── .keep │ │ └── storage │ │ │ └── .keep │ ├── lib │ │ └── tasks │ │ │ └── .keep │ ├── script │ │ └── .keep │ ├── storage │ │ └── .keep │ ├── test │ │ ├── helpers │ │ │ └── .keep │ │ ├── mailers │ │ │ └── .keep │ │ ├── models │ │ │ └── .keep │ │ ├── system │ │ │ └── .keep │ │ ├── controllers │ │ │ └── .keep │ │ ├── fixtures │ │ │ └── files │ │ │ │ └── .keep │ │ ├── integration │ │ │ └── .keep │ │ ├── application_system_test_case.rb │ │ └── test_helper.rb │ ├── .ruby-version │ ├── app │ │ ├── assets │ │ │ ├── images │ │ │ │ └── .keep │ │ │ └── stylesheets │ │ │ │ └── application.css │ │ ├── models │ │ │ ├── concerns │ │ │ │ └── .keep │ │ │ └── application_record.rb │ │ ├── controllers │ │ │ ├── concerns │ │ │ │ └── .keep │ │ │ └── application_controller.rb │ │ ├── views │ │ │ ├── layouts │ │ │ │ ├── mailer.text.erb │ │ │ │ ├── mailer.html.erb │ │ │ │ └── application.html.erb │ │ │ └── pwa │ │ │ │ ├── manifest.json.erb │ │ │ │ └── service-worker.js │ │ ├── helpers │ │ │ └── application_helper.rb │ │ ├── mailers │ │ │ └── application_mailer.rb │ │ └── jobs │ │ │ └── application_job.rb │ ├── bin │ │ ├── dev │ │ ├── rake │ │ ├── thrust │ │ ├── rails │ │ ├── brakeman │ │ ├── rubocop │ │ ├── docker-entrypoint │ │ ├── kamal │ │ ├── setup │ │ └── bundle │ ├── .kamal │ │ ├── hooks │ │ │ ├── docker-setup.sample │ │ │ ├── post-proxy-reboot.sample │ │ │ ├── pre-proxy-reboot.sample │ │ │ ├── post-app-boot.sample │ │ │ ├── pre-app-boot.sample │ │ │ ├── post-deploy.sample │ │ │ ├── pre-connect.sample │ │ │ ├── pre-build.sample │ │ │ └── pre-deploy.sample │ │ └── secrets │ ├── public │ │ ├── icon.png │ │ ├── robots.txt │ │ ├── icon.svg │ │ ├── 404.html │ │ ├── 400.html │ │ └── 406-unsupported-browser.html │ ├── config │ │ ├── environment.rb │ │ ├── boot.rb │ │ ├── cable.yml │ │ ├── initializers │ │ │ ├── assets.rb │ │ │ ├── filter_parameter_logging.rb │ │ │ ├── inflections.rb │ │ │ └── content_security_policy.rb │ │ ├── credentials.yml.enc │ │ ├── routes.rb │ │ ├── locales │ │ │ └── en.yml │ │ ├── application.rb │ │ ├── storage.yml │ │ ├── database.yml │ │ ├── puma.rb │ │ ├── environments │ │ │ ├── test.rb │ │ │ ├── development.rb │ │ │ └── production.rb │ │ └── deploy.yml │ ├── config.ru │ ├── Rakefile │ ├── .github │ │ ├── dependabot.yml │ │ └── workflows │ │ │ └── ci.yml │ ├── .solargraph.yml │ ├── .gitattributes │ ├── README.md │ ├── db │ │ └── seeds.rb │ ├── rbs_collection.yaml │ ├── .gitignore │ ├── .dockerignore │ ├── Dockerfile │ └── Gemfile ├── rails7 │ ├── app │ │ ├── mailers │ │ │ └── .gitkeep │ │ ├── models │ │ │ └── .gitkeep │ │ ├── assets │ │ │ └── config │ │ │ │ └── manifest.js │ │ └── controllers │ │ │ └── application_controller.rb │ ├── db │ │ ├── migrate │ │ │ └── .gitkeep │ │ └── dev.sqlite3 │ ├── .gitignore │ ├── config │ │ ├── storage.yml │ │ ├── database.yml │ │ ├── boot.rb │ │ ├── application.rb │ │ └── environment.rb │ ├── config.ru │ ├── Gemfile │ ├── Rakefile │ ├── .solargraph.yml │ └── rbs_collection.yaml ├── definitions │ └── core │ │ ├── Object.yml │ │ ├── Enumerable.yml │ │ └── Kernel.yml ├── solargraph-rails │ ├── storage_spec.rb │ ├── devise_spec.rb │ ├── autoload_spec.rb │ ├── delegate_spec.rb │ ├── annotate_spec.rb │ ├── schema_spec.rb │ ├── rails_spec.rb │ └── model_spec.rb ├── spec_helper.rb └── helpers.rb ├── .rspec ├── assets ├── peek.png ├── solar_rails_goto.gif ├── solar_rails_associations.gif ├── solar_rails_autocomplete.gif ├── sg_rails_1_0_routes_support.gif ├── sg_rails_1_0_activerecord_support.gif ├── sg_rails_1_0_association_completion.gif └── sg_rails_1_0_go_to_attribute_definition.gif ├── lib ├── solargraph │ └── rails │ │ ├── annotations │ │ ├── class.rb │ │ ├── array.rb │ │ ├── action_mailer.rb │ │ ├── object.rb │ │ ├── README.md │ │ ├── module.rb │ │ ├── active_support.rb │ │ ├── action_text.rb │ │ ├── rails.rb │ │ ├── active_model.rb │ │ ├── time.rb │ │ ├── action_dispatch.rb │ │ ├── date.rb │ │ ├── stdlib_requires.rb │ │ ├── action_controller.rb │ │ └── active_storage.rb │ │ ├── version.rb │ │ ├── puma.rb │ │ ├── importmap.rb │ │ ├── debug.rb │ │ ├── devise.rb │ │ ├── storage.rb │ │ ├── rails_api.rb │ │ ├── annotate.rb │ │ ├── autoload.rb │ │ ├── walker.rb │ │ ├── delegate.rb │ │ ├── util.rb │ │ └── schema.rb └── solargraph-rails.rb ├── .travis.yml ├── Rakefile ├── bin ├── setup ├── console ├── rubocop └── overcommit ├── .gitignore ├── .solargraph.yml ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ ├── typecheck.yml │ ├── linter.yml │ └── rails_new.yml ├── LICENSE.txt ├── .overcommit.yml ├── solargraph-rails.gemspec ├── Gemfile ├── DEVELOPMENT.md ├── README.md ├── script └── generate_definitions.rb └── CHANGELOG.md /ci/auto_yard/lib/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/rails8/log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/rails8/tmp/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/rails8/lib/tasks/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/rails8/script/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/rails8/storage/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/rails8/tmp/pids/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/rails7/app/mailers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/rails7/app/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/rails7/db/migrate/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/rails8/test/helpers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/rails8/test/mailers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/rails8/test/models/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/rails8/test/system/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/rails8/tmp/storage/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/rails8/.ruby-version: -------------------------------------------------------------------------------- 1 | 3.2.8 2 | -------------------------------------------------------------------------------- /spec/rails8/app/assets/images/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/rails8/app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/rails8/test/controllers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/rails8/test/fixtures/files/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/rails8/test/integration/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/rails7/app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/rails8/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/rails8/app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /spec/rails7/.gitignore: -------------------------------------------------------------------------------- 1 | /.gem_rbs_collection 2 | /rbs_collection.lock.yaml -------------------------------------------------------------------------------- /spec/rails8/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /spec/rails8/bin/dev: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | exec "./bin/rails", "server", *ARGV 3 | -------------------------------------------------------------------------------- /assets/peek.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftheshoefritz/solargraph-rails/HEAD/assets/peek.png -------------------------------------------------------------------------------- /lib/solargraph/rails/annotations/class.rb: -------------------------------------------------------------------------------- 1 | # @!override Class#subclasses 2 | # @return [Array] 3 | -------------------------------------------------------------------------------- /spec/rails7/config/storage.yml: -------------------------------------------------------------------------------- 1 | local: 2 | service: Disk 3 | root: <%= Rails.root.join("storage") %> 4 | -------------------------------------------------------------------------------- /spec/rails8/.kamal/hooks/docker-setup.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Docker set up on $KAMAL_HOSTS..." 4 | -------------------------------------------------------------------------------- /lib/solargraph/rails/version.rb: -------------------------------------------------------------------------------- 1 | module Solargraph 2 | module Rails 3 | VERSION = '1.2.4' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/definitions/core/Object.yml: -------------------------------------------------------------------------------- 1 | --- 2 | Object#with_options: 3 | types: 4 | - undefined 5 | skip: false 6 | -------------------------------------------------------------------------------- /spec/rails7/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | end 3 | -------------------------------------------------------------------------------- /spec/rails7/config/database.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: sqlite3 3 | pool: 5 4 | database: db/dev.sqlite3 5 | -------------------------------------------------------------------------------- /spec/rails8/.kamal/hooks/post-proxy-reboot.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Rebooted kamal-proxy on $KAMAL_HOSTS" 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: ruby 3 | rvm: 4 | - 2.5.0 5 | before_install: gem install bundler -v 1.16.1 6 | -------------------------------------------------------------------------------- /assets/solar_rails_goto.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftheshoefritz/solargraph-rails/HEAD/assets/solar_rails_goto.gif -------------------------------------------------------------------------------- /spec/rails7/db/dev.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftheshoefritz/solargraph-rails/HEAD/spec/rails7/db/dev.sqlite3 -------------------------------------------------------------------------------- /spec/rails8/.kamal/hooks/pre-proxy-reboot.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Rebooting kamal-proxy on $KAMAL_HOSTS..." 4 | -------------------------------------------------------------------------------- /spec/rails8/public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftheshoefritz/solargraph-rails/HEAD/spec/rails8/public/icon.png -------------------------------------------------------------------------------- /ci/auto_yard/.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | -------------------------------------------------------------------------------- /spec/rails8/.kamal/hooks/post-app-boot.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Booted app version $KAMAL_VERSION on $KAMAL_HOSTS..." 4 | -------------------------------------------------------------------------------- /spec/rails8/.kamal/hooks/pre-app-boot.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Booting app version $KAMAL_VERSION on $KAMAL_HOSTS..." 4 | -------------------------------------------------------------------------------- /spec/rails8/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative "../config/boot" 3 | require "rake" 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /spec/rails8/app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | primary_abstract_class 3 | end 4 | -------------------------------------------------------------------------------- /spec/rails8/public/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /assets/solar_rails_associations.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftheshoefritz/solargraph-rails/HEAD/assets/solar_rails_associations.gif -------------------------------------------------------------------------------- /assets/solar_rails_autocomplete.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftheshoefritz/solargraph-rails/HEAD/assets/solar_rails_autocomplete.gif -------------------------------------------------------------------------------- /assets/sg_rails_1_0_routes_support.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftheshoefritz/solargraph-rails/HEAD/assets/sg_rails_1_0_routes_support.gif -------------------------------------------------------------------------------- /spec/rails8/bin/thrust: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "rubygems" 3 | require "bundler/setup" 4 | 5 | load Gem.bin_path("thruster", "thrust") 6 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task :default => :spec 7 | -------------------------------------------------------------------------------- /assets/sg_rails_1_0_activerecord_support.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftheshoefritz/solargraph-rails/HEAD/assets/sg_rails_1_0_activerecord_support.gif -------------------------------------------------------------------------------- /assets/sg_rails_1_0_association_completion.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftheshoefritz/solargraph-rails/HEAD/assets/sg_rails_1_0_association_completion.gif -------------------------------------------------------------------------------- /spec/rails8/app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: "from@example.com" 3 | layout "mailer" 4 | end 5 | -------------------------------------------------------------------------------- /spec/rails8/public/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/sg_rails_1_0_go_to_attribute_definition.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iftheshoefritz/solargraph-rails/HEAD/assets/sg_rails_1_0_go_to_attribute_definition.gif -------------------------------------------------------------------------------- /spec/rails7/config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /spec/rails7/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative 'config/environment' 4 | 5 | run Rails.application 6 | -------------------------------------------------------------------------------- /spec/rails8/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path("../config/application", __dir__) 3 | require_relative "../config/boot" 4 | require "rails/commands" 5 | -------------------------------------------------------------------------------- /spec/rails8/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative "application" 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /spec/rails7/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'devise' 4 | gem 'rails', "~> #{ENV.fetch('MATRIX_RAILS_VERSION', '7.0')}.0" 5 | gem 'sqlite3' 6 | gem 'pry' 7 | gem 'rake' 8 | -------------------------------------------------------------------------------- /spec/rails8/bin/brakeman: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "rubygems" 3 | require "bundler/setup" 4 | 5 | ARGV.unshift("--ensure-latest") 6 | 7 | load Gem.bin_path("brakeman", "brakeman") 8 | -------------------------------------------------------------------------------- /lib/solargraph/rails/annotations/array.rb: -------------------------------------------------------------------------------- 1 | class Array 2 | def sum; end 3 | 4 | # @param format [Symbol] 5 | # @return [String] 6 | def to_formatted_s(format = :some_default); end 7 | end 8 | -------------------------------------------------------------------------------- /spec/rails8/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative "config/environment" 4 | 5 | run Rails.application 6 | Rails.application.load_server 7 | -------------------------------------------------------------------------------- /lib/solargraph/rails/annotations/action_mailer.rb: -------------------------------------------------------------------------------- 1 | class ActionMailer::Base 2 | # @return [self] 3 | def self.with(**params); end 4 | # 5 | # @return [ActionMailer::MessageDelivery] 6 | def mail(**params); end 7 | end 8 | -------------------------------------------------------------------------------- /spec/rails7/config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative 'boot' 2 | require 'rails/all' 3 | 4 | Bundler.require(*Rails.groups) 5 | 6 | module MyApp 7 | class Application < Rails::Application 8 | ## 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/rails8/test/application_system_test_case.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ApplicationSystemTestCase < ActionDispatch::SystemTestCase 4 | driven_by :selenium, using: :headless_chrome, screen_size: [ 1400, 1400 ] 5 | end 6 | -------------------------------------------------------------------------------- /spec/rails8/config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 2 | 3 | require "bundler/setup" # Set up gems listed in the Gemfile. 4 | require "bootsnap/setup" # Speed up boot time by caching expensive operations. 5 | -------------------------------------------------------------------------------- /lib/solargraph/rails/annotations/object.rb: -------------------------------------------------------------------------------- 1 | class Object 2 | # @return [self, nil] 3 | def presence; end 4 | 5 | # @return [self, nil] 6 | def presence_in(x); end 7 | end 8 | 9 | # @!override Object#present? 10 | # @return [Boolean] 11 | -------------------------------------------------------------------------------- /spec/rails8/config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: test 6 | 7 | production: 8 | adapter: redis 9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 10 | channel_prefix: rails8_production 11 | -------------------------------------------------------------------------------- /spec/rails7/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative 'config/application' 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /spec/rails8/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative "config/application" 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /spec/rails8/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has. 3 | allow_browser versions: :modern 4 | end 5 | -------------------------------------------------------------------------------- /lib/solargraph/rails/annotations/README.md: -------------------------------------------------------------------------------- 1 | # annotations 2 | 3 | Code in this directory is _not_ loaded or evaluated at runtime, either by solargraph or your application. 4 | 5 | The code in [rails_api.rb](../rails_api.rb) loads each of these files inside a YARD `@!parse` directive. 6 | -------------------------------------------------------------------------------- /spec/rails7/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 3 | 4 | Rails.application.configure do 5 | config.eager_load = true 6 | config.active_storage.service = :local 7 | end 8 | 9 | # Initialize the Rails application. 10 | Rails.application.initialize! 11 | -------------------------------------------------------------------------------- /spec/rails8/bin/rubocop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "rubygems" 3 | require "bundler/setup" 4 | 5 | # explicit rubocop config increases performance slightly while avoiding config confusion. 6 | ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) 7 | 8 | load Gem.bin_path("rubocop", "rubocop") 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | .DS_Store 10 | spec/rails7/log/ 11 | spec/rails7/tmp/ 12 | .projections.json 13 | .byebug_history 14 | 15 | # rspec failure tracking 16 | .rspec_status 17 | Gemfile.lock 18 | /.Gemfile 19 | vendor 20 | -------------------------------------------------------------------------------- /lib/solargraph/rails/annotations/module.rb: -------------------------------------------------------------------------------- 1 | class Module 2 | # @return [self, nil] 3 | def self.presence; end 4 | 5 | # @return [self, nil] 6 | def self.presence_in(x); end 7 | 8 | # @return [self, nil] 9 | def presence; end 10 | 11 | # @return [self, nil] 12 | def presence_in(x); end 13 | end 14 | -------------------------------------------------------------------------------- /spec/rails8/app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | # Automatically retry jobs that encountered a deadlock 3 | # retry_on ActiveRecord::Deadlocked 4 | 5 | # Most jobs are safe to ignore if the underlying records are no longer available 6 | # discard_on ActiveJob::DeserializationError 7 | end 8 | -------------------------------------------------------------------------------- /spec/rails8/app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /spec/rails8/.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: bundler 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | - package-ecosystem: github-actions 9 | directory: "/" 10 | schedule: 11 | interval: daily 12 | open-pull-requests-limit: 10 13 | -------------------------------------------------------------------------------- /lib/solargraph/rails/annotations/active_support.rb: -------------------------------------------------------------------------------- 1 | # @!override ActiveSupport::Rescuable::ClassMethods#rescue_from 2 | # @return [void] 3 | 4 | # @!override ActiveSupport::DescendantsTracker#subclasses 5 | # @return [Array] 6 | 7 | # @!override ActiveSupport::DescendantsTracker::ReloadedClassesFiltering#subclasses 8 | # @return [Array] 9 | -------------------------------------------------------------------------------- /spec/rails8/config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = "1.0" 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | -------------------------------------------------------------------------------- /spec/rails7/.solargraph.yml: -------------------------------------------------------------------------------- 1 | include: 2 | - "**/*.rb" 3 | exclude: 4 | - test/**/* 5 | - vendor/**/* 6 | - ".bundle/**/*" 7 | require: [] 8 | domains: [] 9 | reporters: [] 10 | formatter: 11 | rubocop: 12 | cops: safe 13 | except: [] 14 | only: [] 15 | extra_args: [] 16 | require_paths: [] 17 | plugins: [solargraph-rails] 18 | max_files: 5000 19 | -------------------------------------------------------------------------------- /spec/rails8/.solargraph.yml: -------------------------------------------------------------------------------- 1 | include: 2 | - "**/*.rb" 3 | exclude: 4 | - test/**/* 5 | - vendor/**/* 6 | - ".bundle/**/*" 7 | require: [] 8 | domains: [] 9 | reporters: [] 10 | formatter: 11 | rubocop: 12 | cops: safe 13 | except: [] 14 | only: [] 15 | extra_args: [] 16 | require_paths: [] 17 | plugins: [solargraph-rails] 18 | max_files: 5000 19 | -------------------------------------------------------------------------------- /spec/rails8/.gitattributes: -------------------------------------------------------------------------------- 1 | # See https://git-scm.com/docs/gitattributes for more about git attribute files. 2 | 3 | # Mark the database schema as having been generated. 4 | db/schema.rb linguist-generated 5 | 6 | # Mark any vendored files as having been vendored. 7 | vendor/* linguist-vendored 8 | config/credentials/*.yml.enc diff=rails_credentials 9 | config/credentials.yml.enc diff=rails_credentials 10 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "solargraph-rails" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start(__FILE__) 15 | -------------------------------------------------------------------------------- /spec/rails8/.kamal/hooks/post-deploy.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # A sample post-deploy hook 4 | # 5 | # These environment variables are available: 6 | # KAMAL_RECORDED_AT 7 | # KAMAL_PERFORMER 8 | # KAMAL_VERSION 9 | # KAMAL_HOSTS 10 | # KAMAL_ROLES (if set) 11 | # KAMAL_DESTINATION (if set) 12 | # KAMAL_RUNTIME 13 | 14 | echo "$KAMAL_PERFORMER deployed $KAMAL_VERSION to $KAMAL_DESTINATION in $KAMAL_RUNTIME seconds" 15 | -------------------------------------------------------------------------------- /lib/solargraph/rails/annotations/action_text.rb: -------------------------------------------------------------------------------- 1 | module ActionText 2 | module Attribute 3 | # @param name [String, Symbol] 4 | # @param encrypted [Boolean] 5 | # @param strict_loading [Boolean] 6 | # @return [void] 7 | def self.has_rich_text(name, encrypted = false, strict_loading = false); end 8 | # @return [Array] 9 | def self.rich_text_association_names; end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/rails8/bin/docker-entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Enable jemalloc for reduced memory usage and latency. 4 | if [ -z "${LD_PRELOAD+x}" ]; then 5 | LD_PRELOAD=$(find /usr/lib -name libjemalloc.so.2 -print -quit) 6 | export LD_PRELOAD 7 | fi 8 | 9 | # If running the rails server then create or migrate existing database 10 | if [ "${@: -2:1}" == "./bin/rails" ] && [ "${@: -1:1}" == "server" ]; then 11 | ./bin/rails db:prepare 12 | fi 13 | 14 | exec "${@}" 15 | -------------------------------------------------------------------------------- /spec/rails8/README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | This README would normally document whatever steps are necessary to get the 4 | application up and running. 5 | 6 | Things you may want to cover: 7 | 8 | * Ruby version 9 | 10 | * System dependencies 11 | 12 | * Configuration 13 | 14 | * Database creation 15 | 16 | * Database initialization 17 | 18 | * How to run the test suite 19 | 20 | * Services (job queues, cache servers, search engines, etc.) 21 | 22 | * Deployment instructions 23 | 24 | * ... 25 | -------------------------------------------------------------------------------- /spec/rails8/test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] ||= "test" 2 | require_relative "../config/environment" 3 | require "rails/test_help" 4 | 5 | module ActiveSupport 6 | class TestCase 7 | # Run tests in parallel with specified workers 8 | parallelize(workers: :number_of_processors) 9 | 10 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. 11 | fixtures :all 12 | 13 | # Add more helper methods to be used by all tests here... 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/solargraph/rails/annotations/rails.rb: -------------------------------------------------------------------------------- 1 | class Rails 2 | # @return [Rails::Application] 3 | def self.application; end 4 | end 5 | 6 | class Rails::Engine 7 | # @return [ActionDispatch::Routing::RouteSet] 8 | def routes; end 9 | end 10 | 11 | class Rails::Application 12 | # @return [ActionDispatch::Routing::RouteSet] 13 | def routes; end 14 | # @return [Rails::Application::Configuration] 15 | def config; end 16 | # @return [Rails::Application::Configuration] 17 | def self.config; end 18 | end 19 | -------------------------------------------------------------------------------- /ci/auto_yard/plugins.rb: -------------------------------------------------------------------------------- 1 | Bundler::Plugin.add_hook('after-install-all') do 2 | cmd = 'bundle exec yard gems --plugin solargraph' 3 | STDERR.puts("Installing yard info using #{cmd.inspect}") 4 | # Attempt to run yard, and if it fails, try again - sometimes YARD gets indigestion on a given gem 5 | system(cmd + ' || ' + cmd) 6 | STDERR.puts("Installed yard info using #{cmd.inspect}") 7 | rescue StandardError => e 8 | STDERR.puts("Error running yard gems: #{e.message}") 9 | STDERR.puts("Backtrace:\n#{e.backtrace}") 10 | end 11 | -------------------------------------------------------------------------------- /spec/rails8/app/views/pwa/manifest.json.erb: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Rails8", 3 | "icons": [ 4 | { 5 | "src": "/icon.png", 6 | "type": "image/png", 7 | "sizes": "512x512" 8 | }, 9 | { 10 | "src": "/icon.png", 11 | "type": "image/png", 12 | "sizes": "512x512", 13 | "purpose": "maskable" 14 | } 15 | ], 16 | "start_url": "/", 17 | "display": "standalone", 18 | "scope": "/", 19 | "description": "Rails8.", 20 | "theme_color": "red", 21 | "background_color": "red" 22 | } 23 | -------------------------------------------------------------------------------- /spec/rails8/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. 4 | # Use this to limit dissemination of sensitive information. 5 | # See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. 6 | Rails.application.config.filter_parameters += [ 7 | :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc 8 | ] 9 | -------------------------------------------------------------------------------- /spec/rails8/db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should ensure the existence of records required to run the application in every environment (production, 2 | # development, test). The code here should be idempotent so that it can be executed at any point in every environment. 3 | # The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). 4 | # 5 | # Example: 6 | # 7 | # ["Action", "Comedy", "Drama", "Horror"].each do |genre_name| 8 | # MovieGenre.find_or_create_by!(name: genre_name) 9 | # end 10 | -------------------------------------------------------------------------------- /.solargraph.yml: -------------------------------------------------------------------------------- 1 | --- 2 | include: 3 | - "**/*.rb" 4 | - "Gemfile" 5 | - "Rakefile" 6 | - "*.gemspec" 7 | exclude: 8 | - spec/**/* 9 | - lib/solargraph/rails/annotations/**/* 10 | - vendor/**/* 11 | - ".bundle/**/*" 12 | require: 13 | # we generally assume a solargraph require has been made and don't make it explicitly 14 | - solargraph 15 | domains: [] 16 | reporters: [] 17 | formatter: 18 | rubocop: 19 | cops: safe 20 | except: [] 21 | only: [] 22 | extra_args: [] 23 | require_paths: [] 24 | plugins: [] 25 | max_files: 5000 26 | -------------------------------------------------------------------------------- /lib/solargraph/rails/annotations/active_model.rb: -------------------------------------------------------------------------------- 1 | module ActiveModel 2 | module Translation 3 | # @param options [Hash] options to customize the output 4 | # @param attribute [Symbol, String] the name of the attribute 5 | # @return [String] 6 | def human_attribute_name(attribute, options = {}); end 7 | end 8 | 9 | module Naming 10 | # @return [ActiveModel::Name] the model name for the class 11 | def model_name; end 12 | end 13 | 14 | module Validations 15 | # @return [Boolean] 16 | def validate; end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/rails8/config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | nsvA339g4VgUC3m5NbgwRJEiv0cMi6PyjSE00XVk3t22NBgA3TFD8G/nziNaSiMxwbhMIqdHdYpQLHby1mtwGhuT5u9fhl8hGvvVQL4rMICNc77sEU6YkVL2nVRgFlI9bNh3AcsgUf6Mz2E54BKERpfas4hadk3KUAIR/P4dEvXx4x+aNxgMCBUVdsZDPBilqxewiBU0mQdEHI0HVT8MpYfEbRpESTEBq3GEIJ3eVltoUu3nauNWPSCI2jMu0TrMn1msrTDao280Ss819pYCuudcC0UvctVxjU5O4WFu7kHwNoOlYimOG5Mfrnk5l/NyWmPmyzvd5zv8Vjoy+cWNUC1CLijT5lHnTTJTPEyaHMNZXLu2bJNnUGOh21D/3w3LMhPVfRBiE5nXEqXb2DMuTKnSGP+QO7GwOZrPJNXb+bKgTUYr2Ss9EktNbHBpiaUgEsu46nxzlBC98oQAF3bJKuhaZ8ZBizz35eKrSureo1OfAgLG+rxA+YXC--8fmepHCU4GHJlqkZ--l5zbcPO/11Qq1CK2xpbXzQ== -------------------------------------------------------------------------------- /spec/rails8/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css. 3 | * 4 | * With Propshaft, assets are served efficiently without preprocessing steps. You can still include 5 | * application-wide styles in this file, but keep in mind that CSS precedence will follow the standard 6 | * cascading order, meaning styles declared later in the document or manifest will override earlier ones, 7 | * depending on specificity. 8 | * 9 | * Consider organizing styles into separate files for maintainability. 10 | */ 11 | -------------------------------------------------------------------------------- /ci/auto_yard/auto_yard.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Gem::Specification.new do |spec| 4 | spec.name = 'auto_yard' 5 | spec.version = '0.0.1' 6 | spec.authors = ['Stephen Sugden'] 7 | spec.email = ['grncdr@users.noreply.github.com'] 8 | 9 | spec.summary = 'Run yard gems automatically after bundle install' 10 | spec.description = spec.summary 11 | spec.license = 'MIT' 12 | spec.required_ruby_version = '>= 2.6.0' 13 | 14 | spec.homepage = 'https://github.com' 15 | spec.metadata['homepage_uri'] = spec.homepage 16 | 17 | spec.files = ['plugins.rb'] 18 | end 19 | -------------------------------------------------------------------------------- /spec/solargraph-rails/storage_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe Solargraph::Rails::Storage do 4 | it "can auto-complete ActiveStorage" do 5 | filename = nil 6 | map = rails_workspace do |root| 7 | filename = root.write_file 'app/models/thing.rb', <<~EOS 8 | class Thing < ActiveRecord::Base 9 | has_one_attached :image 10 | has_many_attached :photos 11 | end 12 | 13 | Thing.new.image.att 14 | Thing.new.photos.att 15 | EOS 16 | end 17 | 18 | expect(completion_at(filename, [5, 19], map)).to include("attach") 19 | expect(completion_at(filename, [6, 20], map)).to include("attach") 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/rails8/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, "\\1en" 8 | # inflect.singular /^(ox)en/i, "\\1" 9 | # inflect.irregular "person", "people" 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym "RESTful" 16 | # end 17 | -------------------------------------------------------------------------------- /spec/rails8/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html 3 | 4 | # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. 5 | # Can be used by load balancers and uptime monitors to verify that the app is live. 6 | get "up" => "rails/health#show", as: :rails_health_check 7 | 8 | # Render dynamic PWA files from app/views/pwa/* (remember to link manifest in application.html.erb) 9 | # get "manifest" => "rails/pwa#manifest", as: :pwa_manifest 10 | # get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker 11 | 12 | # Defines the root path route ("/") 13 | # root "posts#index" 14 | end 15 | -------------------------------------------------------------------------------- /lib/solargraph/rails/puma.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Solargraph 4 | module Rails 5 | class Puma 6 | EMPTY_ENVIRON = Environ.new 7 | 8 | # @return [Solargraph::Rails::Puma] 9 | def self.instance 10 | @instance ||= new 11 | end 12 | 13 | # @param environ [Solargraph::Environ] 14 | # @param basename [String] 15 | # 16 | # @return [void] 17 | def add_dsl(environ, basename) 18 | return unless basename == 'puma.rb' 19 | 20 | environ.requires.push('puma') 21 | environ.domains.push('Puma::DSL') 22 | 23 | Solargraph.logger.debug( 24 | "[Rails][Puma] added DSL to environ: #{environ.inspect}" 25 | ) 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/solargraph/rails/importmap.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Solargraph 4 | module Rails 5 | class Importmap 6 | EMPTY_ENVIRON = Environ.new 7 | 8 | # @return [Solargraph::Rails::Importmap] 9 | def self.instance 10 | @instance ||= new 11 | end 12 | 13 | # @param environ [Solargraph::Environ] 14 | # @param basename [String] 15 | # 16 | # @return [void] 17 | def add_dsl(environ, basename) 18 | return unless basename == 'importmap.rb' 19 | 20 | environ.requires.push('importmap-rails') 21 | environ.domains.push('Importmap::Map') 22 | 23 | Solargraph.logger.debug( 24 | "[Rails][Importmap] added DSL to environ: #{environ.inspect}" 25 | ) 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Debug log** 27 | 28 | Run the following command in the project you are having problems: 29 | ``` 30 | ruby -r'solargraph-rails' -e 'Solargraph::Rails::Debug.run()' 31 | ``` 32 | 33 | and paste the output here 34 | -------------------------------------------------------------------------------- /spec/solargraph-rails/devise_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Solargraph::Rails::Devise do 4 | it "includes devise modules" do 5 | filename = nil 6 | map = rails_workspace do |root| 7 | root.write_file 'app/models/awesome_user.rb', <<~RUBY 8 | class AwesomeUser < ActiveRecord::Base 9 | devise :registerable, :confirmable, :timeoutable, timeout_in: 12.hours 10 | end 11 | RUBY 12 | 13 | filename = root.write_file 'app/controllers/pages_controller.rb', <<~RUBY 14 | class PagesController < ApplicationController 15 | def index 16 | curr 17 | AwesomeUser.new.conf 18 | end 19 | end 20 | RUBY 21 | end 22 | 23 | expect(completion_at(filename, [3, 23], map)).to include("confirm") 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/solargraph/rails/annotations/time.rb: -------------------------------------------------------------------------------- 1 | require 'time' 2 | 3 | class Time 4 | # @return [Boolean] 5 | def eql_with_coercion(other); end 6 | 7 | # @return [-1, 0, 1, nil] 8 | def compare_with_coercion(other); end 9 | 10 | # @return [-1, 0, 1, nil] 11 | def compare_without_coercion(other); end 12 | 13 | # @return [-1, 0, 1, nil] 14 | def <=>(other); end 15 | 16 | # @return [Time] 17 | def +(other); end 18 | 19 | # @param seconds [Integer, Float] 20 | # @param microseconds [Integer] 21 | # @param utc [Boolean] 22 | # @return [Time] 23 | def self.at(seconds, microseconds = 0, utc = false); end 24 | 25 | # @return [Time] 26 | def to_time; end 27 | 28 | # @param format [Symbol] 29 | # @return [String] 30 | def to_formatted_s(format = :some_default); end 31 | end 32 | 33 | # @!override Time#+ 34 | # @return [Time] 35 | -------------------------------------------------------------------------------- /lib/solargraph/rails/debug.rb: -------------------------------------------------------------------------------- 1 | module Solargraph 2 | module Rails 3 | class Debug 4 | def self.run(query = nil) 5 | self.new.run(query) 6 | end 7 | 8 | def run(query) 9 | Solargraph.logger.level = Logger::DEBUG 10 | 11 | api_map = Solargraph::ApiMap.load('./') 12 | 13 | puts "Ruby version: #{RUBY_VERSION}" 14 | puts "Solargraph version: #{Solargraph::VERSION}" 15 | puts "Solargraph Rails version: #{Solargraph::Rails::VERSION}" 16 | 17 | return unless query 18 | 19 | puts "Known methods for #{query}" 20 | 21 | pin = api_map.pins.find { |p| p.path == query } 22 | return unless pin 23 | 24 | api_map 25 | .get_complex_type_methods(pin.return_type) 26 | .each { |pin| puts "- #{pin.path}" } 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /bin/rubocop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'rubocop' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 12 | 13 | bundle_binstub = File.expand_path("bundle", __dir__) 14 | 15 | if File.file?(bundle_binstub) 16 | if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") 17 | load(bundle_binstub) 18 | else 19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 21 | end 22 | end 23 | 24 | require "rubygems" 25 | require "bundler/setup" 26 | 27 | load Gem.bin_path("rubocop", "rubocop") 28 | -------------------------------------------------------------------------------- /spec/rails8/bin/kamal: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'kamal' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 12 | 13 | bundle_binstub = File.expand_path("bundle", __dir__) 14 | 15 | if File.file?(bundle_binstub) 16 | if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") 17 | load(bundle_binstub) 18 | else 19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 21 | end 22 | end 23 | 24 | require "rubygems" 25 | require "bundler/setup" 26 | 27 | load Gem.bin_path("kamal", "kamal") 28 | -------------------------------------------------------------------------------- /bin/overcommit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'overcommit' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 12 | 13 | bundle_binstub = File.expand_path("bundle", __dir__) 14 | 15 | if File.file?(bundle_binstub) 16 | if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") 17 | load(bundle_binstub) 18 | else 19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 21 | end 22 | end 23 | 24 | require "rubygems" 25 | require "bundler/setup" 26 | 27 | load Gem.bin_path("overcommit", "overcommit") 28 | -------------------------------------------------------------------------------- /spec/rails8/rbs_collection.yaml: -------------------------------------------------------------------------------- 1 | # Download sources 2 | sources: 3 | - type: git 4 | name: ruby/gem_rbs_collection 5 | remote: https://github.com/ruby/gem_rbs_collection.git 6 | # locked for test stability - you're encouraged to bump this and 7 | # submit a PR with the definition changes! we just don't want 8 | # things failing after unrelated changes. 9 | # 10 | # https://github.com/ruby/gem_rbs_collection/commits/main/ 11 | revision: ed61b16bf0a42201814e6a3baa9663d9f3a69046 12 | repo_dir: gems 13 | 14 | # You can specify local directories as sources also. 15 | # - type: local 16 | # path: path/to/your/local/repository 17 | 18 | # A directory to install the downloaded RBSs 19 | path: .gem_rbs_collection 20 | 21 | # gems: 22 | # # If you want to avoid installing rbs files for gems, you can specify them here. 23 | # - name: GEM_NAME 24 | # ignore: true 25 | -------------------------------------------------------------------------------- /lib/solargraph/rails/annotations/action_dispatch.rb: -------------------------------------------------------------------------------- 1 | module ActionDispatch 2 | module Flash 3 | class FlashHash 4 | # @return [ActionDispatch::Flash::FlashNow] 5 | def now; end 6 | end 7 | end 8 | 9 | module Routing 10 | class Mapper 11 | include ActionDispatch::Routing::Mapper::Base 12 | include ActionDispatch::Routing::Mapper::HttpHelpers 13 | include ActionDispatch::Routing::Mapper::Redirection 14 | include ActionDispatch::Routing::Mapper::Scoping 15 | include ActionDispatch::Routing::Mapper::Concerns 16 | include ActionDispatch::Routing::Mapper::Resources 17 | include ActionDispatch::Routing::Mapper::CustomUrls 18 | end 19 | 20 | class RouteSet 21 | # @yieldself [ActionDispatch::Routing::Mapper] 22 | # @yieldreceiver [ActionDispatch::Routing::Mapper] 23 | def draw; end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/solargraph/rails/annotations/date.rb: -------------------------------------------------------------------------------- 1 | require 'date' 2 | require 'active_support/core_ext' 3 | 4 | class Date 5 | # @param form [Symbol] 6 | # @return [Time] 7 | def to_time(form = :local); end 8 | 9 | # @return [Rational, self] 10 | def -(other); end 11 | 12 | # @return [self] 13 | def +(other); end 14 | 15 | # @return [String] 16 | def readable_inspect; end 17 | 18 | # @return [-1, 0, 1, nil] 19 | def compare_with_coercion(other); end 20 | 21 | # @return [-1, 0, 1, nil] 22 | def <=>(other); end 23 | 24 | # @return [::Time] 25 | def to_time; end 26 | 27 | # @param format [Symbol] 28 | # @return [String] 29 | def to_formatted_s(format = :some_default); end 30 | end 31 | 32 | class DateTime 33 | # @return [String] 34 | def readable_inspect; end 35 | 36 | # @param format [Symbol] 37 | # @return [String] 38 | def to_formatted_s(format = :some_default); end 39 | end 40 | -------------------------------------------------------------------------------- /spec/rails7/rbs_collection.yaml: -------------------------------------------------------------------------------- 1 | # Download sources 2 | sources: 3 | - type: git 4 | name: ruby/gem_rbs_collection 5 | remote: https://github.com/ruby/gem_rbs_collection.git 6 | # locked for test stability - you're encouraged to bump this and 7 | # submit a PR with the definition changes! we just don't want 8 | # things failing after unrelated changes. 9 | # 10 | # https://github.com/ruby/gem_rbs_collection/commits/main/ 11 | revision: ed61b16bf0a42201814e6a3baa9663d9f3a69046 12 | # revision: main 13 | repo_dir: gems 14 | 15 | # You can specify local directories as sources also. 16 | # - type: local 17 | # path: path/to/your/local/repository 18 | 19 | # A directory to install the downloaded RBSs 20 | path: .gem_rbs_collection 21 | 22 | # gems: 23 | # # If you want to avoid installing rbs files for gems, you can specify them here. 24 | # - name: GEM_NAME 25 | # ignore: true 26 | -------------------------------------------------------------------------------- /spec/rails8/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # Temporary files generated by your text editor or operating system 4 | # belong in git's global ignore instead: 5 | # `$XDG_CONFIG_HOME/git/ignore` or `~/.config/git/ignore` 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all environment files. 11 | /.env* 12 | 13 | # Ignore all logfiles and tempfiles. 14 | /log/* 15 | /tmp/* 16 | !/log/.keep 17 | !/tmp/.keep 18 | 19 | # Ignore pidfiles, but keep the directory. 20 | /tmp/pids/* 21 | !/tmp/pids/ 22 | !/tmp/pids/.keep 23 | 24 | # Ignore storage (uploaded files in development and any SQLite databases). 25 | /storage/* 26 | !/storage/.keep 27 | /tmp/storage/* 28 | !/tmp/storage/ 29 | !/tmp/storage/.keep 30 | 31 | /public/assets 32 | 33 | # Ignore master key for decrypting credentials and more. 34 | /config/master.key 35 | 36 | /.gem_rbs_collection 37 | /rbs_collection.lock.yaml 38 | -------------------------------------------------------------------------------- /spec/rails8/app/views/pwa/service-worker.js: -------------------------------------------------------------------------------- 1 | // Add a service worker for processing Web Push notifications: 2 | // 3 | // self.addEventListener("push", async (event) => { 4 | // const { title, options } = await event.data.json() 5 | // event.waitUntil(self.registration.showNotification(title, options)) 6 | // }) 7 | // 8 | // self.addEventListener("notificationclick", function(event) { 9 | // event.notification.close() 10 | // event.waitUntil( 11 | // clients.matchAll({ type: "window" }).then((clientList) => { 12 | // for (let i = 0; i < clientList.length; i++) { 13 | // let client = clientList[i] 14 | // let clientPath = (new URL(client.url)).pathname 15 | // 16 | // if (clientPath == event.notification.data.path && "focus" in client) { 17 | // return client.focus() 18 | // } 19 | // } 20 | // 21 | // if (clients.openWindow) { 22 | // return clients.openWindow(event.notification.data.path) 23 | // } 24 | // }) 25 | // ) 26 | // }) 27 | -------------------------------------------------------------------------------- /spec/rails8/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= content_for(:title) || "Rails8" %> 5 | 6 | 7 | 8 | <%= csrf_meta_tags %> 9 | <%= csp_meta_tag %> 10 | 11 | <%= yield :head %> 12 | 13 | <%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %> 14 | <%#= tag.link rel: "manifest", href: pwa_manifest_path(format: :json) %> 15 | 16 | 17 | 18 | 19 | 20 | <%# Includes all stylesheet files in app/assets/stylesheets %> 21 | <%= stylesheet_link_tag :app, "data-turbo-track": "reload" %> 22 | 23 | 24 | 25 | <%= yield %> 26 | 27 | 28 | -------------------------------------------------------------------------------- /spec/rails8/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization and 2 | # are automatically loaded by Rails. If you want to use locales other than 3 | # English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t "hello" 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t("hello") %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more about the API, please read the Rails Internationalization guide 20 | # at https://guides.rubyonrails.org/i18n.html. 21 | # 22 | # Be aware that YAML interprets the following case-insensitive strings as 23 | # booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings 24 | # must be quoted to be interpreted as strings. For example: 25 | # 26 | # en: 27 | # "yes": yup 28 | # enabled: "ON" 29 | 30 | en: 31 | hello: "Hello world" 32 | -------------------------------------------------------------------------------- /spec/solargraph-rails/autoload_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Solargraph::Rails::Autoload do 4 | let(:api_map) { Solargraph::ApiMap.new } 5 | 6 | it "auto completes explicit nested classes" do 7 | load_string 'test1.rb', %( 8 | class Foo 9 | class Bar 10 | class Baz 11 | def run; end 12 | end 13 | end 14 | end 15 | Foo::Bar::Baz 16 | ) 17 | 18 | expect(completion_at('test1.rb', [8, 6])).to include("Foo") 19 | expect(completion_at('test1.rb', [8, 11])).to include("Bar") 20 | expect(completion_at('test1.rb', [8, 16])).to include("Baz") 21 | end 22 | 23 | it "auto completes implicit nested classes" do 24 | load_string 'test2.rb', %( 25 | class Fop::Bap::Ban 26 | def rux; end 27 | end 28 | Fop::Bap::Ban 29 | ) 30 | 31 | expect(completion_at('test2.rb', [4, 6])).to include("Fop") 32 | expect(completion_at('test2.rb', [4, 11])).to include("Bap") 33 | expect(completion_at('test2.rb', [4, 16])).to include("Ban") 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/rails8/.kamal/secrets: -------------------------------------------------------------------------------- 1 | # Secrets defined here are available for reference under registry/password, env/secret, builder/secrets, 2 | # and accessories/*/env/secret in config/deploy.yml. All secrets should be pulled from either 3 | # password manager, ENV, or a file. DO NOT ENTER RAW CREDENTIALS HERE! This file needs to be safe for git. 4 | 5 | # Example of extracting secrets from 1password (or another compatible pw manager) 6 | # SECRETS=$(kamal secrets fetch --adapter 1password --account your-account --from Vault/Item KAMAL_REGISTRY_PASSWORD RAILS_MASTER_KEY) 7 | # KAMAL_REGISTRY_PASSWORD=$(kamal secrets extract KAMAL_REGISTRY_PASSWORD ${SECRETS}) 8 | # RAILS_MASTER_KEY=$(kamal secrets extract RAILS_MASTER_KEY ${SECRETS}) 9 | 10 | # Use a GITHUB_TOKEN if private repositories are needed for the image 11 | # GITHUB_TOKEN=$(gh config get -h github.com oauth_token) 12 | 13 | # Grab the registry password from ENV 14 | KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD 15 | 16 | # Improve security by using a password manager. Never check config/master.key into git! 17 | RAILS_MASTER_KEY=$(cat config/master.key) 18 | -------------------------------------------------------------------------------- /spec/rails8/.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files. 2 | 3 | # Ignore git directory. 4 | /.git/ 5 | /.gitignore 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all environment files. 11 | /.env* 12 | 13 | # Ignore all default key files. 14 | /config/master.key 15 | /config/credentials/*.key 16 | 17 | # Ignore all logfiles and tempfiles. 18 | /log/* 19 | /tmp/* 20 | !/log/.keep 21 | !/tmp/.keep 22 | 23 | # Ignore pidfiles, but keep the directory. 24 | /tmp/pids/* 25 | !/tmp/pids/.keep 26 | 27 | # Ignore storage (uploaded files in development and any SQLite databases). 28 | /storage/* 29 | !/storage/.keep 30 | /tmp/storage/* 31 | !/tmp/storage/.keep 32 | 33 | # Ignore assets. 34 | /node_modules/ 35 | /app/assets/builds/* 36 | !/app/assets/builds/.keep 37 | /public/assets 38 | 39 | # Ignore CI service files. 40 | /.github 41 | 42 | # Ignore Kamal files. 43 | /config/deploy*.yml 44 | /.kamal 45 | 46 | # Ignore development files 47 | /.devcontainer 48 | 49 | # Ignore Docker-related files 50 | /.dockerignore 51 | /Dockerfile* 52 | -------------------------------------------------------------------------------- /spec/rails8/config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative "boot" 2 | 3 | require "rails/all" 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module Rails8 10 | class Application < Rails::Application 11 | # Initialize configuration defaults for originally generated Rails version. 12 | config.load_defaults 8.0 13 | 14 | # Please, add to the `ignore` list any other `lib` subdirectories that do 15 | # not contain `.rb` files, or that should not be reloaded or eager loaded. 16 | # Common ones are `templates`, `generators`, or `middleware`, for example. 17 | config.autoload_lib(ignore: %w[assets tasks]) 18 | 19 | # Configuration for the application, engines, and railties goes here. 20 | # 21 | # These settings can be overridden in specific environments using the files 22 | # in config/environments, which are processed later. 23 | # 24 | # config.time_zone = "Central Time (US & Canada)" 25 | # config.eager_load_paths << Rails.root.join("extras") 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Fritz Meissner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /spec/rails8/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "fileutils" 3 | 4 | APP_ROOT = File.expand_path("..", __dir__) 5 | 6 | def system!(*args) 7 | system(*args, exception: true) 8 | end 9 | 10 | FileUtils.chdir APP_ROOT do 11 | # This script is a way to set up or update your development environment automatically. 12 | # This script is idempotent, so that you can run it at any time and get an expectable outcome. 13 | # Add necessary setup steps to this file. 14 | 15 | puts "== Installing dependencies ==" 16 | system("bundle check") || system!("bundle install") 17 | 18 | # puts "\n== Copying sample files ==" 19 | # unless File.exist?("config/database.yml") 20 | # FileUtils.cp "config/database.yml.sample", "config/database.yml" 21 | # end 22 | 23 | puts "\n== Preparing database ==" 24 | system! "bin/rails db:prepare" 25 | 26 | puts "\n== Removing old logs and tempfiles ==" 27 | system! "bin/rails log:clear tmp:clear" 28 | 29 | unless ARGV.include?("--skip-server") 30 | puts "\n== Starting development server ==" 31 | STDOUT.flush # flush the output before exec(2) so that it displays 32 | exec "bin/dev" 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/rails8/config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide content security policy. 4 | # See the Securing Rails Applications Guide for more information: 5 | # https://guides.rubyonrails.org/security.html#content-security-policy-header 6 | 7 | # Rails.application.configure do 8 | # config.content_security_policy do |policy| 9 | # policy.default_src :self, :https 10 | # policy.font_src :self, :https, :data 11 | # policy.img_src :self, :https, :data 12 | # policy.object_src :none 13 | # policy.script_src :self, :https 14 | # policy.style_src :self, :https 15 | # # Specify URI for violation reports 16 | # # policy.report_uri "/csp-violation-report-endpoint" 17 | # end 18 | # 19 | # # Generate session nonces for permitted importmap, inline scripts, and inline styles. 20 | # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } 21 | # config.content_security_policy_nonce_directives = %w(script-src style-src) 22 | # 23 | # # Report violations without enforcing the policy. 24 | # # config.content_security_policy_report_only = true 25 | # end 26 | -------------------------------------------------------------------------------- /spec/solargraph-rails/delegate_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | skip_reason = 'Missing required Solargraph pin type' unless Solargraph::Rails::Delegate.supported? 4 | 5 | RSpec.describe Solargraph::Rails::Delegate, skip: skip_reason do 6 | let(:api_map) { Solargraph::ApiMap.new } 7 | 8 | it 'generates delegate method pins' do 9 | load_string 'app/thing.rb', <<-RUBY 10 | class Thing 11 | delegate :one, :two, to: :foo 12 | # @return [Thing::Foo] 13 | def foo 14 | Foo.new 15 | end 16 | 17 | class Foo 18 | # @return [Integer] 19 | def one 20 | 1 21 | end 22 | 23 | # @return [String] 24 | def two 25 | "two" 26 | end 27 | end 28 | end 29 | RUBY 30 | 31 | assert_method(api_map, 'Thing::Foo#one', ['Integer']) 32 | assert_method(api_map, 'Thing::Foo#two', ['String']) 33 | assert_method(api_map, 'Thing#one', ['Integer']) 34 | assert_method(api_map, 'Thing#two', ['String']) do |pin| 35 | expect(pin.location.range.to_hash).to eq( 36 | start: { line: 14, character: 10 }, 37 | end: { line: 16, character: 13 } 38 | ) 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/rails8/.kamal/hooks/pre-connect.sample: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # A sample pre-connect check 4 | # 5 | # Warms DNS before connecting to hosts in parallel 6 | # 7 | # These environment variables are available: 8 | # KAMAL_RECORDED_AT 9 | # KAMAL_PERFORMER 10 | # KAMAL_VERSION 11 | # KAMAL_HOSTS 12 | # KAMAL_ROLES (if set) 13 | # KAMAL_DESTINATION (if set) 14 | # KAMAL_RUNTIME 15 | 16 | hosts = ENV["KAMAL_HOSTS"].split(",") 17 | results = nil 18 | max = 3 19 | 20 | elapsed = Benchmark.realtime do 21 | results = hosts.map do |host| 22 | Thread.new do 23 | tries = 1 24 | 25 | begin 26 | Socket.getaddrinfo(host, 0, Socket::AF_UNSPEC, Socket::SOCK_STREAM, nil, Socket::AI_CANONNAME) 27 | rescue SocketError 28 | if tries < max 29 | puts "Retrying DNS warmup: #{host}" 30 | tries += 1 31 | sleep rand 32 | retry 33 | else 34 | puts "DNS warmup failed: #{host}" 35 | host 36 | end 37 | end 38 | 39 | tries 40 | end 41 | end.map(&:value) 42 | end 43 | 44 | retries = results.sum - hosts.size 45 | nopes = results.count { |r| r == max } 46 | 47 | puts "Prewarmed %d DNS lookups in %.2f sec: %d retries, %d failures" % [ hosts.size, elapsed, retries, nopes ] 48 | -------------------------------------------------------------------------------- /spec/rails8/config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | 9 | # Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 10 | # amazon: 11 | # service: S3 12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 14 | # region: us-east-1 15 | # bucket: your_own_bucket-<%= Rails.env %> 16 | 17 | # Remember not to checkin your GCS keyfile to a repository 18 | # google: 19 | # service: GCS 20 | # project: your_project 21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 22 | # bucket: your_own_bucket-<%= Rails.env %> 23 | 24 | # Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 25 | # microsoft: 26 | # service: AzureStorage 27 | # storage_account_name: your_account_name 28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 29 | # container: your_container_name-<%= Rails.env %> 30 | 31 | # mirror: 32 | # service: Mirror 33 | # primary: local 34 | # mirrors: [ amazon, google, microsoft ] 35 | -------------------------------------------------------------------------------- /lib/solargraph/rails/devise.rb: -------------------------------------------------------------------------------- 1 | module Solargraph 2 | module Rails 3 | class Devise 4 | def self.instance 5 | @instance ||= self.new 6 | end 7 | 8 | def process(source_map, ns) 9 | if Model.valid_filename?(source_map.filename) 10 | process_model(source_map.source, ns) 11 | else 12 | [] 13 | end 14 | end 15 | 16 | private 17 | 18 | def process_model(source, ns) 19 | walker = Walker.from_source(source) 20 | pins = [] 21 | 22 | walker.on :send, [nil, :devise] do |ast| 23 | modules = 24 | ast.children[2..-1] 25 | .map { |c| c.children.first } 26 | .select { |s| s.is_a?(Symbol) } 27 | 28 | modules.each do |mod| 29 | pins << 30 | Util.build_module_include( 31 | ns, 32 | "Devise::Models::#{mod.to_s.capitalize}", 33 | Util.build_location(ast, ns.filename) 34 | ) 35 | end 36 | end 37 | 38 | walker.walk 39 | if pins.any? 40 | Solargraph.logger.debug( 41 | "[Rails][Devise] added #{pins.map(&:name)} to #{ns.path}" 42 | ) 43 | end 44 | pins 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/rails8/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite. Versions 3.8.0 and up are supported. 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem "sqlite3" 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: storage/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: storage/test.sqlite3 22 | 23 | 24 | # Store production database in the storage/ directory, which by default 25 | # is mounted as a persistent Docker volume in config/deploy.yml. 26 | production: 27 | primary: 28 | <<: *default 29 | database: storage/production.sqlite3 30 | cache: 31 | <<: *default 32 | database: storage/production_cache.sqlite3 33 | migrations_paths: db/cache_migrate 34 | queue: 35 | <<: *default 36 | database: storage/production_queue.sqlite3 37 | migrations_paths: db/queue_migrate 38 | cable: 39 | <<: *default 40 | database: storage/production_cable.sqlite3 41 | migrations_paths: db/cable_migrate 42 | -------------------------------------------------------------------------------- /spec/rails8/.kamal/hooks/pre-build.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # A sample pre-build hook 4 | # 5 | # Checks: 6 | # 1. We have a clean checkout 7 | # 2. A remote is configured 8 | # 3. The branch has been pushed to the remote 9 | # 4. The version we are deploying matches the remote 10 | # 11 | # These environment variables are available: 12 | # KAMAL_RECORDED_AT 13 | # KAMAL_PERFORMER 14 | # KAMAL_VERSION 15 | # KAMAL_HOSTS 16 | # KAMAL_ROLES (if set) 17 | # KAMAL_DESTINATION (if set) 18 | 19 | if [ -n "$(git status --porcelain)" ]; then 20 | echo "Git checkout is not clean, aborting..." >&2 21 | git status --porcelain >&2 22 | exit 1 23 | fi 24 | 25 | first_remote=$(git remote) 26 | 27 | if [ -z "$first_remote" ]; then 28 | echo "No git remote set, aborting..." >&2 29 | exit 1 30 | fi 31 | 32 | current_branch=$(git branch --show-current) 33 | 34 | if [ -z "$current_branch" ]; then 35 | echo "Not on a git branch, aborting..." >&2 36 | exit 1 37 | fi 38 | 39 | remote_head=$(git ls-remote $first_remote --tags $current_branch | cut -f1) 40 | 41 | if [ -z "$remote_head" ]; then 42 | echo "Branch not pushed to remote, aborting..." >&2 43 | exit 1 44 | fi 45 | 46 | if [ "$KAMAL_VERSION" != "$remote_head" ]; then 47 | echo "Version ($KAMAL_VERSION) does not match remote HEAD ($remote_head), aborting..." >&2 48 | exit 1 49 | fi 50 | 51 | exit 0 52 | -------------------------------------------------------------------------------- /spec/definitions/core/Enumerable.yml: -------------------------------------------------------------------------------- 1 | --- 2 | Enumerable#compact_blank: 3 | types: 4 | - Array> 5 | skip: 6 | - 0.48.0 7 | - 0.49.0 8 | - 0.50.0 9 | - 0.51.2 10 | - 0.52.0 11 | Enumerable#exclude?: 12 | types: 13 | - Boolean 14 | skip: false 15 | Enumerable#excluding: 16 | types: 17 | - undefined 18 | skip: false 19 | Enumerable#in_order_of: 20 | types: 21 | - undefined 22 | skip: false 23 | Enumerable#including: 24 | types: 25 | - undefined 26 | skip: false 27 | Enumerable#index_by: 28 | types: 29 | - undefined 30 | skip: false 31 | Enumerable#index_with: 32 | types: 33 | - undefined 34 | skip: false 35 | Enumerable#many?: 36 | types: 37 | - Boolean 38 | skip: false 39 | Enumerable#maximum: 40 | types: 41 | - undefined 42 | skip: false 43 | Enumerable#minimum: 44 | types: 45 | - undefined 46 | skip: false 47 | Enumerable#pick: 48 | types: 49 | - undefined 50 | skip: false 51 | Enumerable#pluck: 52 | types: 53 | - undefined 54 | skip: false 55 | Enumerable#sole: 56 | types: 57 | - undefined 58 | skip: false 59 | Enumerable#sum: 60 | types: 61 | - Integer 62 | - generic 63 | - generic 64 | - generic 65 | skip: 66 | - 0.48.0 67 | - 0.49.0 68 | - 0.50.0 69 | - 0.51.2 70 | Enumerable#without: 71 | types: 72 | - undefined 73 | skip: false 74 | -------------------------------------------------------------------------------- /lib/solargraph/rails/storage.rb: -------------------------------------------------------------------------------- 1 | module Solargraph 2 | module Rails 3 | class Storage 4 | def self.instance 5 | @instance ||= self.new 6 | end 7 | 8 | def process(source_map, ns) 9 | return [] unless Model.valid_filename?(source_map.filename) 10 | 11 | walker = Walker.from_source(source_map.source) 12 | pins = [] 13 | 14 | walker.on :send, [nil, :has_one_attached] do |ast| 15 | name = ast.children[2].children.first 16 | 17 | pins << 18 | Util.build_public_method( 19 | ns, 20 | name.to_s, 21 | types: ['ActiveStorage::Attached::One'], 22 | location: Util.build_location(ast, ns.filename) 23 | ) 24 | end 25 | 26 | walker.on :send, [nil, :has_many_attached] do |ast| 27 | name = ast.children[2].children.first 28 | 29 | pins << 30 | Util.build_public_method( 31 | ns, 32 | name.to_s, 33 | types: ['ActiveStorage::Attached::Many'], 34 | location: Util.build_location(ast, ns.filename) 35 | ) 36 | end 37 | 38 | walker.walk 39 | if pins.any? 40 | Solargraph.logger.debug( 41 | "[Rails][Storage] added #{pins.map(&:name)} to #{ns.path}" 42 | ) 43 | end 44 | pins 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/solargraph/rails/rails_api.rb: -------------------------------------------------------------------------------- 1 | module Solargraph 2 | module Rails 3 | class RailsApi 4 | def self.instance 5 | @instance ||= self.new 6 | end 7 | 8 | def extra_source_maps 9 | @extra_source_maps ||= Dir[File.join(__dir__, 'annotations', '*.rb')].to_h do |path| 10 | code = File.read(path) 11 | source = Solargraph::Source.load_string(code, path) 12 | map = Solargraph::SourceMap.map(source) 13 | [File.basename(path, '.rb'), map] 14 | end 15 | end 16 | 17 | # @param yard_map [Solargraph::DocMap] 18 | def global(_doc_map) 19 | extra_source_maps.values.flat_map(&:pins) 20 | end 21 | 22 | def local(source_map, ns) 23 | return [] unless source_map.filename.include?('db/migrate') 24 | node, _ = Walker.normalize_ast(source_map.source) 25 | 26 | pins = [ 27 | Util.build_module_include( 28 | ns, 29 | 'ActiveRecord::ConnectionAdapters::SchemaStatements', 30 | Util.build_location(node, ns.filename) 31 | ), 32 | Util.build_module_extend( 33 | ns, 34 | 'ActiveRecord::ConnectionAdapters::SchemaStatements', 35 | Util.build_location(node, ns.filename) 36 | ) 37 | ] 38 | 39 | Solargraph.logger.debug( 40 | "[Rails][RailsApi] added #{pins.map(&:name)} to #{ns.path}" 41 | ) 42 | pins 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/solargraph/rails/annotate.rb: -------------------------------------------------------------------------------- 1 | module Solargraph 2 | module Rails 3 | class Annotate 4 | def self.instance 5 | @instance ||= self.new 6 | end 7 | 8 | def self.reset 9 | @instance = nil 10 | end 11 | 12 | def initialize 13 | @schema_present = File.exist?('db/schema.rb') 14 | end 15 | 16 | def process(source_map, ns) 17 | return [] if @schema_present 18 | return [] unless Model.valid_filename?(source_map.filename) 19 | 20 | pins = [] 21 | walker = Walker.from_source(source_map.source) 22 | walker.comments.each do |_, snip| 23 | name, type = snip.text.gsub(/[\(\),:\d]/, '').split[1..2] 24 | 25 | next unless name && type 26 | 27 | ruby_type = Schema::RUBY_TYPES[type.to_sym] 28 | next unless ruby_type 29 | 30 | pins << 31 | Util.build_public_method( 32 | ns, 33 | name, 34 | types: [ruby_type], 35 | location: 36 | Solargraph::Location.new(source_map.filename, snip.range) 37 | ) 38 | 39 | pins << 40 | Util.build_public_method( 41 | ns, 42 | "#{name}=", 43 | types: [ruby_type], 44 | params: { 'value' => [ruby_type] }, 45 | location: 46 | Solargraph::Location.new(source_map.filename, snip.range) 47 | ) 48 | end 49 | 50 | pins 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/solargraph/rails/autoload.rb: -------------------------------------------------------------------------------- 1 | module Solargraph 2 | module Rails 3 | class Autoload 4 | def self.instance 5 | @instance ||= self.new 6 | end 7 | 8 | def process(source_map, ns, ds) 9 | return [] unless ds.size == 1 && ns.path.include?('::') 10 | Solargraph.logger.debug( 11 | "[Rails][Autoload] seeding class tree for #{ns.path}" 12 | ) 13 | 14 | root_ns = source_map.pins.find { |p| p.path == '' } 15 | namespace_stubs(root_ns, ns) 16 | end 17 | 18 | def namespace_stubs(root_ns, ns) 19 | parts = ns.path.split('::') 20 | 21 | candidates = 22 | parts 23 | .each_with_index 24 | .reduce([]) { |acc, (_, i)| acc + [parts[0..i].join('::')] } 25 | .reject { |el| el == ns.path } 26 | 27 | previous_ns = root_ns 28 | pins = [] 29 | 30 | parts[0..-2].each_with_index do |name, i| 31 | gates = candidates[0..i].reverse + [''] 32 | path = gates.first 33 | next if path == ns.path 34 | 35 | previous_ns = 36 | Solargraph::Pin::Namespace.new( 37 | type: :class, 38 | location: ns.location, 39 | closure: previous_ns, 40 | name: name, 41 | comments: ns.comments, 42 | visibility: :public, 43 | gates: gates[1..-1] 44 | ) 45 | 46 | pins << previous_ns 47 | end 48 | 49 | pins 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/solargraph/rails/annotations/stdlib_requires.rb: -------------------------------------------------------------------------------- 1 | # See issue #54 2 | class Object 3 | require 'abbrev' 4 | require 'base64' 5 | require 'benchmark' 6 | require 'bigdecimal' 7 | require 'bundler' 8 | require 'cgi' 9 | require 'coverage' 10 | require 'csv' 11 | require 'date' 12 | require 'delegate' 13 | require 'did_you_mean' 14 | require 'digest' 15 | require 'drb' 16 | require 'English' 17 | require 'erb' 18 | require 'error_highlight' 19 | require 'expect' 20 | require 'fiddle' 21 | require 'fileutils' 22 | require 'find' 23 | require 'forwardable' 24 | require 'getoptlong' 25 | require 'ipaddr' 26 | require 'irb' 27 | require 'json' 28 | require 'kconv' 29 | require 'logger' 30 | require 'mkmf' 31 | require 'monitor' 32 | require 'mutex_m' 33 | require 'objspace' 34 | require 'observer' 35 | require 'open3' 36 | require 'openssl' 37 | require 'open-uri' 38 | require 'optionparser' 39 | require 'optparse' 40 | require 'ostruct' 41 | require 'pathname' 42 | require 'prettyprint' 43 | require 'pstore' 44 | require 'psych' 45 | require 'racc' 46 | require 'rdoc' 47 | require 'readline' 48 | require 'reline' 49 | require 'resolv' 50 | require 'resolv-replace' 51 | require 'ripper' 52 | require 'rubygems' 53 | require 'securerandom' 54 | require 'shellwords' 55 | require 'singleton' 56 | require 'socket' 57 | require 'tempfile' 58 | require 'timeout' 59 | require 'time' 60 | require 'tmpdir' 61 | require 'tsort' 62 | require 'un' 63 | require 'uri' 64 | require 'weakref' 65 | require 'yaml' 66 | end 67 | -------------------------------------------------------------------------------- /spec/solargraph-rails/annotate_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Solargraph::Rails::Annotate do 4 | before { Solargraph::Rails::Annotate.reset } 5 | 6 | let(:api_map) { Solargraph::ApiMap.new } 7 | 8 | it "reads `annotate' comments" do 9 | load_string 'app/models/my_model.rb', 10 | <<~RUBY 11 | # id :integer not null, primary key 12 | # start_date :date 13 | # living_expenses :decimal(, ) 14 | # less_deposits :boolean default(FALSE) 15 | # notes :text 16 | # name :string 17 | # created_at :datetime 18 | # price :float 19 | class MyModel < ApplicationRecord 20 | end 21 | RUBY 22 | 23 | assert_method(api_map, 'MyModel#id', ['Integer']) do |pin| 24 | expect(pin.location.range.to_hash).to eq( 25 | { start: { line: 0, character: 0 }, end: { line: 0, character: 68 } } 26 | ) 27 | end 28 | 29 | assert_method(api_map, 'MyModel#start_date', ['Date']) 30 | assert_method(api_map, 'MyModel#start_date=', ['Date'], 31 | args: { value: 'Date' }) 32 | assert_method( 33 | api_map, 34 | 'MyModel#living_expenses', 35 | ['BigDecimal'] 36 | ) 37 | assert_method(api_map, 'MyModel#less_deposits', ['Boolean']) 38 | assert_method(api_map, 'MyModel#notes', ['String']) 39 | assert_method(api_map, 'MyModel#name', ['String']) 40 | assert_method( 41 | api_map, 42 | 'MyModel#created_at', 43 | ['ActiveSupport::TimeWithZone'] 44 | ) 45 | assert_method(api_map, 'MyModel#price', ['BigDecimal']) 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /.overcommit.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Use this file to configure the Overcommit hooks you wish to use. This will 3 | # extend the default configuration defined in: 4 | # https://github.com/sds/overcommit/blob/master/config/default.yml 5 | # 6 | # At the topmost level of this YAML file is a key representing type of hook 7 | # being run (e.g. pre-commit, commit-msg, etc.). Within each type you can 8 | # customize each hook, such as whether to only run it on certain files (via 9 | # `include`), whether to only display output if it fails (via `quiet`), etc. 10 | # 11 | # For a complete list of hooks, see: 12 | # https://github.com/sds/overcommit/tree/master/lib/overcommit/hook 13 | # 14 | # For a complete list of options that you can use to customize hooks, see: 15 | # https://github.com/sds/overcommit#configuration 16 | # 17 | # Uncomment the following lines to make the configuration take effect. 18 | 19 | PreCommit: 20 | RuboCop: 21 | enabled: true 22 | on_warn: fail # Treat all warnings as failures 23 | 24 | Solargraph: 25 | enabled: true 26 | exclude: 27 | - 'spec/**/*' 28 | - lib/solargraph/rails/annotations/**/* 29 | - vendor/**/* 30 | - ".bundle/**/*" 31 | 32 | # creates false positives in CI 33 | AuthorName: 34 | enabled: false 35 | 36 | # creates false positives in CI 37 | AuthorEmail: 38 | enabled: false 39 | 40 | # 41 | # TrailingWhitespace: 42 | # enabled: true 43 | # exclude: 44 | # - '**/db/structure.sql' # Ignore trailing whitespace in generated files 45 | # 46 | #PostCheckout: 47 | # ALL: # Special hook name that customizes all hooks of this type 48 | # quiet: true # Change all post-checkout hooks to only display output on failure 49 | # 50 | # IndexTags: 51 | # enabled: true # Generate a tags file with `ctags` each time HEAD changes 52 | -------------------------------------------------------------------------------- /solargraph-rails.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path('../lib', __FILE__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | require 'solargraph/rails/version' 4 | 5 | 6 | solargraph_force_ci_version = (ENV['CI'] && ENV['MATRIX_SOLARGRAPH_VERSION']) 7 | solargraph_version = 8 | if solargraph_force_ci_version && !solargraph_force_ci_version.start_with?('branch-') 9 | [solargraph_force_ci_version] 10 | else 11 | [ 12 | # below this isn't tested in CI 13 | '>= 0.48.0', 14 | # above this hasn't been tested 15 | '<= 0.57' 16 | ] 17 | end 18 | 19 | Gem::Specification.new do |spec| 20 | spec.name = 'solargraph-rails' 21 | spec.version = Solargraph::Rails::VERSION 22 | spec.authors = ['Fritz Meissner'] 23 | spec.email = ['fritz.meissner@gmail.com'] 24 | 25 | spec.summary = 26 | 'Solargraph plugin that adds Rails-specific code through a Convention' 27 | spec.description = 28 | 'Add reflection on ActiveModel dynamic attributes that will be created at runtime' 29 | spec.homepage = 'https://github.com/iftheshoefritz/solargraph-rails' 30 | spec.license = 'MIT' 31 | 32 | spec.files = 33 | `git ls-files -z`.split("\x0").reject do |f| 34 | f.match(%r{^(assets|test|spec|features)/}) 35 | end 36 | spec.bindir = 'exe' 37 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 38 | spec.require_paths = ['lib'] 39 | 40 | spec.add_development_dependency 'bundler', '~> 2.3' 41 | spec.add_development_dependency 'rake', '~> 12.3.3' 42 | spec.add_development_dependency 'rspec', '~> 3.0' 43 | spec.add_development_dependency 'rubocop', '~> 1.76' 44 | spec.add_development_dependency 'simplecov', '~> 0.22.0' 45 | spec.add_development_dependency 'simplecov-lcov', '~> 0.8.0' 46 | 47 | spec.add_runtime_dependency 'solargraph', *solargraph_version 48 | 49 | spec.add_runtime_dependency 'activesupport' 50 | end 51 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | ENV['RAILS_ENV'] = 'test' 2 | 3 | require 'bundler/setup' 4 | unless ENV['SIMPLECOV_DISABLED'] 5 | # set up lcov reporting for undercover 6 | require 'simplecov' 7 | require 'simplecov-lcov' 8 | SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true 9 | SimpleCov.formatters = SimpleCov::Formatter::MultiFormatter.new( 10 | [ 11 | SimpleCov::Formatter::HTMLFormatter, 12 | SimpleCov::Formatter::LcovFormatter 13 | ] 14 | ) 15 | SimpleCov.start do 16 | add_filter %r{^/spec/} 17 | add_filter '/Rakefile' 18 | track_files 'lib/**/*.rb' 19 | add_filter %r{lib/solargraph/rails/annotations} 20 | enable_coverage(:branch) 21 | end 22 | end 23 | 24 | # https://stackoverflow.com/questions/79360526/uninitialized-constant-activesupportloggerthreadsafelevellogger-nameerror 25 | require 'logger' 26 | require 'solargraph' 27 | require 'solargraph-rails' 28 | require 'logger' 29 | require 'debug' 30 | require 'fileutils' 31 | require_relative './helpers' 32 | 33 | RSpec.configure do |config| 34 | coverages = {} 35 | 36 | config.expect_with :rspec do |expectations| 37 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 38 | end 39 | 40 | config.mock_with :rspec do |mocks| 41 | mocks.verify_partial_doubles = true 42 | end 43 | 44 | config.include(Helpers) 45 | config.shared_context_metadata_behavior = :apply_to_host_groups 46 | config.example_status_persistence_file_path = 'tmp/examples.txt' 47 | config.disable_monkey_patching! 48 | 49 | config.before(:suite) do 50 | # NOTE: without this, gem logic does not see gems inside sample project" 51 | Bundler.reset_rubygems! 52 | end 53 | 54 | config.around(:each, :debug) do |example| 55 | Solargraph.logger.level = Logger::DEBUG 56 | example.run 57 | Solargraph.logger.level = Logger::INFO 58 | end 59 | 60 | config.default_formatter = 'doc' if config.files_to_run.one? 61 | 62 | # config.order = :random 63 | Kernel.srand config.seed 64 | end 65 | -------------------------------------------------------------------------------- /spec/rails8/config/puma.rb: -------------------------------------------------------------------------------- 1 | # This configuration file will be evaluated by Puma. The top-level methods that 2 | # are invoked here are part of Puma's configuration DSL. For more information 3 | # about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. 4 | # 5 | # Puma starts a configurable number of processes (workers) and each process 6 | # serves each request in a thread from an internal thread pool. 7 | # 8 | # You can control the number of workers using ENV["WEB_CONCURRENCY"]. You 9 | # should only set this value when you want to run 2 or more workers. The 10 | # default is already 1. 11 | # 12 | # The ideal number of threads per worker depends both on how much time the 13 | # application spends waiting for IO operations and on how much you wish to 14 | # prioritize throughput over latency. 15 | # 16 | # As a rule of thumb, increasing the number of threads will increase how much 17 | # traffic a given process can handle (throughput), but due to CRuby's 18 | # Global VM Lock (GVL) it has diminishing returns and will degrade the 19 | # response time (latency) of the application. 20 | # 21 | # The default is set to 3 threads as it's deemed a decent compromise between 22 | # throughput and latency for the average Rails application. 23 | # 24 | # Any libraries that use a connection pool or another resource pool should 25 | # be configured to provide at least as many connections as the number of 26 | # threads. This includes Active Record's `pool` parameter in `database.yml`. 27 | threads_count = ENV.fetch("RAILS_MAX_THREADS", 3) 28 | threads threads_count, threads_count 29 | 30 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 31 | port ENV.fetch("PORT", 3000) 32 | 33 | # Allow puma to be restarted by `bin/rails restart` command. 34 | plugin :tmp_restart 35 | 36 | # Run the Solid Queue supervisor inside of Puma for single-server deployments 37 | plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"] 38 | 39 | # Specify the PID file. Defaults to tmp/pids/server.pid in development. 40 | # In other environments, only set the PID file if requested. 41 | pidfile ENV["PIDFILE"] if ENV["PIDFILE"] 42 | -------------------------------------------------------------------------------- /lib/solargraph/rails/walker.rb: -------------------------------------------------------------------------------- 1 | module Solargraph 2 | module Rails 3 | class Walker 4 | class Hook 5 | attr_reader :node_type 6 | def initialize(node_type, args, &block) 7 | @node_type = node_type 8 | @args = args 9 | @proc = Proc.new(&block) 10 | end 11 | 12 | def visit(node) 13 | return unless matches?(node) 14 | 15 | if @proc.arity == 1 16 | @proc.call(node) 17 | elsif @proc.arity == 2 18 | walker = Walker.new(node) 19 | @proc.call(node, walker) 20 | walker.walk 21 | end 22 | end 23 | 24 | private 25 | 26 | def matches?(node) 27 | return unless node.type == node_type 28 | return unless node.children 29 | return true if @args.empty? 30 | 31 | a_child_matches = node.children.first.is_a?(::Parser::AST::Node) && node.children.any? do |child| 32 | child.is_a?(::Parser::AST::Node) && 33 | match_children(child.children, @args[1..-1]) 34 | end 35 | 36 | return true if a_child_matches 37 | 38 | match_children(node.children) 39 | end 40 | 41 | def match_children(children, args = @args) 42 | args.each_with_index.all? do |arg, i| 43 | if children[i].is_a?(::Parser::AST::Node) 44 | children[i].type == arg 45 | else 46 | children[i] == arg 47 | end 48 | end 49 | end 50 | end 51 | 52 | def self.normalize_ast(source) 53 | NodeParser.parse_with_comments(source.code, source.filename) 54 | end 55 | 56 | def self.from_source(source) 57 | self.new(*self.normalize_ast(source)) 58 | end 59 | 60 | attr_reader :ast, :comments 61 | def initialize(ast, comments = {}) 62 | @comments = comments 63 | @ast = ast 64 | @hooks = Hash.new([]) 65 | end 66 | 67 | def on(node_type, args = [], &block) 68 | @hooks[node_type] << Hook.new(node_type, args, &block) 69 | end 70 | 71 | def walk 72 | @ast.is_a?(Array) ? @ast.each { |node| traverse(node) } : traverse(@ast) 73 | end 74 | 75 | private 76 | 77 | def traverse(node) 78 | return unless node.is_a?(::Parser::AST::Node) 79 | 80 | @hooks[node.type].each { |hook| hook.visit(node) } 81 | 82 | node.children.each { |child| traverse(child) } 83 | end 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /spec/rails8/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | # The test environment is used exclusively to run your application's 2 | # test suite. You never need to work with it otherwise. Remember that 3 | # your test database is "scratch space" for the test suite and is wiped 4 | # and recreated between test runs. Don't rely on the data there! 5 | 6 | Rails.application.configure do 7 | # Settings specified here will take precedence over those in config/application.rb. 8 | 9 | # While tests run files are not watched, reloading is not necessary. 10 | config.enable_reloading = false 11 | 12 | # Eager loading loads your entire application. When running a single test locally, 13 | # this is usually not necessary, and can slow down your test suite. However, it's 14 | # recommended that you enable it in continuous integration systems to ensure eager 15 | # loading is working properly before deploying your code. 16 | config.eager_load = ENV["CI"].present? 17 | 18 | # Configure public file server for tests with cache-control for performance. 19 | config.public_file_server.headers = { "cache-control" => "public, max-age=3600" } 20 | 21 | # Show full error reports. 22 | config.consider_all_requests_local = true 23 | config.cache_store = :null_store 24 | 25 | # Render exception templates for rescuable exceptions and raise for other exceptions. 26 | config.action_dispatch.show_exceptions = :rescuable 27 | 28 | # Disable request forgery protection in test environment. 29 | config.action_controller.allow_forgery_protection = false 30 | 31 | # Store uploaded files on the local file system in a temporary directory. 32 | config.active_storage.service = :test 33 | 34 | # Tell Action Mailer not to deliver emails to the real world. 35 | # The :test delivery method accumulates sent emails in the 36 | # ActionMailer::Base.deliveries array. 37 | config.action_mailer.delivery_method = :test 38 | 39 | # Set host to be used by links generated in mailer templates. 40 | config.action_mailer.default_url_options = { host: "example.com" } 41 | 42 | # Print deprecation notices to the stderr. 43 | config.active_support.deprecation = :stderr 44 | 45 | # Raises error for missing translations. 46 | # config.i18n.raise_on_missing_translations = true 47 | 48 | # Annotate rendered view with file names. 49 | # config.action_view.annotate_rendered_view_with_filenames = true 50 | 51 | # Raise error when a before_action's only/except options reference missing actions. 52 | config.action_controller.raise_on_missing_callback_actions = true 53 | end 54 | -------------------------------------------------------------------------------- /spec/rails8/.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [ main ] 7 | 8 | jobs: 9 | scan_ruby: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v4 15 | 16 | - name: Set up Ruby 17 | uses: ruby/setup-ruby@v1 18 | with: 19 | ruby-version: .ruby-version 20 | bundler-cache: true 21 | 22 | - name: Scan for common Rails security vulnerabilities using static analysis 23 | run: bin/brakeman --no-pager 24 | 25 | scan_js: 26 | runs-on: ubuntu-latest 27 | 28 | steps: 29 | - name: Checkout code 30 | uses: actions/checkout@v4 31 | 32 | - name: Set up Ruby 33 | uses: ruby/setup-ruby@v1 34 | with: 35 | ruby-version: .ruby-version 36 | bundler-cache: true 37 | 38 | - name: Scan for security vulnerabilities in JavaScript dependencies 39 | run: bin/importmap audit 40 | 41 | lint: 42 | runs-on: ubuntu-latest 43 | steps: 44 | - name: Checkout code 45 | uses: actions/checkout@v4 46 | 47 | - name: Set up Ruby 48 | uses: ruby/setup-ruby@v1 49 | with: 50 | ruby-version: .ruby-version 51 | bundler-cache: true 52 | 53 | - name: Lint code for consistent style 54 | run: bin/rubocop -f github 55 | 56 | test: 57 | runs-on: ubuntu-latest 58 | 59 | # services: 60 | # redis: 61 | # image: redis 62 | # ports: 63 | # - 6379:6379 64 | # options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 65 | steps: 66 | - name: Install packages 67 | run: sudo apt-get update && sudo apt-get install --no-install-recommends -y build-essential git libyaml-dev pkg-config google-chrome-stable 68 | 69 | - name: Checkout code 70 | uses: actions/checkout@v4 71 | 72 | - name: Set up Ruby 73 | uses: ruby/setup-ruby@v1 74 | with: 75 | ruby-version: .ruby-version 76 | bundler-cache: true 77 | 78 | - name: Run tests 79 | env: 80 | RAILS_ENV: test 81 | # REDIS_URL: redis://localhost:6379/0 82 | run: bin/rails db:test:prepare test test:system 83 | 84 | - name: Keep screenshots from failed system tests 85 | uses: actions/upload-artifact@v4 86 | if: failure() 87 | with: 88 | name: screenshots 89 | path: ${{ github.workspace }}/tmp/screenshots 90 | if-no-files-found: ignore 91 | -------------------------------------------------------------------------------- /.github/workflows/typecheck.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # To debug locally: 3 | # npm install -g act 4 | # act pull_request 5 | # 6 | 7 | name: Typecheck 8 | 9 | on: 10 | workflow_dispatch: {} 11 | pull_request: 12 | branches: [main] 13 | push: 14 | branches: 15 | - 'main' 16 | tags: 17 | - 'v*' 18 | 19 | jobs: 20 | solargraph: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v2 24 | 25 | - uses: ruby/setup-ruby@v1 26 | with: 27 | ruby-version: 3.4 28 | bundler: latest 29 | bundler-cache: true 30 | cache-version: ${{ matrix.versions.rails-major }}.${{ matrix.versions.rails-minor }}-2025-06-06 31 | env: 32 | MATRIX_RAILS_MAJOR_VERSION: "8" 33 | MATRIX_RAILS_VERSION: "8.0" 34 | 35 | - name: Restore cache of gem annotations 36 | id: dot-cache-restore 37 | uses: actions/cache/restore@v4 38 | with: 39 | key: | 40 | ${{ runner.os }}-dot-cache-${{ hashFiles('Gemfile.lock') }} 41 | restore-keys: | 42 | ${{ runner.os }}-dot-cache- 43 | path: | 44 | /home/runner/.cache/solargraph 45 | 46 | - name: bundle list 47 | run: | 48 | # same as used by ruby/setup-ruby 49 | BUNDLE_PATH="${GITHUB_WORKSPACE:?}/vendor/bundle" 50 | export BUNDLE_PATH 51 | bundle list && cat Gemfile.lock && find vendor -name Gemfile 52 | env: 53 | MATRIX_RAILS_MAJOR_VERSION: "8" 54 | MATRIX_RAILS_VERSION: "8.0" 55 | 56 | #- name: Setup upterm session 57 | # uses: lhotari/action-upterm@v1 58 | 59 | - name: Typecheck 60 | run: | 61 | set -x 62 | # same as used by ruby/setup-ruby 63 | ruby --version 64 | bundle install 65 | # reject known bad versions 66 | echo 'gem "solargraph", ["!= 0.56.2"]' >> .Gemfile 67 | bundle update solargraph 68 | bundle exec solargraph version 69 | # SOLARGRAPH_ASSERTS=on 70 | bundle exec solargraph typecheck --level typed 71 | env: 72 | MATRIX_RAILS_MAJOR_VERSION: "8" 73 | MATRIX_RAILS_VERSION: "8.0" 74 | 75 | - name: Cache gem annotations 76 | id: dot-cache-save 77 | if: always() && steps.dot-cache-restore.outputs.cache-hit != 'true' 78 | uses: actions/cache/save@v4 79 | with: 80 | key: | 81 | ${{ runner.os }}-dot-cache-${{ hashFiles('Gemfile.lock') }} 82 | path: | 83 | /home/runner/.cache/solargraph 84 | -------------------------------------------------------------------------------- /spec/rails8/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | # check=error=true 3 | 4 | # This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand: 5 | # docker build -t rails8 . 6 | # docker run -d -p 80:80 -e RAILS_MASTER_KEY= --name rails8 rails8 7 | 8 | # For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html 9 | 10 | # Make sure RUBY_VERSION matches the Ruby version in .ruby-version 11 | ARG RUBY_VERSION=3.2.8 12 | FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base 13 | 14 | # Rails app lives here 15 | WORKDIR /rails 16 | 17 | # Install base packages 18 | RUN apt-get update -qq && \ 19 | apt-get install --no-install-recommends -y curl libjemalloc2 libvips sqlite3 && \ 20 | rm -rf /var/lib/apt/lists /var/cache/apt/archives 21 | 22 | # Set production environment 23 | ENV RAILS_ENV="production" \ 24 | BUNDLE_DEPLOYMENT="1" \ 25 | BUNDLE_PATH="/usr/local/bundle" \ 26 | BUNDLE_WITHOUT="development" 27 | 28 | # Throw-away build stage to reduce size of final image 29 | FROM base AS build 30 | 31 | # Install packages needed to build gems 32 | RUN apt-get update -qq && \ 33 | apt-get install --no-install-recommends -y build-essential git libyaml-dev pkg-config && \ 34 | rm -rf /var/lib/apt/lists /var/cache/apt/archives 35 | 36 | # Install application gems 37 | COPY Gemfile Gemfile.lock ./ 38 | RUN bundle install && \ 39 | rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \ 40 | bundle exec bootsnap precompile --gemfile 41 | 42 | # Copy application code 43 | COPY . . 44 | 45 | # Precompile bootsnap code for faster boot times 46 | RUN bundle exec bootsnap precompile app/ lib/ 47 | 48 | # Precompiling assets for production without requiring secret RAILS_MASTER_KEY 49 | RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile 50 | 51 | 52 | 53 | 54 | # Final stage for app image 55 | FROM base 56 | 57 | # Copy built artifacts: gems, application 58 | COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}" 59 | COPY --from=build /rails /rails 60 | 61 | # Run and own only the runtime files as a non-root user for security 62 | RUN groupadd --system --gid 1000 rails && \ 63 | useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \ 64 | chown -R rails:rails db log storage tmp 65 | USER 1000:1000 66 | 67 | # Entrypoint prepares the database. 68 | ENTRYPOINT ["/rails/bin/docker-entrypoint"] 69 | 70 | # Start server via Thruster by default, this can be overwritten at runtime 71 | EXPOSE 80 72 | CMD ["./bin/thrust", "./bin/rails", "server"] 73 | -------------------------------------------------------------------------------- /lib/solargraph/rails/delegate.rb: -------------------------------------------------------------------------------- 1 | require 'parser' 2 | 3 | module Solargraph 4 | module Rails 5 | class Delegate 6 | def self.instance 7 | @instance ||= self.new 8 | end 9 | 10 | def self.supported? 11 | Solargraph::Pin.const_defined?(:DelegatedMethod) 12 | end 13 | 14 | def process(source_map, ns) 15 | return [] unless self.class.supported? 16 | return [] unless source_map.code.include?('delegate') 17 | 18 | walker = Walker.from_source(source_map.source) 19 | pins = [] 20 | 21 | walker.on :send, [nil, :delegate] do |ast| 22 | last_child = ast.children[-1] 23 | next unless last_child.instance_of?(::Parser::AST::Node) && last_child.type == :hash 24 | 25 | methods = ast.children[2...-1].select { |c| c.type == :sym } 26 | 27 | delegate_node = Util.extract_option(ast, :to) 28 | next unless delegate_node 29 | 30 | chain = if delegate_node.type == :sym 31 | # `delegate ..., to: :bar` means call the #bar method to get the delegate object 32 | call = Solargraph::Source::Chain::Call.new(delegate_node.children[0].to_s) 33 | Solargraph::Source::Chain.new([call], delegate_node) 34 | else 35 | # for any other type of delegate, we create a chain from the AST node 36 | Solargraph::Parser::Legacy::NodeChainer.chain(delegate_node, ns.filename) 37 | end 38 | 39 | prefix_node = Util.extract_option(ast, :prefix) 40 | 41 | prefix = nil 42 | if prefix_node 43 | if prefix_node.type == :sym 44 | prefix = prefix_node.children[0] 45 | elsif prefix_node.type == :true && delegate_node.type == :sym 46 | prefix = delegate_node.children[0] 47 | end 48 | end 49 | 50 | location = Util.build_location(delegate_node, ns.filename) 51 | methods.each do |meth| 52 | method_name = meth.children[0] 53 | pins << Solargraph::Pin::DelegatedMethod.new( 54 | closure: ns, 55 | scope: :instance, 56 | name: [prefix, method_name].select(&:itself).join("_"), 57 | node: meth, 58 | receiver: chain, 59 | receiver_method_name: method_name.to_s, 60 | ) 61 | end 62 | end 63 | 64 | walker.walk 65 | 66 | if pins.any? 67 | Solargraph.logger.debug( 68 | "[Rails][Delegate] added #{pins.map(&:name)} to #{ns.path}" 69 | ) 70 | end 71 | 72 | pins 73 | end 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /spec/rails8/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main" 4 | gem 'rails', "~> #{ENV.fetch('MATRIX_RAILS_VERSION', '8.0')}.0" 5 | # The modern asset pipeline for Rails [https://github.com/rails/propshaft] 6 | gem "propshaft" 7 | # Use sqlite3 as the database for Active Record 8 | gem "sqlite3", ">= 2.1" 9 | # Use the Puma web server [https://github.com/puma/puma] 10 | gem "puma", ">= 5.0" 11 | # Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails] 12 | gem "importmap-rails" 13 | # Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev] 14 | gem "turbo-rails" 15 | # Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev] 16 | gem "stimulus-rails" 17 | # Build JSON APIs with ease [https://github.com/rails/jbuilder] 18 | gem "jbuilder" 19 | 20 | # Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword] 21 | # gem "bcrypt", "~> 3.1.7" 22 | 23 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 24 | gem "tzinfo-data", platforms: %i[ windows jruby ] 25 | 26 | # Use the database-backed adapters for Rails.cache, Active Job, and Action Cable 27 | gem "solid_cache" 28 | gem "solid_queue" 29 | gem "solid_cable" 30 | 31 | # Reduces boot times through caching; required in config/boot.rb 32 | gem "bootsnap", require: false 33 | 34 | # Deploy this application anywhere as a Docker container [https://kamal-deploy.org] 35 | gem "kamal", require: false 36 | 37 | # Add HTTP asset caching/compression and X-Sendfile acceleration to Puma [https://github.com/basecamp/thruster/] 38 | gem "thruster", require: false 39 | 40 | # Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images] 41 | # gem "image_processing", "~> 1.2" 42 | 43 | gem "devise" 44 | gem "pry" 45 | 46 | group :development, :test do 47 | # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem 48 | gem "debug", platforms: %i[ mri windows ], require: "debug/prelude" 49 | 50 | # Static analysis for security vulnerabilities [https://brakemanscanner.org/] 51 | gem "brakeman", require: false 52 | 53 | # Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/] 54 | gem "rubocop-rails-omakase", require: false 55 | end 56 | 57 | group :development do 58 | # Use console on exceptions pages [https://github.com/rails/web-console] 59 | gem "web-console" 60 | end 61 | 62 | group :test do 63 | # Use system testing [https://guides.rubyonrails.org/testing.html#system-testing] 64 | gem "capybara" 65 | gem "selenium-webdriver" 66 | end 67 | 68 | # used in generate_definitions.rb 69 | gem 'method_source', require: false 70 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Kind of ugly, but works. The setup-ruby action forces gems to be installed to vendor/bundle 4 | # If we use it naively we end up with vendor/bundle and spec/rails*/vendor/bundle, which 5 | # breaks all the tests because docs are generated in two different directories. 6 | # 7 | # So if we just install the rails deps at the same time, we have a single cache and a single 8 | # directory for gems. 9 | rails_version = (ENV['CI'] && ENV['MATRIX_RAILS_VERSION']) || '7.2' 10 | rails_major_version = rails_version.split('.').first 11 | instance_eval File.read(File.expand_path("spec/rails#{rails_major_version}/Gemfile", __dir__)) 12 | 13 | solargraph_version = (ENV['CI'] && ENV['MATRIX_SOLARGRAPH_VERSION']) 14 | 15 | # ensure that YARD docs get cached by ruby/setup-ruby in GitHub 16 | # Actions if using an older version of solargraph that needs user to 17 | # run `yard gems` manually 18 | if solargraph_version && !solargraph_version.start_with?('branch-') 19 | solargraph_minor_version = solargraph_version.split('.')[1].to_i 20 | solargraph_major_version = solargraph_version.split('.')[0].to_i 21 | if solargraph_version && solargraph_major_version == 0 && solargraph_minor_version < 53 22 | plugin 'auto_yard', path: File.expand_path('ci/auto_yard', __dir__) 23 | STDERR.puts("Using auto_yard plugin at #{File.expand_path('ci/auto_yard', __dir__)}") 24 | end 25 | end 26 | 27 | group :development, :test do 28 | gem 'bundler-audit' 29 | gem 'debug' 30 | gem 'byebug' 31 | end 32 | 33 | group :development, :rubocop do 34 | gem 'rubocop', require: false 35 | gem 'rubocop-rake', require: false 36 | gem 'rubocop-rspec', require: false 37 | gem 'rubocop-performance', require: false 38 | gem 'rubocop-yard', require: true 39 | gem 'overcommit' 40 | gem 'simplecov-lcov', 41 | github: 'apiology/simplecov-lcov', 42 | branch: 'avoid_blank_lines' 43 | end 44 | 45 | if rails_major_version == '7' 46 | # https://stackoverflow.com/questions/79360526/uninitialized-constant-activesupportloggerthreadsafelevellogger-nameerror 47 | gem "concurrent-ruby", '<=1.3.5' 48 | end 49 | 50 | # Specify your gem's dependencies in solargraph_rails.gemspec 51 | gemspec 52 | 53 | solargraph_force_ci_version = (ENV['CI'] && ENV['MATRIX_SOLARGRAPH_VERSION']) 54 | 55 | case solargraph_force_ci_version 56 | when '0.57.alpha' 57 | gem "solargraph", 58 | github: 'apiology/solargraph', 59 | branch: '2025-07-02' 60 | when /branch-castwide-(.*)/ 61 | gem "solargraph", 62 | github: 'castwide/solargraph', 63 | branch: Regexp.last_match(1) 64 | when /branch-(.*)/ 65 | gem "solargraph", 66 | github: 'apiology/solargraph', 67 | branch: Regexp.last_match(1) 68 | end 69 | 70 | # Local gemfile for development tools, etc. 71 | local_gemfile = File.expand_path(".Gemfile", __dir__) 72 | instance_eval File.read local_gemfile if File.exist? local_gemfile 73 | -------------------------------------------------------------------------------- /lib/solargraph/rails/annotations/action_controller.rb: -------------------------------------------------------------------------------- 1 | module ActionController 2 | class Base < Metal 3 | # 4 | # NOTE: keep this list synced with new items from MODULES in action_controller/base.rb 5 | # 6 | # @todo pull this as literal array dynamically from 7 | # ActionController::Base::MODULES and walk through it (and other 8 | # cases of same pattern) to help future-proof things 9 | # 10 | include AbstractController::Rendering 11 | extend AbstractController::Rendering::ClassMethods 12 | include AbstractController::Translation 13 | include AbstractController::AssetPaths 14 | include Helpers 15 | include UrlFor 16 | include Redirecting 17 | include ActionView::Layouts 18 | include Rendering 19 | include Renderers::All 20 | include ConditionalGet 21 | include EtagWithTemplateDigest 22 | include EtagWithFlash 23 | include Caching 24 | include MimeResponds 25 | include ImplicitRender 26 | include StrongParameters 27 | include ParameterEncoding 28 | include Cookies 29 | include Flash 30 | include FormBuilder 31 | include RequestForgeryProtection 32 | extend RequestForgeryProtection::ClassMethods 33 | include ContentSecurityPolicy 34 | include PermissionsPolicy 35 | extend PermissionsPolicy::ClassMethods 36 | include Streaming 37 | include DataStreaming 38 | include HttpAuthentication::Basic::ControllerMethods 39 | extend HttpAuthentication::Basic::ControllerMethods::ClassMethods 40 | include HttpAuthentication::Digest::ControllerMethods 41 | include HttpAuthentication::Token::ControllerMethods 42 | include DefaultHeaders 43 | include Logging 44 | extend Logging::ClassMethods 45 | include AbstractController::Callbacks 46 | extend AbstractController::Callbacks::ClassMethods 47 | include Rescue 48 | include Instrumentation 49 | include ParamsWrapper 50 | 51 | # 52 | # I don't see the thinsg below in action_controller/base.rb, at least in Rails 53 | # 7.0. Maybe they need to be moved to be under a different class? 54 | # 55 | extend ActiveSupport::Callbacks::ClassMethods 56 | extend ActiveSupport::Rescuable::ClassMethods 57 | include ActiveSupport::Rescuable 58 | 59 | # @return [ActionDispatch::Response] 60 | def response; end 61 | # @return [ActionDispatch::Request] 62 | def request; end 63 | # @return [ActionDispatch::Request::Session] 64 | def session; end 65 | # @return [ActionDispatch::Flash::FlashHash] 66 | def flash; end 67 | end 68 | end 69 | 70 | class ActionController::Metal 71 | # @return [ActionController::Parameters] 72 | def params; end 73 | end 74 | 75 | class ActionController::Cookies 76 | # @return [ActionDispatch::Cookies::CookieJar] 77 | def cookies; end 78 | end 79 | 80 | class ActionController::StrongParameters 81 | # @return [ActionController::Parameters] 82 | def params; end 83 | end 84 | -------------------------------------------------------------------------------- /spec/rails8/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # Make code changes take effect immediately without server restart. 7 | config.enable_reloading = true 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports. 13 | config.consider_all_requests_local = true 14 | 15 | # Enable server timing. 16 | config.server_timing = true 17 | 18 | # Enable/disable Action Controller caching. By default Action Controller caching is disabled. 19 | # Run rails dev:cache to toggle Action Controller caching. 20 | if Rails.root.join("tmp/caching-dev.txt").exist? 21 | config.action_controller.perform_caching = true 22 | config.action_controller.enable_fragment_cache_logging = true 23 | config.public_file_server.headers = { "cache-control" => "public, max-age=#{2.days.to_i}" } 24 | else 25 | config.action_controller.perform_caching = false 26 | end 27 | 28 | # Change to :null_store to avoid any caching. 29 | config.cache_store = :memory_store 30 | 31 | # Store uploaded files on the local file system (see config/storage.yml for options). 32 | config.active_storage.service = :local 33 | 34 | # Don't care if the mailer can't send. 35 | config.action_mailer.raise_delivery_errors = false 36 | 37 | # Make template changes take effect immediately. 38 | config.action_mailer.perform_caching = false 39 | 40 | # Set localhost to be used by links generated in mailer templates. 41 | config.action_mailer.default_url_options = { host: "localhost", port: 3000 } 42 | 43 | # Print deprecation notices to the Rails logger. 44 | config.active_support.deprecation = :log 45 | 46 | # Raise an error on page load if there are pending migrations. 47 | config.active_record.migration_error = :page_load 48 | 49 | # Highlight code that triggered database queries in logs. 50 | config.active_record.verbose_query_logs = true 51 | 52 | # Append comments with runtime information tags to SQL queries in logs. 53 | config.active_record.query_log_tags_enabled = true 54 | 55 | # Highlight code that enqueued background job in logs. 56 | config.active_job.verbose_enqueue_logs = true 57 | 58 | # Raises error for missing translations. 59 | # config.i18n.raise_on_missing_translations = true 60 | 61 | # Annotate rendered view with file names. 62 | config.action_view.annotate_rendered_view_with_filenames = true 63 | 64 | # Uncomment if you wish to allow Action Cable access from any origin. 65 | # config.action_cable.disable_request_forgery_protection = true 66 | 67 | # Raise error when a before_action's only/except options reference missing actions. 68 | config.action_controller.raise_on_missing_callback_actions = true 69 | 70 | # Apply autocorrection by RuboCop to files generated by `bin/rails generate`. 71 | # config.generators.apply_rubocop_autocorrect_after_generate! 72 | end 73 | -------------------------------------------------------------------------------- /spec/rails8/.kamal/hooks/pre-deploy.sample: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # A sample pre-deploy hook 4 | # 5 | # Checks the Github status of the build, waiting for a pending build to complete for up to 720 seconds. 6 | # 7 | # Fails unless the combined status is "success" 8 | # 9 | # These environment variables are available: 10 | # KAMAL_RECORDED_AT 11 | # KAMAL_PERFORMER 12 | # KAMAL_VERSION 13 | # KAMAL_HOSTS 14 | # KAMAL_COMMAND 15 | # KAMAL_SUBCOMMAND 16 | # KAMAL_ROLES (if set) 17 | # KAMAL_DESTINATION (if set) 18 | 19 | # Only check the build status for production deployments 20 | if ENV["KAMAL_COMMAND"] == "rollback" || ENV["KAMAL_DESTINATION"] != "production" 21 | exit 0 22 | end 23 | 24 | require "bundler/inline" 25 | 26 | # true = install gems so this is fast on repeat invocations 27 | gemfile(true, quiet: true) do 28 | source "https://rubygems.org" 29 | 30 | gem "octokit" 31 | gem "faraday-retry" 32 | end 33 | 34 | MAX_ATTEMPTS = 72 35 | ATTEMPTS_GAP = 10 36 | 37 | def exit_with_error(message) 38 | $stderr.puts message 39 | exit 1 40 | end 41 | 42 | class GithubStatusChecks 43 | attr_reader :remote_url, :git_sha, :github_client, :combined_status 44 | 45 | def initialize 46 | @remote_url = `git config --get remote.origin.url`.strip.delete_prefix("https://github.com/") 47 | @git_sha = `git rev-parse HEAD`.strip 48 | @github_client = Octokit::Client.new(access_token: ENV["GITHUB_TOKEN"]) 49 | refresh! 50 | end 51 | 52 | def refresh! 53 | @combined_status = github_client.combined_status(remote_url, git_sha) 54 | end 55 | 56 | def state 57 | combined_status[:state] 58 | end 59 | 60 | def first_status_url 61 | first_status = combined_status[:statuses].find { |status| status[:state] == state } 62 | first_status && first_status[:target_url] 63 | end 64 | 65 | def complete_count 66 | combined_status[:statuses].count { |status| status[:state] != "pending"} 67 | end 68 | 69 | def total_count 70 | combined_status[:statuses].count 71 | end 72 | 73 | def current_status 74 | if total_count > 0 75 | "Completed #{complete_count}/#{total_count} checks, see #{first_status_url} ..." 76 | else 77 | "Build not started..." 78 | end 79 | end 80 | end 81 | 82 | 83 | $stdout.sync = true 84 | 85 | begin 86 | puts "Checking build status..." 87 | 88 | attempts = 0 89 | checks = GithubStatusChecks.new 90 | 91 | loop do 92 | case checks.state 93 | when "success" 94 | puts "Checks passed, see #{checks.first_status_url}" 95 | exit 0 96 | when "failure" 97 | exit_with_error "Checks failed, see #{checks.first_status_url}" 98 | when "pending" 99 | attempts += 1 100 | end 101 | 102 | exit_with_error "Checks are still pending, gave up after #{MAX_ATTEMPTS * ATTEMPTS_GAP} seconds" if attempts == MAX_ATTEMPTS 103 | 104 | puts checks.current_status 105 | sleep(ATTEMPTS_GAP) 106 | checks.refresh! 107 | end 108 | rescue Octokit::NotFound 109 | exit_with_error "Build status could not be found" 110 | end 111 | -------------------------------------------------------------------------------- /lib/solargraph-rails.rb: -------------------------------------------------------------------------------- 1 | require 'solargraph' 2 | require 'logger' 3 | require 'active_support' 4 | require 'active_support/core_ext/string/inflections' 5 | 6 | require_relative 'solargraph/rails/util' 7 | require_relative 'solargraph/rails/schema' 8 | require_relative 'solargraph/rails/annotate' 9 | require_relative 'solargraph/rails/autoload' 10 | require_relative 'solargraph/rails/model' 11 | require_relative 'solargraph/rails/devise' 12 | require_relative 'solargraph/rails/walker' 13 | require_relative 'solargraph/rails/rails_api' 14 | require_relative 'solargraph/rails/delegate' 15 | require_relative 'solargraph/rails/storage' 16 | require_relative 'solargraph/rails/puma' 17 | require_relative 'solargraph/rails/importmap' 18 | require_relative 'solargraph/rails/debug' 19 | require_relative 'solargraph/rails/version' 20 | 21 | module Solargraph 22 | module Rails 23 | class NodeParser 24 | extend Solargraph::Parser::Legacy::ClassMethods 25 | end 26 | 27 | class Convention < Solargraph::Convention::Base 28 | # @param yard_map [Solargraph::DocMap] 29 | # 30 | # @return [Solargraph::Environ] 31 | def global(yard_map) 32 | Solargraph::Environ.new( 33 | pins: Solargraph::Rails::RailsApi.instance.global(yard_map) 34 | ) 35 | rescue => error 36 | Solargraph.logger.warn( 37 | error.message + "\n" + error.backtrace.join("\n") 38 | ) 39 | EMPTY_ENVIRON 40 | end 41 | 42 | # @param source_map [Solargraph::SourceMap] 43 | # 44 | # @return [Solargraph::Environ] 45 | def local(source_map) 46 | pins = [] 47 | ds = 48 | source_map.document_symbols.select do |n| 49 | n.is_a?(Solargraph::Pin::Namespace) 50 | end 51 | ns = ds.first 52 | 53 | basename = File.basename(source_map.filename) 54 | 55 | environ = Solargraph::Environ.new 56 | Puma.instance.add_dsl(environ, basename) 57 | Importmap.instance.add_dsl(environ, basename) 58 | 59 | return environ unless ns 60 | 61 | pins += run_feature { Schema.instance.process(source_map, ns) } 62 | pins += run_feature { Annotate.instance.process(source_map, ns) } 63 | pins += run_feature { Model.instance.process(source_map, ns) } 64 | pins += run_feature { Storage.instance.process(source_map, ns) } 65 | pins += run_feature { Autoload.instance.process(source_map, ns, ds) } 66 | pins += run_feature { Devise.instance.process(source_map, ns) } 67 | pins += run_feature { Delegate.instance.process(source_map, ns) } if Delegate.supported? 68 | pins += run_feature { RailsApi.instance.local(source_map, ns) } 69 | 70 | environ = Solargraph::Environ.new(pins: pins) 71 | Puma.instance.add_dsl(environ, basename) 72 | environ 73 | end 74 | 75 | private 76 | 77 | # @yieldreturn [Array] 78 | # 79 | # @return [Array] 80 | def run_feature(&block) 81 | yield 82 | rescue => error 83 | Solargraph.logger.warn( 84 | error.message + "\n" + error.backtrace.join("\n") 85 | ) 86 | [] 87 | end 88 | end 89 | end 90 | end 91 | 92 | Solargraph::Convention.register(Solargraph::Rails::Convention) 93 | -------------------------------------------------------------------------------- /spec/rails8/bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'bundle' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "rubygems" 12 | 13 | m = Module.new do 14 | module_function 15 | 16 | def invoked_as_script? 17 | File.expand_path($0) == File.expand_path(__FILE__) 18 | end 19 | 20 | def env_var_version 21 | ENV["BUNDLER_VERSION"] 22 | end 23 | 24 | def cli_arg_version 25 | return unless invoked_as_script? # don't want to hijack other binstubs 26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` 27 | bundler_version = nil 28 | update_index = nil 29 | ARGV.each_with_index do |a, i| 30 | if update_index && update_index.succ == i && a.match?(Gem::Version::ANCHORED_VERSION_PATTERN) 31 | bundler_version = a 32 | end 33 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ 34 | bundler_version = $1 35 | update_index = i 36 | end 37 | bundler_version 38 | end 39 | 40 | def gemfile 41 | gemfile = ENV["BUNDLE_GEMFILE"] 42 | return gemfile if gemfile && !gemfile.empty? 43 | 44 | File.expand_path("../Gemfile", __dir__) 45 | end 46 | 47 | def lockfile 48 | lockfile = 49 | case File.basename(gemfile) 50 | when "gems.rb" then gemfile.sub(/\.rb$/, ".locked") 51 | else "#{gemfile}.lock" 52 | end 53 | File.expand_path(lockfile) 54 | end 55 | 56 | def lockfile_version 57 | return unless File.file?(lockfile) 58 | lockfile_contents = File.read(lockfile) 59 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ 60 | Regexp.last_match(1) 61 | end 62 | 63 | def bundler_requirement 64 | @bundler_requirement ||= 65 | env_var_version || 66 | cli_arg_version || 67 | bundler_requirement_for(lockfile_version) 68 | end 69 | 70 | def bundler_requirement_for(version) 71 | return "#{Gem::Requirement.default}.a" unless version 72 | 73 | bundler_gem_version = Gem::Version.new(version) 74 | 75 | bundler_gem_version.approximate_recommendation 76 | end 77 | 78 | def load_bundler! 79 | ENV["BUNDLE_GEMFILE"] ||= gemfile 80 | 81 | activate_bundler 82 | end 83 | 84 | def activate_bundler 85 | gem_error = activation_error_handling do 86 | gem "bundler", bundler_requirement 87 | end 88 | return if gem_error.nil? 89 | require_error = activation_error_handling do 90 | require "bundler/version" 91 | end 92 | return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) 93 | warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" 94 | exit 42 95 | end 96 | 97 | def activation_error_handling 98 | yield 99 | nil 100 | rescue StandardError, LoadError => e 101 | e 102 | end 103 | end 104 | 105 | m.load_bundler! 106 | 107 | if m.invoked_as_script? 108 | load Gem.bin_path("bundler", "bundle") 109 | end 110 | -------------------------------------------------------------------------------- /lib/solargraph/rails/util.rb: -------------------------------------------------------------------------------- 1 | module Solargraph 2 | module Rails 3 | module Util 4 | def self.build_public_method( 5 | ns, 6 | name, 7 | comments: nil, 8 | parameters: [], 9 | types: nil, 10 | params: {}, 11 | location: nil, 12 | attribute: false, 13 | scope: :instance 14 | ) 15 | opts = { 16 | name: name, 17 | parameters: parameters, 18 | location: location, 19 | closure: ns, 20 | scope: scope, 21 | attribute: attribute 22 | } 23 | 24 | comments_arr = [comments].compact 25 | params.each do |name, types| 26 | comments_arr << "@param [#{types.join(',')}] #{name}" 27 | end 28 | comments_arr << "@return [#{types.join(',')}]" if types 29 | 30 | opts[:comments] ||= comments_arr.join("\n") 31 | 32 | m = Solargraph::Pin::Method.new(**opts) 33 | parameters = parameters + params.map do |name, type| 34 | Solargraph::Pin::Parameter.new( 35 | location: nil, 36 | closure: m, 37 | comments: '', 38 | name: name, 39 | presence: nil, 40 | decl: :arg, 41 | asgn_code: nil 42 | ) 43 | end 44 | m.parameters.concat(parameters) 45 | m 46 | end 47 | 48 | def self.build_module_include(ns, module_name, location) 49 | Solargraph::Pin::Reference::Include.new( 50 | closure: ns, 51 | name: module_name, 52 | location: location 53 | ) 54 | end 55 | 56 | def self.build_module_extend(ns, module_name, location) 57 | Solargraph::Pin::Reference::Extend.new( 58 | closure: ns, 59 | name: module_name, 60 | location: location 61 | ) 62 | end 63 | 64 | def self.dummy_location(path) 65 | Solargraph::Location.new( 66 | File.expand_path(path), 67 | Solargraph::Range.from_to(0, 0, 0, 0) 68 | ) 69 | end 70 | 71 | def self.build_location(ast, path) 72 | Solargraph::Location.new( 73 | File.expand_path(path), 74 | Solargraph::Range.from_to( 75 | ast.location.first_line, 76 | 0, 77 | ast.location.last_line, 78 | ast.location.column 79 | ) 80 | ) 81 | end 82 | 83 | def self.method_return(path, type) 84 | Solargraph::Pin::Reference::Override.method_return(path, type) 85 | end 86 | 87 | # Extract the value of a given option from a :send syntax node. 88 | # 89 | # E.g. given an AST node for `foo(:bar, baz: qux)`, you can use 90 | # `extract_option(node, :baz)` to get the AST node for `qux`. 91 | # 92 | # @param call_node [AST::Node] 93 | # @param option_name [Symbol] 94 | # @return [AST::Node, nil] 95 | def self.extract_option(call_node, option_name) 96 | options = call_node.children[3..-1].find { |n| n.type == :hash } 97 | return unless options 98 | 99 | pair = 100 | options.children.find do |n| 101 | n.children[0] && n.children[0].deconstruct == [:sym, option_name] 102 | end 103 | return unless pair 104 | 105 | pair.children[1] 106 | end 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | # Solargraph-Rails development guide 2 | 3 | ## Debugging workflow / test matrix issues locally 4 | 5 | ```sh 6 | npm install -g act 7 | act pull_request 8 | ``` 9 | 10 | ## Completion coverage tracking 11 | 12 | Solargraph-Rails uses a [set of yaml files](https://github.com/iftheshoefritz/solargraph-rails/tree/master/spec/definitions) to track coverage of found completions. 13 | Those yaml files are generated at runtime from a dummy Rails apps in the spec/rails* directories. 14 | 15 | The main goal is to catch any regressions in case of any change. In case a method completion is marked completed and it is not found in solargraph completions, the tests will fail. 16 | 17 | ### Checking coverage 18 | 19 | To see what is completion coverage for solargraph-rails, run the tests with the `PRINT_STATS=true` environment variable: 20 | 21 | ``` 22 | $ PRINT_STATS=true bundle exec rspec 23 | ``` 24 | 25 | What you will see in test output is reported coverage for classes that are tracked: 26 | 27 | ``` 28 | {:class_name=>"ActiveRecord::Base", :total=>800, :covered=>321, :typed=>10, :percent_covered=>40.1, :percent_typed=>1.3} 29 | provides completions for ActiveRecord::Base 30 | ``` 31 | 32 | ### Updating assertions 33 | 34 | In case an improvement is made, and more completions are found then being asserted, tests will throw a warning: 35 | 36 | ``` 37 | ActionDispatch::Routing::Mapper.try! is marked as skipped in spec/definitions/routes.yml, but is actually present. 38 | Consider setting skip=false 39 | provides completions for ActionDispatch::Routing::Mapper 40 | ``` 41 | 42 | In this case there are 2 options: 43 | 1. Manually updating yml file and setting `skip: false` for that method 44 | 2. Updating yml file in place by passing `update: true` to assertion: 45 | 46 | ```diff 47 | assert_matches_definition( 48 | map, 49 | 'ActionDispatch::Routing::Mapper', 50 | 'rails5/routes', 51 | + update: true 52 | ) 53 | end 54 | ``` 55 | 56 | In case of option 2, don't forget to remove the flag after yml file has been updated. Also review git diff, to make sure that no regressions have been set (skip=true was set for entries which previously had skip=false) 57 | 58 | ### Generating assertions 59 | 60 | In case a new set of assertion files has to be created (for a new Rails version for example), a script can be used - https://github.com/iftheshoefritz/solargraph-rails/blob/master/script/generate_definitions.rb. 61 | 62 | All you have to do is execute the script and pass it a path to rails app: 63 | 64 | ``` 65 | cd spec/rails8 66 | rails g model model 67 | rails db:drop db:create db:migrate 68 | bundle exec ruby ../../script/generate_definitions.rb . 69 | ``` 70 | 71 | Move .yml files into place, then make sure to review the script and uncomment relevant parts 72 | 73 | ## Preparing a release (maintainers) 74 | 75 | 1. Look up [most recent release](https://rubygems.org/gems/solargraph-rails) 76 | 2. Open up [commit list](https://github.com/iftheshoefritz/solargraph-rails/compare/v1.2.3...main) 77 | 3. Update [CHANGELOG.md](./CHANGELOG.md) 78 | 4. Flip to 'files changed view' and refine updates 79 | 5. Bump [version](./lib/solargraph/rails/version.rb) appropriately 80 | 6. Create branch, commit and merge changes - "Prepare for vX.Y.Z release", branch: `prepare_vX.Y.Z_release` 81 | 7. `git config branch.main.remote` 82 | 8. Ensure your local main branch is directly from iftheshoefritz 83 | 9. `direnv block` 84 | 10. `git checkout main && git pull && bundle install && bundle exec rake release` 85 | 11. `direnv allow` 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Solargraph::Rails - Help solargraph with Rails 2 | 3 | ## Models 4 | Consider pair of typical Rails models like this: 5 | 6 | ```sh 7 | rails g model Author lastname:string firstnames:string 8 | rails g model Book title:string isbn:string author:belongs_to 9 | ``` 10 | 11 | ```ruby 12 | class Author < ApplicationRecord 13 | has_many :books 14 | 15 | def sortable_name 16 | "#{lastname}, #{firstnames}" 17 | end 18 | end 19 | 20 | class Book < ApplicationRecord 21 | belongs_to :author 22 | 23 | def label 24 | [author.sortable_name, title, isbn].join("\n") 25 | end 26 | end 27 | ``` 28 | 29 | The various Ruby intellisense tools are ok at knowing that there are `Book` and `Author` constants, and some (including Solargraph) are aware that objects like `Book.new` have a `.label` method. But what about those "magical" dynamic methods that ActiveRecord creates like `.title`, or `.author`? 30 | 31 | Since these attributes are only created at runtime, a simple static analysis of the `Book` class alone can't identify them. Your editor has no idea that these attributes exist, but they're amongst the most common things that you will work with in any Rails app. 32 | 33 | That's where this plugin for Solargraph comes in: it understands db/schema.rb and any comments from the annotate\_models gem for models, and also supplies key annotations and Rails-specific context on top of what Solargraph pulls via YARD and RBS. As a result, you have access to database attributes: 34 | 35 | ![Go to attribute schema definition](assets/sg_rails_1_0_go_to_attribute_definition.gif) 36 | 37 | ... and ActiveRecord finders: 38 | 39 | ![ActiveRecord method support](assets/sg_rails_1_0_activerecord_support.gif) 40 | 41 | ... and associations: 42 | 43 | ![Association support](assets/sg_rails_1_0_association_completion.gif) 44 | 45 | ... and routes file syntax: 46 | 47 | ![Routes file support](assets/sg_rails_1_0_routes_support.gif) 48 | 49 | and more! 50 | 51 | ## Installation 52 | 53 | ### Install `solargraph` and `solargraph-rails` 54 | 55 | If you add them to your Gemfile, you'll have to tell your IDE plugin to use bundler to load the right version of solargraph. 56 | 57 | ### Import Rails RBS types 58 | 59 | Use [gem\_rbs\_collection](https://github.com/ruby/gem_rbs_collection) 60 | to install RBS types for Rails: 61 | 62 | ```sh 63 | rbs collection init 64 | rbs collection install 65 | ``` 66 | 67 | ### Add `solargraph-rails` to your `.solargraph.yml` 68 | 69 | (if you don't have a `.solargraph.yml` in your project root, you can run `solargraph config` to add one) 70 | 71 | ``` 72 | plugins: 73 | - solargraph-rails 74 | ``` 75 | 76 | ### Build YARD docs 77 | 78 | In the project root, run `yard gems`. 79 | 80 | ## Contributing 81 | Bug reports and pull requests are welcome on GitHub at https://github.com/iftheshoefritz/solargraph_rails. 82 | 83 | 1. create fork and clone the repo 84 | 85 | 2. install gem deps `bundle install` 86 | 87 | 3. install dummy rails app deps: 88 | 89 | ``` 90 | cd spec/rails7 && bundle install && rbs collection init && rbs collection install && cd ../../ 91 | cd spec/rails8 && bundle install && rbs collection init && rbs collection install && cd ../../ 92 | ``` 93 | 94 | 4. now tests should pass locally and you can try different changes 95 | 96 | 5. submit PR 97 | 98 | See [DEVELOPMENT.md](./DEVELOPMENT.md) for more information 99 | 100 | ## License 101 | 102 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 103 | -------------------------------------------------------------------------------- /.github/workflows/linter.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # To debug locally: 3 | # npm install -g act 4 | # act pull_release -j overcommit 5 | # 6 | 7 | name: Linter 8 | 9 | on: 10 | workflow_dispatch: {} 11 | pull_request: 12 | branches: [main] 13 | push: 14 | branches: 15 | - 'main' 16 | tags: 17 | - 'v*' 18 | 19 | permissions: 20 | pull-requests: write 21 | 22 | jobs: 23 | overcommit: 24 | runs-on: ubuntu-latest 25 | strategy: 26 | matrix: 27 | versions: 28 | # ruby 3.2 is minimum Ruby supported by Rails 8.0 29 | - ruby: "3.2" 30 | rails-major: "8" 31 | rails-minor: "0" 32 | run_coverage: true 33 | solargraph-version: 34 | - "branch-castwide-master" 35 | fail-fast: false 36 | steps: 37 | - uses: actions/checkout@v2 38 | # Number of commits to fetch. 0 indicates all history for all branches and tags. 39 | with: 40 | fetch-depth: 0 41 | 42 | - uses: ruby/setup-ruby@v1 43 | with: 44 | ruby-version: 3.4 45 | bundler: latest 46 | bundler-cache: true 47 | cache-version: ${{ matrix.solargraph-version }}-${{ matrix.versions.rails-major }}.${{ matrix.versions.rails-minor }}-2025-06-06 48 | env: 49 | MATRIX_SOLARGRAPH_VERSION: ${{ matrix.solargraph-version }} 50 | MATRIX_RAILS_VERSION: ${{ matrix.versions.rails-major }}.${{ matrix.versions.rails-minor }} 51 | MATRIX_RAILS_MAJOR_VERSION: ${{ matrix.versions.rails-major }} 52 | 53 | - name: Restore cache of gem annotations 54 | id: dot-cache-restore 55 | uses: actions/cache/restore@v4 56 | with: 57 | key: | 58 | 2025-06-26-09-${{ runner.os }}-dot-cache-${{ matrix.solargraph-version }}-${{ matrix.versions.rails-major }}--${{ matrix.versions.rails-minor }}-${{ hashFiles('spec/**/Gemfile.lock') }}-${{ hashFiles('Gemfile.lock') }} 59 | restore-keys: | 60 | 2025-06-26-09-${{ runner.os }}-dot-cache-${{ matrix.solargraph-version }}-${{ matrix.versions.rails-major }}-${{ matrix.versions.rails-minor }}-${{ hashFiles('spec/**/Gemfile.lock') }}- 61 | 2025-06-26-09-${{ runner.os }}-dot-cache-${{ matrix.solargraph-version }}-${{ matrix.versions.rails-major }}-${{ matrix.versions.rails-minor }}- 62 | 2025-06-26-09-${{ runner.os }}-dot-cache-${{ matrix.solargraph-version }}-${{ matrix.versions.rails-major }}- 63 | 2025-06-26-09-${{ runner.os }}-dot-cache-${{ matrix.solargraph-version }}- 64 | path: | 65 | /home/runner/.cache/solargraph 66 | 67 | - name: Overcommit 68 | run: | 69 | bundle exec overcommit --sign 70 | bundle exec overcommit --run --diff origin/main 71 | env: 72 | MATRIX_SOLARGRAPH_VERSION: ${{ matrix.solargraph-version }} 73 | MATRIX_RAILS_VERSION: ${{ matrix.versions.rails-major }}.${{ matrix.versions.rails-minor }} 74 | MATRIX_RAILS_MAJOR_VERSION: ${{ matrix.versions.rails-major }} 75 | rubocop: 76 | name: rubocop 77 | runs-on: ubuntu-latest 78 | env: 79 | BUNDLE_ONLY: rubocop 80 | steps: 81 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 82 | - uses: ruby/setup-ruby@1a615958ad9d422dd932dc1d5823942ee002799f # v1.227.0 83 | with: 84 | ruby-version: '3.3' 85 | bundler-cache: true 86 | - uses: reviewdog/action-rubocop@fcb74ba274da10b18d038d0bcddaae3518739634 # v2.21.2 87 | with: 88 | reporter: github-pr-check 89 | skip_install: true 90 | use_bundler: true 91 | rubocop_extensions: 'rubocop-performance:gemfile rubocop-rspec:gemfile rubocop-rake:gemfile rubocop-yard:gemfile' 92 | fail_level: info 93 | rubocop_version: Gemfile 94 | level: info 95 | -------------------------------------------------------------------------------- /spec/rails8/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # Code is not reloaded between requests. 7 | config.enable_reloading = false 8 | 9 | # Eager load code on boot for better performance and memory savings (ignored by Rake tasks). 10 | config.eager_load = true 11 | 12 | # Full error reports are disabled. 13 | config.consider_all_requests_local = false 14 | 15 | # Turn on fragment caching in view templates. 16 | config.action_controller.perform_caching = true 17 | 18 | # Cache assets for far-future expiry since they are all digest stamped. 19 | config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.year.to_i}" } 20 | 21 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 22 | # config.asset_host = "http://assets.example.com" 23 | 24 | # Store uploaded files on the local file system (see config/storage.yml for options). 25 | config.active_storage.service = :local 26 | 27 | # Assume all access to the app is happening through a SSL-terminating reverse proxy. 28 | config.assume_ssl = true 29 | 30 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 31 | config.force_ssl = true 32 | 33 | # Skip http-to-https redirect for the default health check endpoint. 34 | # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } 35 | 36 | # Log to STDOUT with the current request id as a default log tag. 37 | config.log_tags = [ :request_id ] 38 | config.logger = ActiveSupport::TaggedLogging.logger(STDOUT) 39 | 40 | # Change to "debug" to log everything (including potentially personally-identifiable information!) 41 | config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") 42 | 43 | # Prevent health checks from clogging up the logs. 44 | config.silence_healthcheck_path = "/up" 45 | 46 | # Don't log any deprecations. 47 | config.active_support.report_deprecations = false 48 | 49 | # Replace the default in-process memory cache store with a durable alternative. 50 | # config.cache_store = :mem_cache_store 51 | 52 | # Replace the default in-process and non-durable queuing backend for Active Job. 53 | # config.active_job.queue_adapter = :resque 54 | 55 | # Ignore bad email addresses and do not raise email delivery errors. 56 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 57 | # config.action_mailer.raise_delivery_errors = false 58 | 59 | # Set host to be used by links generated in mailer templates. 60 | config.action_mailer.default_url_options = { host: "example.com" } 61 | 62 | # Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit. 63 | # config.action_mailer.smtp_settings = { 64 | # user_name: Rails.application.credentials.dig(:smtp, :user_name), 65 | # password: Rails.application.credentials.dig(:smtp, :password), 66 | # address: "smtp.example.com", 67 | # port: 587, 68 | # authentication: :plain 69 | # } 70 | 71 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 72 | # the I18n.default_locale when a translation cannot be found). 73 | config.i18n.fallbacks = true 74 | 75 | # Do not dump schema after migrations. 76 | config.active_record.dump_schema_after_migration = false 77 | 78 | # Only use :id for inspections in production. 79 | config.active_record.attributes_for_inspect = [ :id ] 80 | 81 | # Enable DNS rebinding protection and other `Host` header attacks. 82 | # config.hosts = [ 83 | # "example.com", # Allow requests from example.com 84 | # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` 85 | # ] 86 | # 87 | # Skip DNS rebinding protection for the default health check endpoint. 88 | # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } 89 | end 90 | -------------------------------------------------------------------------------- /spec/rails8/config/deploy.yml: -------------------------------------------------------------------------------- 1 | # Name of your application. Used to uniquely configure containers. 2 | service: rails8 3 | 4 | # Name of the container image. 5 | image: your-user/rails8 6 | 7 | # Deploy to these servers. 8 | servers: 9 | web: 10 | - 192.168.0.1 11 | # job: 12 | # hosts: 13 | # - 192.168.0.1 14 | # cmd: bin/jobs 15 | 16 | # Enable SSL auto certification via Let's Encrypt and allow for multiple apps on a single web server. 17 | # Remove this section when using multiple web servers and ensure you terminate SSL at your load balancer. 18 | # 19 | # Note: If using Cloudflare, set encryption mode in SSL/TLS setting to "Full" to enable CF-to-app encryption. 20 | proxy: 21 | ssl: true 22 | host: app.example.com 23 | 24 | # Credentials for your image host. 25 | registry: 26 | # Specify the registry server, if you're not using Docker Hub 27 | # server: registry.digitalocean.com / ghcr.io / ... 28 | username: your-user 29 | 30 | # Always use an access token rather than real password when possible. 31 | password: 32 | - KAMAL_REGISTRY_PASSWORD 33 | 34 | # Inject ENV variables into containers (secrets come from .kamal/secrets). 35 | env: 36 | secret: 37 | - RAILS_MASTER_KEY 38 | clear: 39 | # Run the Solid Queue Supervisor inside the web server's Puma process to do jobs. 40 | # When you start using multiple servers, you should split out job processing to a dedicated machine. 41 | SOLID_QUEUE_IN_PUMA: true 42 | 43 | # Set number of processes dedicated to Solid Queue (default: 1) 44 | # JOB_CONCURRENCY: 3 45 | 46 | # Set number of cores available to the application on each server (default: 1). 47 | # WEB_CONCURRENCY: 2 48 | 49 | # Match this to any external database server to configure Active Record correctly 50 | # Use rails8-db for a db accessory server on same machine via local kamal docker network. 51 | # DB_HOST: 192.168.0.2 52 | 53 | # Log everything from Rails 54 | # RAILS_LOG_LEVEL: debug 55 | 56 | # Aliases are triggered with "bin/kamal ". You can overwrite arguments on invocation: 57 | # "bin/kamal logs -r job" will tail logs from the first server in the job section. 58 | aliases: 59 | console: app exec --interactive --reuse "bin/rails console" 60 | shell: app exec --interactive --reuse "bash" 61 | logs: app logs -f 62 | dbc: app exec --interactive --reuse "bin/rails dbconsole" 63 | 64 | 65 | # Use a persistent storage volume for sqlite database files and local Active Storage files. 66 | # Recommended to change this to a mounted volume path that is backed up off server. 67 | volumes: 68 | - "rails8_storage:/rails/storage" 69 | 70 | 71 | # Bridge fingerprinted assets, like JS and CSS, between versions to avoid 72 | # hitting 404 on in-flight requests. Combines all files from new and old 73 | # version inside the asset_path. 74 | asset_path: /rails/public/assets 75 | 76 | # Configure the image builder. 77 | builder: 78 | arch: amd64 79 | 80 | # # Build image via remote server (useful for faster amd64 builds on arm64 computers) 81 | # remote: ssh://docker@docker-builder-server 82 | # 83 | # # Pass arguments and secrets to the Docker build process 84 | # args: 85 | # RUBY_VERSION: 3.2.8 86 | # secrets: 87 | # - GITHUB_TOKEN 88 | # - RAILS_MASTER_KEY 89 | 90 | # Use a different ssh user than root 91 | # ssh: 92 | # user: app 93 | 94 | # Use accessory services (secrets come from .kamal/secrets). 95 | # accessories: 96 | # db: 97 | # image: mysql:8.0 98 | # host: 192.168.0.2 99 | # # Change to 3306 to expose port to the world instead of just local network. 100 | # port: "127.0.0.1:3306:3306" 101 | # env: 102 | # clear: 103 | # MYSQL_ROOT_HOST: '%' 104 | # secret: 105 | # - MYSQL_ROOT_PASSWORD 106 | # files: 107 | # - config/mysql/production.cnf:/etc/mysql/my.cnf 108 | # - db/production.sql:/docker-entrypoint-initdb.d/setup.sql 109 | # directories: 110 | # - data:/var/lib/mysql 111 | # redis: 112 | # image: redis:7.0 113 | # host: 192.168.0.2 114 | # port: 6379 115 | # directories: 116 | # - data:/data 117 | -------------------------------------------------------------------------------- /.github/workflows/rails_new.yml: -------------------------------------------------------------------------------- 1 | # To debug locally: 2 | # npm install -g act 3 | # act pull_request 4 | # 5 | 6 | name: New Rails project 7 | 8 | on: 9 | workflow_dispatch: {} 10 | pull_request: 11 | branches: [main] 12 | push: 13 | branches: 14 | - 'main' 15 | tags: 16 | - 'v*' 17 | 18 | jobs: 19 | typecheck_strong: 20 | name: strong typecheck 21 | runs-on: ubuntu-latest 22 | strategy: 23 | matrix: 24 | versions: 25 | # ruby 3.2 is minimum Ruby supported by Rails 8.0 26 | - ruby: "3.2" 27 | rails-major: "8" 28 | rails-minor: "0" 29 | - ruby: "3.2" 30 | rails-major: "7" 31 | rails-minor: "0" 32 | - ruby: "3.2" 33 | rails-major: "7" 34 | rails-minor: "1" 35 | fail-fast: false 36 | steps: 37 | - uses: actions/checkout@v2 38 | - uses: ruby/setup-ruby@v1 39 | with: 40 | ruby-version: ${{ matrix.versions.ruby }} 41 | bundler: latest 42 | bundler-cache: true 43 | cache-version: ${{ matrix.solargraph-version }}-${{ matrix.versions.rails-major }}.${{ matrix.versions.rails-minor }}-2025-09-09 44 | env: 45 | MATRIX_RAILS_MAJOR_VERSION: ${{ matrix.versions.rails-major }} 46 | MATRIX_RAILS_VERSION: ${{ matrix.versions.rails-major }}.${{ matrix.versions.rails-minor }} 47 | - uses: awalsh128/cache-apt-pkgs-action@latest 48 | with: 49 | packages: yq 50 | version: 1.0 51 | - name: Restore cache of gem annotations 52 | id: dot-cache-restore 53 | uses: actions/cache/restore@v4 54 | with: 55 | key: | 56 | 2025-09-09-${{ runner.os }}-dot-cache-${{ matrix.solargraph-version }}-${{ matrix.versions.rails-major }}--${{ matrix.versions.rails-minor }}-${{ hashFiles('spec/**/Gemfile.lock') }}-${{ hashFiles('Gemfile.lock') }} 57 | restore-keys: | 58 | 2025-09-09-${{ runner.os }}-dot-cache-${{ matrix.solargraph-version }}-${{ matrix.versions.rails-major }}-${{ matrix.versions.rails-minor }}-${{ hashFiles('spec/**/Gemfile.lock') }}- 59 | 2025-09-09-${{ runner.os }}-dot-cache-${{ matrix.solargraph-version }}-${{ matrix.versions.rails-major }}-${{ matrix.versions.rails-minor }}- 60 | 2025-09-09-${{ runner.os }}-dot-cache-${{ matrix.solargraph-version }}-${{ matrix.versions.rails-major }}- 61 | 2025-09-09-${{ runner.os }}-dot-cache-${{ matrix.solargraph-version }}- 62 | path: | 63 | /home/runner/.cache/solargraph 64 | 65 | - name: Typecheck default 'rails new' code 66 | run: | 67 | set -ex 68 | bundle update solargraph 69 | gem install rails -v '~> ${{ matrix.versions.rails-major }}.${{ matrix.versions.rails-minor }}.0' 70 | gem uninstall -I concurrent-ruby 71 | # https://stackoverflow.com/questions/79360526/uninitialized-constant-activesupportloggerthreadsafelevellogger-nameerror 72 | gem install concurrent-ruby -v 1.3.4 73 | pwd 74 | cd .. 75 | rails new test-project 76 | cd test-project 77 | echo 'gem "solargraph-rails", path: "../solargraph-rails"' >> Gemfile 78 | echo 'gem "solargraph", github: "castwide/solargraph", branch: "master"' >> Gemfile 79 | # https://stackoverflow.com/questions/79360526/uninitialized-constant-activesupportloggerthreadsafelevellogger-nameerror 80 | echo "gem 'concurrent-ruby', '1.3.4'" >> Gemfile 81 | bundle install 82 | bundle info solargraph 83 | bundle info solargraph-rails 84 | bin/rails --version 85 | bundle exec rbs collection init 86 | bundle exec rbs collection install 87 | bundle exec solargraph config 88 | yq -yi '.plugins += ["solargraph-rails"]' .solargraph.yml 89 | bundle exec solargraph typecheck --level strong 90 | env: 91 | MATRIX_RAILS_MAJOR_VERSION: ${{ matrix.versions.rails-major }} 92 | MATRIX_RAILS_VERSION: ${{ matrix.versions.rails-major }}.${{ matrix.versions.rails-minor }} 93 | - name: Cache gem annotations 94 | id: dot-cache-save 95 | if: always() && steps.dot-cache-restore.outputs.cache-hit != 'true' 96 | uses: actions/cache/save@v4 97 | with: 98 | key: | 99 | 2025-09-09-${{ runner.os }}-dot-cache-${{ matrix.solargraph-version }}-${{ matrix.versions.rails-major }}--${{ matrix.versions.rails-minor }}-${{ hashFiles('spec/**/Gemfile.lock') }}-${{ hashFiles('Gemfile.lock') }} 100 | path: | 101 | /home/runner/.cache/solargraph 102 | -------------------------------------------------------------------------------- /spec/helpers.rb: -------------------------------------------------------------------------------- 1 | require 'logger' 2 | require 'rails' 3 | require_relative 'definitions' 4 | 5 | module Helpers 6 | def load_string(filename, str) 7 | source = Solargraph::Source.load_string(str, filename) 8 | api_map.map(source) 9 | source 10 | end 11 | 12 | def assert_matches_definitions(map, class_name, definition_name, update: false) 13 | Definitions.new(map, class_name, definition_name, update: update).assert_matches_definitions 14 | end 15 | 16 | class Injector 17 | attr_reader :files 18 | def initialize(folder) 19 | @folder = folder 20 | @files = [] 21 | end 22 | 23 | def solargraph_version_numeric 24 | Solargraph::VERSION.split('.')[0..1].join('.').to_f 25 | end 26 | 27 | def write_file(path, content) 28 | FileUtils.mkdir_p(File.dirname(path)) 29 | File.write(path, content) 30 | @files << path 31 | # Older Solargraph versions store relative paths; return those 32 | # so we can fetch them by the same names later 33 | if solargraph_version_numeric < 0.51 34 | "./" + path 35 | else 36 | File.expand_path(path) 37 | end 38 | end 39 | end 40 | 41 | def rails_workspace(&block) 42 | rails_version = ENV.fetch('MATRIX_RAILS_VERSION') 43 | rails_major_version = rails_version.split('.').first.to_i 44 | folder = "./spec/rails#{rails_major_version}" 45 | 46 | injector = Injector.new(folder) 47 | map = nil 48 | 49 | Dir.chdir folder do 50 | yield injector if block_given? 51 | if Solargraph::ApiMap.respond_to?(:load_with_cache) 52 | map = Solargraph::ApiMap.load_with_cache('./', STDERR) 53 | else 54 | map = Solargraph::ApiMap.load('./') 55 | end 56 | injector.files.each { |f| File.delete(f) } 57 | end 58 | 59 | map 60 | end 61 | 62 | def assert_method(map, query, return_type, args: {}, &block) 63 | pin = find_pin(query, map) 64 | expect(pin).to_not be_nil, "Expected #{query} to exist, but it doesn't" 65 | 66 | pin_return_type = pin.return_type 67 | pin_return_type = pin.typify map if pin_return_type.undefined? 68 | pin_return_type = pin.probe map if pin_return_type.undefined? 69 | expect(pin_return_type.map(&:tag)).to eq(return_type) 70 | 71 | args.each_pair do |name, type| 72 | expect(parameter = pin.parameters.find { _1.name == name.to_s }).to_not be_nil, "expected #{query} param #{name} to exist, but it doesn't" 73 | expect(parameter.return_type.tag).to eq(type), "expected #{query} param #{name} to return #{type} but it returns #{parameter.return_type.tag}" 74 | end 75 | pin.parameters.each do |param| 76 | expect(args).to have_key(param.name.to_sym), "expected #{query} param #{param.name} to be expected, but it isn't" 77 | # TODO: Is this necessary? It should already be expected earlier by the arg.each_pair block 78 | expect(real_type = param.return_type.tag).to eq(args[param.name.to_sym]), "expected #{query} param #{param.name} to return #{args[param.name.to_sym]} but it returns #{real_type}" 79 | end 80 | end 81 | 82 | def find_pin(path, map = api_map) 83 | find_pin_by_path(map.pins, path, map) 84 | end 85 | 86 | def find_pin_by_path(pins, path, map) 87 | if pins.empty? 88 | return nil 89 | else 90 | top_level_pins = pins.select { |p| p.path == path } 91 | if top_level_pins.empty? 92 | scope = nil 93 | scope = :class 94 | class_name, meth, rest = path.split('.') 95 | raise "Did not understand path #{path}" unless rest.nil? 96 | if meth.nil? 97 | class_name, meth, rest = class_name.split('#') 98 | raise "Did not understand path #{path}" unless rest.nil? 99 | scope = :instance 100 | end 101 | return map.get_method_stack(class_name, meth, scope: scope).first 102 | end 103 | return_pin = top_level_pins.find do |pin| 104 | pin_return_type = pin.return_type 105 | pin_return_type = pin.typify map if pin_return_type.undefined? 106 | pin_return_type = pin.probe map if pin_return_type.undefined? 107 | pin_return_type.defined? 108 | end 109 | return_pin ||= top_level_pins.first 110 | return_pin 111 | end 112 | end 113 | 114 | def local_pins(map = api_map) 115 | map.pins.select { |p| p.filename } 116 | end 117 | 118 | def completion_at(filename, position, map = api_map) 119 | clip = map.clip_at(filename, position) 120 | cursor = clip.send(:cursor) 121 | word = cursor.chain.links.first.word 122 | 123 | Solargraph.logger.debug( 124 | "Complete: word=#{word}, links=#{cursor.chain.links}" 125 | ) 126 | 127 | clip.complete.pins.map(&:name) 128 | end 129 | 130 | def completions_for(map, filename, position) 131 | clip = map.clip_at(filename, position) 132 | 133 | clip.complete.pins.map { |pin| [pin.name, pin.return_type.map(&:tag)] }.to_h 134 | end 135 | end 136 | -------------------------------------------------------------------------------- /lib/solargraph/rails/schema.rb: -------------------------------------------------------------------------------- 1 | module Solargraph 2 | module Rails 3 | class Schema 4 | ColumnData = Struct.new(:type, :ast) 5 | 6 | RUBY_TYPES = { 7 | decimal: 'BigDecimal', 8 | float: 'BigDecimal', 9 | integer: 'Integer', 10 | date: 'Date', 11 | datetime: 'ActiveSupport::TimeWithZone', 12 | string: 'String', 13 | boolean: 'Boolean', 14 | text: 'String', 15 | jsonb: 'Hash', 16 | json: 'Hash', 17 | bigint: 'Integer', 18 | uuid: 'String', 19 | inet: 'IPAddr', 20 | citext: 'String', 21 | binary: 'String', 22 | tsvector: 'String', 23 | timestamp: 'ActiveSupport::TimeWithZone' 24 | } 25 | 26 | def self.instance 27 | @instance ||= self.new 28 | end 29 | 30 | def self.reset 31 | @instance = nil 32 | end 33 | 34 | def initialize 35 | @schema_present = File.exist?('db/schema.rb') 36 | end 37 | 38 | def process(source_map, ns) 39 | return [] unless @schema_present 40 | return [] unless Model.valid_filename?(source_map.filename) 41 | 42 | table = find_table(source_map, ns) 43 | 44 | return [] unless table 45 | 46 | pins = [] 47 | table.each do |column, data| 48 | location = Util.build_location(data.ast, 'db/schema.rb') 49 | type = RUBY_TYPES[data.type.to_sym] || 'String' 50 | %w[% %_in_database %_before_last_save].each do |tpl| 51 | name = tpl.sub('%', column) 52 | pins << Util.build_public_method(ns, name, types: [type], location: location) 53 | end 54 | %w[%? %_changed? saved_change_to_%? will_save_change_to_%?].each do |tpl| 55 | name = tpl.sub('%', column) 56 | pins << Util.build_public_method(ns, name, types: ['Boolean'], location: location) 57 | end 58 | %w[%_change_to_be_saved saved_change_to_%].each do |tpl| 59 | name = tpl.sub('%', column) 60 | types = ["Array(#{type}, #{type})"] 61 | pins << Util.build_public_method(ns, name, types: types, location: location) 62 | end 63 | pins << Util.build_public_method(ns, "#{column}=", types: [type], 64 | params: { 'value' => [type] }, 65 | location: location) 66 | 67 | # Note on this: its the starting step of dynamic filters. Technically, you can also do Model.find_by_col1_and_col2(val1, val2) 68 | # However, if we start suggestion all of those possibilities, it will simply be bad UX because of having too many suggestions 69 | pins << Util.build_public_method(ns, "find_by_#{column}", types: ['self', 'nil'], 70 | params: { 'value' => [type] }, 71 | location: location, 72 | scope: :class) 73 | end 74 | 75 | if pins.any? 76 | Solargraph.logger.debug( 77 | "[Rails][Schema] added #{pins.map(&:name)} to #{ns.path}" 78 | ) 79 | end 80 | pins 81 | end 82 | 83 | private 84 | 85 | def schema 86 | @extracted_schema ||= 87 | begin 88 | ast = NodeParser.parse(File.read('db/schema.rb'), 'db/schema.rb') 89 | extract_schema(ast) 90 | end 91 | end 92 | 93 | def find_table(source_map, ns) 94 | table_name = nil 95 | walker = Walker.from_source(source_map.source) 96 | walker.on :send, [:self, :table_name=, :str] do |ast| 97 | table_name = ast.children.last.children.first 98 | end 99 | walker.walk 100 | 101 | # always use explicit table name if present 102 | return schema[table_name] if table_name 103 | 104 | infer_table_names(ns).filter_map { |table_name| schema[table_name] }.first 105 | end 106 | 107 | def infer_table_names(ns) 108 | table_name = ns.name.tableize 109 | if ns.namespace && !ns.namespace.empty? 110 | [ns.path.tableize.tr('/', '_'), table_name] 111 | else 112 | [table_name] 113 | end 114 | end 115 | 116 | def extract_schema(ast) 117 | schema = {} 118 | 119 | walker = Walker.new(ast) 120 | walker.on :block, [:send, nil, :create_table] do |ast, query| 121 | table_name = ast.children.first.children[2].children.last 122 | schema[table_name] = {} 123 | 124 | query.on :send, %i[lvar t] do |column_ast| 125 | name = column_ast.children[2].children.last 126 | type = column_ast.children[1] 127 | 128 | next if type == :index 129 | next if type == :check_constraint 130 | schema[table_name][name] = ColumnData.new(type, column_ast) 131 | end 132 | end 133 | 134 | walker.walk 135 | schema 136 | end 137 | end 138 | end 139 | end 140 | -------------------------------------------------------------------------------- /spec/solargraph-rails/schema_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Solargraph::Rails::Schema do 4 | let(:schema) do 5 | <<~RUBY 6 | ActiveRecord::Schema.define(version: 2021_10_20_084658) do 7 | 8 | enable_extension "pg_trgm" 9 | 10 | create_table "accounts", force: :cascade do |t| 11 | t.jsonb "extra" 12 | t.decimal "balance", precision: 30, scale: 10, null: false 13 | t.integer "some_int" 14 | t.date "some_date" 15 | t.bigint "some_big_id", null: false 16 | t.string "name", null: false 17 | t.boolean "active" 18 | t.text "notes" 19 | t.inet "some_ip" 20 | t.datetime "created_at", null: false 21 | t.json "old_school_json" 22 | t.jsonb "new_school_json" 23 | t.uuid "uuid" 24 | t.citext "some_citext" 25 | t.binary "some_binary" 26 | t.timestamp "some_timestamp" 27 | 28 | t.check_constraint "balance > 0" 29 | t.index ["some_big_id"], name: "index_accounts_on_some_big_id", unique: true 30 | end 31 | end 32 | RUBY 33 | end 34 | 35 | before(:each) do 36 | # This feature has internal state that needs to be reset between test runs 37 | described_class.reset 38 | end 39 | 40 | it "generates methods based on schema" do 41 | map = rails_workspace do |root| 42 | root.write_file 'db/schema.rb', schema 43 | 44 | root.write_file 'app/models/account.rb', <<-RUBY 45 | class Account < ActiveRecord::Base 46 | end 47 | RUBY 48 | end 49 | 50 | assert_method(map, "Account#extra", ["Hash"]) do |pin| 51 | expect(pin.location.range.to_hash).to eq({ 52 | :start => { :line => 5, :character => 0 }, 53 | :end => { :line => 5, :character => 4 } 54 | }) 55 | end 56 | 57 | assert_method(map, "Account#balance", ["BigDecimal"]) 58 | assert_method(map, "Account#balance=", ["BigDecimal"], 59 | args: { value: 'BigDecimal' }) 60 | assert_method(map, "Account#some_int", ["Integer"]) 61 | assert_method(map, "Account#some_int?", ["Boolean"]) 62 | assert_method(map, "Account#some_date", ["Date"]) 63 | assert_method(map, "Account#some_big_id", ["Integer"]) 64 | assert_method(map, "Account#name", ["String"]) 65 | assert_method(map, "Account#active", ["Boolean"]) 66 | assert_method(map, "Account#active?", ["Boolean"]) 67 | assert_method(map, "Account#notes", ["String"]) 68 | assert_method(map, "Account#some_ip", ["IPAddr"]) 69 | assert_method(map, "Account#uuid", ["String"]) 70 | assert_method(map, "Account#old_school_json", ["Hash"]) 71 | assert_method(map, "Account#new_school_json", ["Hash"]) 72 | assert_method(map, "Account#some_citext", ["String"]) 73 | assert_method(map, "Account#some_binary", ["String"]) 74 | assert_method(map, "Account#some_timestamp", ["ActiveSupport::TimeWithZone"]) 75 | 76 | assert_method(map, "Account.find_by_name", ["self", "nil"], args: { value: 'String' }) 77 | end 78 | 79 | it 'infers prefixed table name' do 80 | map = rails_workspace do |root| 81 | root.write_file 'db/schema.rb', <<-RUBY 82 | ActiveRecord::Schema.define(version: 2021_10_20_084658) do 83 | create_table "accounting_invoices", force: :cascade do |t| 84 | t.decimal "amount" 85 | end 86 | end 87 | RUBY 88 | 89 | root.write_file 'app/models/accounting/invoice.rb', <<-RUBY 90 | class Accounting::Invoice < ActiveRecord::Base 91 | end 92 | RUBY 93 | end 94 | 95 | assert_method(map, "Accounting::Invoice#amount", ["BigDecimal"]) 96 | end 97 | 98 | it 'falls back unprefixed tables even if model is namespaced' do 99 | map = rails_workspace do |root| 100 | root.write_file 'db/schema.rb', <<-RUBY 101 | ActiveRecord::Schema.define(version: 2021_10_20_084658) do 102 | create_table "invoices", force: :cascade do |t| 103 | t.decimal "amount" 104 | end 105 | end 106 | RUBY 107 | 108 | root.write_file 'app/models/accounting/invoice.rb', <<-RUBY 109 | class Accounting::Invoice < ActiveRecord::Base 110 | end 111 | RUBY 112 | end 113 | 114 | # resolves to accounts table 115 | assert_method(map, "Accounting::Invoice#amount", ["BigDecimal"]) 116 | end 117 | 118 | it 'uses explicit table name if defined' do 119 | map = rails_workspace do |root| 120 | root.write_file 'db/schema.rb', <<-RUBY 121 | ActiveRecord::Schema.define(version: 2021_10_20_084658) do 122 | create_table "bills", force: :cascade do |t| 123 | t.decimal "amount" 124 | end 125 | end 126 | RUBY 127 | 128 | root.write_file 'app/models/invoice.rb', <<-RUBY 129 | class Invoice < ActiveRecord::Base 130 | self.table_name = 'bills' 131 | end 132 | RUBY 133 | end 134 | 135 | assert_method(map, 'Invoice#amount', ['BigDecimal']) 136 | end 137 | end 138 | -------------------------------------------------------------------------------- /script/generate_definitions.rb: -------------------------------------------------------------------------------- 1 | require File.join(Dir.pwd, ARGV.first, 'config/environment') 2 | 3 | require 'method_source' 4 | 5 | def _instance_methods(klass, test = klass.new) 6 | klass 7 | .instance_methods(true) 8 | .sort 9 | .reject { |m| m.to_s.start_with?('_') || (test && !test.respond_to?(m)) } 10 | .map { |m| klass.instance_method(m) } 11 | end 12 | 13 | def own_instance_methods(klass, test = klass.new) 14 | reject_meth_names = (Module.methods + Module.private_methods + [:to_yaml, :to_json]).to_set 15 | _instance_methods(klass, test).select do |m| 16 | !reject_meth_names.include?(m.name) && 17 | m.source_location && 18 | m.source_location.first.include?('gem') && 19 | m.source_location && 20 | m.source_location.first.include?('gem') && 21 | !m.source_location.first.include?('/pp-') && 22 | m.comment && 23 | !m.comment.empty? && 24 | m.comment != ':nodoc:' 25 | end 26 | end 27 | 28 | def own_class_methods(klass) 29 | reject_meth_names = (Module.methods + Module.private_methods + [:to_yaml, :to_json]).to_set 30 | class_methods(klass).select do |m| 31 | !reject_meth_names.include?(m.name) && 32 | m.source_location && 33 | m.source_location.first.include?('gem') && 34 | m.comment && 35 | !m.comment.empty? && 36 | m.comment != ':nodoc:' 37 | end 38 | end 39 | 40 | def class_methods(klass) 41 | klass 42 | .methods(true) 43 | .sort 44 | .reject { |m| m.to_s.start_with?('_') || !klass.respond_to?(m) } 45 | .map { |m| klass.method(m) } 46 | end 47 | 48 | def build_report(klass, test: klass.new) 49 | result = {} 50 | distribution = {} 51 | 52 | own_class_methods(klass).each do |meth| 53 | distribution[meth.source_location.first] ||= [] 54 | distribution[meth.source_location.first] << ".#{meth.name}" 55 | 56 | result["#{klass.to_s}.#{meth.name}"] = { types: ['undefined'], skip: false } 57 | end 58 | 59 | own_instance_methods(klass, test).each do |meth| 60 | distribution[meth.source_location.first] ||= [] 61 | distribution[meth.source_location.first] << "##{meth.name}" 62 | 63 | result["#{klass.to_s}##{meth.name}"] = { types: ['undefined'], skip: false } 64 | end 65 | 66 | pp distribution 67 | result 68 | end 69 | 70 | def add_new_methods(klass, test, yaml_filename) 71 | new_report = build_report(klass, test: test) 72 | existing_report = {} 73 | existing_report = YAML.load_file(yaml_filename) if File.exist?(yaml_filename) 74 | report = { **new_report, **existing_report } 75 | class_methods, instance_methods = report.partition { |k, _| k.include?('.') } 76 | class_methods = class_methods.sort_by { |k, _v| k }.to_h 77 | instance_methods = instance_methods.sort_by { |k, _v| k }.to_h 78 | report = { **class_methods, **instance_methods } 79 | File.write(yaml_filename, report.deep_stringify_keys.to_yaml) 80 | end 81 | 82 | def core_ext_report(klass, test = klass.new) 83 | result = {} 84 | distribution = {} 85 | 86 | class_methods(klass) 87 | .select(&:source_location) 88 | .select do |meth| 89 | loc = meth.source_location.first 90 | loc.include?('activesupport') && loc.include?('core_ext') 91 | end 92 | .each do |meth| 93 | distribution[meth.source_location.first] ||= [] 94 | distribution[meth.source_location.first] << ".#{meth.name}" 95 | 96 | result["#{klass.to_s}.#{meth.name}"] = { 97 | types: ['undefined'], 98 | skip: false 99 | } 100 | end 101 | 102 | _instance_methods(klass, test) 103 | .select(&:source_location) 104 | .select do |meth| 105 | loc = meth.source_location.first 106 | loc.include?('activesupport') && loc.include?('core_ext') 107 | end 108 | .each do |meth| 109 | distribution[meth.source_location.first] ||= [] 110 | distribution[meth.source_location.first] << "##{meth.name}" 111 | 112 | result["#{klass.to_s}##{meth.name}"] = { 113 | types: ['undefined'], 114 | skip: false 115 | } 116 | end 117 | 118 | result 119 | end 120 | 121 | add_new_methods(ActiveRecord::Base, Model.new, '../definitions/activerecord.yml') 122 | add_new_methods(ActionController::Base, false, '../definitions/actioncontroller.yml') 123 | add_new_methods(ActiveJob::Base, false, '../definitions/activejob.yml') 124 | 125 | Rails.application.routes.draw do 126 | add_new_methods(self.class, false, '../definitions/routes.yml') 127 | end 128 | 129 | add_new_methods(Rails::Application, false, '../definitions/application.yml') 130 | 131 | Rails.application.routes.draw do 132 | add_new_methods(self.class, false, '../definitions/routes.yml') 133 | end 134 | 135 | [ 136 | Array, 137 | String, 138 | Time, 139 | Date, 140 | Class, 141 | DateTime, 142 | File, 143 | Hash, 144 | Integer, 145 | Kernel 146 | ].each do |klass| 147 | test = 148 | case klass 149 | when Time 150 | Time.now 151 | when Date 152 | Date.today 153 | when File 154 | false 155 | else 156 | begin 157 | klass.new 158 | rescue StandardError 159 | false 160 | end 161 | end 162 | 163 | add_new_methods(klass, test, "../definitions/core/#{klass.to_s}.yml") 164 | end 165 | 166 | # binding.pry 167 | -------------------------------------------------------------------------------- /spec/solargraph-rails/rails_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe 'Rails API completion' do 4 | filename = nil 5 | it 'it provides Rails controller api' do 6 | map = 7 | rails_workspace do |root| 8 | filename = root.write_file 'app/controllers/things_controller.rb', <<~EOS 9 | class ThingsController < ActionController::Base 10 | res 11 | def index 12 | re 13 | end 14 | end 15 | EOS 16 | end 17 | 18 | expect(completion_at(filename, [1, 4], map)).to include('rescue_from') 19 | 20 | expect(completion_at(filename, [3, 5], map)).to include( 21 | 'respond_to', 22 | 'redirect_to', 23 | 'response', 24 | 'request', 25 | 'render' 26 | ) 27 | end 28 | 29 | it 'can auto-complete inside routes', skip: 'not working' do 30 | filename = nil 31 | map = 32 | rails_workspace do |root| 33 | filename = root.write_file 'config/routes.rb', <<~EOS 34 | Rails.application.routes.draw do 35 | res 36 | resource :things do 37 | res 38 | end 39 | end 40 | EOS 41 | end 42 | 43 | expect(completion_at(filename, [1, 5], map)).to include('resources') 44 | expect(completion_at(filename, [3, 7], map)).to include('resources') 45 | end 46 | 47 | it 'can auto-complete inside mailers' do 48 | filename = nil 49 | map = 50 | rails_workspace do |root| 51 | filename = root.write_file 'app/mailers/test_mailer.rb', <<~EOS 52 | class TestMailer < ActionMailer::Base 53 | defa 54 | def welcome_email 55 | ma 56 | end 57 | end 58 | EOS 59 | end 60 | 61 | expect(completion_at(filename, [1, 6], map)).to include('default') 62 | expect(completion_at(filename, [3, 6], map)).to include('mail') 63 | end 64 | 65 | xit 'understands mattr methods' do 66 | map = rails_workspace 67 | # assert_method(map, 'ActiveJob::QueuePriority::ClassMethods.default_priority', ['undefined']) 68 | assert_method(map, 'ActiveJob::QueueName::ClassMethods.default_queue_name', ['undefined']) 69 | # assert_method(map, 'ActiveJob::QueueName::ClassMethods#default_queue_name', ['undefined']) 70 | end 71 | 72 | it 'can auto-complete inside migrations' do 73 | filename = nil 74 | map = 75 | rails_workspace do |root| 76 | filename = root.write_file 'db/migrate/20130502114652_create_things.rb', <<~EOS 77 | class CreateThings < ActiveRecord::Migration[7.0] 78 | def self.up 79 | crea 80 | end 81 | 82 | def change 83 | crea 84 | create_table :things do |t| 85 | t.col 86 | end 87 | change_table :things do |t| 88 | t.col 89 | end 90 | create_join_table :things do |t| 91 | t.col 92 | end 93 | end 94 | end 95 | EOS 96 | end 97 | 98 | expect(completion_at(filename, [2, 7], map)).to include('create_table') 99 | expect(completion_at(filename, [6, 7], map)).to include('create_table') 100 | expect(completion_at(filename, [8, 10], map)).to include('column') 101 | expect(completion_at(filename, [11, 10], map)).to include('column') 102 | expect(completion_at(filename, [14, 10], map)).to include('column') 103 | end 104 | 105 | it 'provides completions for ActiveJob::Base' do 106 | map = rails_workspace 107 | 108 | assert_matches_definitions( 109 | map, 110 | 'ActiveJob::Base', 111 | 'activejob' 112 | ) 113 | end 114 | 115 | it 'provides completions for Rails::Application' do 116 | map = rails_workspace 117 | 118 | assert_matches_definitions( 119 | map, 120 | 'Rails::Application', 121 | 'application' 122 | ) 123 | end 124 | 125 | it 'provides completions for ActionDispatch::Routing::Mapper' do 126 | map = rails_workspace 127 | 128 | assert_matches_definitions( 129 | map, 130 | 'ActionDispatch::Routing::Mapper', 131 | 'routes' 132 | ) 133 | end 134 | 135 | it 'provides completions for ActiveRecord::Base' do 136 | map = rails_workspace 137 | 138 | assert_matches_definitions(map, 'ActiveRecord::Base', 'activerecord') 139 | end 140 | 141 | it 'provides completions for ActionController::Base' do 142 | map = rails_workspace 143 | assert_matches_definitions( 144 | map, 145 | 'ActionController::Base', 146 | 'actioncontroller' 147 | ) 148 | end 149 | 150 | # https://github.com/iftheshoefritz/solargraph-rails/issues/124 151 | xit 'understands ActiveRecord::Base#validation_context' do 152 | map = rails_workspace 153 | assert_method(map, 'ActiveRecord::Base#validation_context', ['undefined']) 154 | end 155 | 156 | context 'auto-completes ActiveSupport core extensions' do 157 | Dir 158 | .glob('spec/definitions/core/*.yml') 159 | .each do |path| 160 | name = File.basename(path).split('.').first 161 | 162 | it "core/#{name}" do 163 | map = rails_workspace 164 | 165 | assert_matches_definitions(map, name, "core/#{name}") 166 | end 167 | end 168 | end 169 | end 170 | -------------------------------------------------------------------------------- /spec/rails8/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | The page you were looking for doesn’t exist (404 Not found) 8 | 9 | 10 | 11 | 12 | 13 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 |
104 |
105 | 106 |
107 |
108 |

The page you were looking for doesn’t exist. You may have mistyped the address or the page may have moved. If you’re the application owner check the logs for more information.

109 |
110 |
111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /spec/solargraph-rails/model_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Solargraph::Rails::Model do 4 | let(:api_map) { Solargraph::ApiMap.new } 5 | 6 | it 'generates methods for singular association' do 7 | load_string 'app/models/transaction.rb', 8 | <<~RUBY 9 | class Transaction < ActiveRecord::Base 10 | belongs_to :account 11 | has_one :category 12 | end 13 | RUBY 14 | 15 | assert_method( 16 | api_map, 17 | 'Transaction#account', 18 | ['Account'] 19 | ) do |pin| 20 | expect(pin.location.filename).to eq( 21 | File.expand_path('app/models/transaction.rb') 22 | ) 23 | expect(pin.location.range.to_hash).to eq( 24 | { start: { line: 1, character: 0 }, end: { line: 1, character: 2 } } 25 | ) 26 | end 27 | 28 | assert_method(api_map, 'Transaction#category', ['Category']) 29 | end 30 | 31 | it 'generates methods for association with custom class_name' do 32 | load_string 'app/models/transaction.rb', 33 | <<-RUBY 34 | class Transaction < ActiveRecord::Base 35 | belongs_to :account, class_name: 'CustomAccount' 36 | end 37 | RUBY 38 | 39 | assert_method( 40 | api_map, 41 | 'Transaction#account', 42 | ['CustomAccount'] 43 | ) 44 | end 45 | 46 | it 'generates methods for plural associations' do 47 | load_string 'app/models/account.rb', 48 | <<-RUBY 49 | class Account < ActiveRecord::Base 50 | has_many :transactions 51 | has_and_belongs_to_many :things 52 | end 53 | RUBY 54 | 55 | assert_method( 56 | api_map, 57 | 'Account#transactions', 58 | ['Transaction::ActiveRecord_Relation'] 59 | ) 60 | assert_method( 61 | api_map, 62 | 'Account#things', 63 | ['Thing::ActiveRecord_Relation'] 64 | ) 65 | end 66 | 67 | it 'exposes scopes as class methods' do 68 | load_string 'app/models/transaction.rb', 69 | <<-RUBY 70 | class Transaction < ActiveRecord::Base 71 | scope :positive, ->() { where(foo: 'bar') } 72 | end 73 | RUBY 74 | 75 | assert_method(api_map, 'Transaction.positive', ['Transaction::ActiveRecord_Relation']) 76 | assert_method(api_map, 'Transaction::ActiveRecord_Relation#positive', ['Transaction::ActiveRecord_Relation']) 77 | end 78 | 79 | it 'exposes scopes as relation instance methods' do 80 | load_string 'app/models/person.rb', 81 | <<~RUBY 82 | class Person < ActiveRecord::Base 83 | scope :taller_than, ->(h) { where(height: h..) } 84 | end 85 | RUBY 86 | 87 | assert_method( 88 | api_map, 89 | 'Person::ActiveRecord_Relation#taller_than', 90 | ['Person::ActiveRecord_Relation'], 91 | args: { h: 'undefined' } 92 | ) 93 | end 94 | 95 | it 'handles primary_abstract_class without breaking' do 96 | expect do 97 | load_string 'app/models/application_record.rb', 98 | <<-RUBY 99 | class ApplicationRecord < ActiveRecord::Base 100 | primary_abstract_class 101 | end 102 | RUBY 103 | end.not_to raise_error 104 | end 105 | 106 | it 'generates scope methods with parameters' do 107 | load_string 'app/models/person.rb', 108 | <<-RUBY 109 | class Person < ActiveRecord::Base 110 | scope :taller_than, 111 | ->(min_height) { where('height > ?', min_height) } 112 | end 113 | RUBY 114 | 115 | assert_method( 116 | api_map, 117 | 'Person.taller_than', 118 | ['Person::ActiveRecord_Relation'], 119 | args: { min_height: 'undefined' } 120 | ) 121 | end 122 | 123 | it 'does not generate methods for variable named scope' do 124 | load_string 'app/models/person.rb', 125 | <<-RUBY 126 | class Person < ActiveRecord::Base 127 | def some_method 128 | scope = Person.where(id: 0) 129 | scope.count 130 | end 131 | 132 | scope :taller_than, 133 | ->(min_height) { where('height > ?', min_height) } 134 | end 135 | RUBY 136 | 137 | # TODO: Does this test do the thing it says does? 138 | assert_method( 139 | api_map, 140 | 'Person.taller_than', 141 | ['Person::ActiveRecord_Relation'], 142 | args: { min_height: 'undefined' } 143 | ) 144 | end 145 | 146 | it 'exposes class methods as instance methods on relations', if: Solargraph::Rails::Delegate.supported? do 147 | load_string 'app/models/person.rb', 148 | <<~RUBY 149 | class Person < ActiveRecord::Base 150 | # @param h [Numeric] 151 | def self.taller_than(h) 152 | where(height: h..) 153 | end 154 | end 155 | RUBY 156 | 157 | assert_method( 158 | api_map, 159 | 'Person::ActiveRecord_Relation#taller_than', 160 | ['Person::ActiveRecord_Relation'], 161 | args: { h: 'Numeric' } 162 | ) 163 | end 164 | end 165 | -------------------------------------------------------------------------------- /lib/solargraph/rails/annotations/active_storage.rb: -------------------------------------------------------------------------------- 1 | module ActiveStorage 2 | # Provides the class-level DSL for declaring an Active Record model's attachments. 3 | module Attached 4 | module Model 5 | # NOTE: the below would normally be in a ClassMethods module - 6 | # but Solargraph currently uses yard-activesupport-concern, 7 | # which jams the method in as class methods under this module. 8 | # 9 | # Specifies the relation between a single attachment and the model. 10 | # 11 | # class User < ApplicationRecord 12 | # has_one_attached :avatar 13 | # end 14 | # 15 | # There is no column defined on the model side, Active Storage takes 16 | # care of the mapping between your records and the attachment. 17 | # 18 | # To avoid N+1 queries, you can include the attached blobs in your query like so: 19 | # 20 | # User.with_attached_avatar 21 | # 22 | # Under the covers, this relationship is implemented as a +has_one+ association to a 23 | # ActiveStorage::Attachment record and a +has_one-through+ association to a 24 | # ActiveStorage::Blob record. These associations are available as +avatar_attachment+ 25 | # and +avatar_blob+. But you shouldn't need to work with these associations directly in 26 | # most circumstances. 27 | # 28 | # The system has been designed to having you go through the ActiveStorage::Attached::One 29 | # proxy that provides the dynamic proxy to the associations and factory methods, like +attach+. 30 | # 31 | # If the +:dependent+ option isn't set, the attachment will be purged 32 | # (i.e. destroyed) whenever the record is destroyed. 33 | # 34 | # If you need the attachment to use a service which differs from the globally configured one, 35 | # pass the +:service+ option. For instance: 36 | # 37 | # class User < ActiveRecord::Base 38 | # has_one_attached :avatar, service: :s3 39 | # end 40 | # 41 | # If you need to enable +strict_loading+ to prevent lazy loading of attachment, 42 | # pass the +:strict_loading+ option. You can do: 43 | # 44 | # class User < ApplicationRecord 45 | # has_one_attached :avatar, strict_loading: true 46 | # end 47 | # 48 | 49 | # @param name [String, Symbol] 50 | # @param dependent [Symbol] the action to take on the attachment when the record is destroyed 51 | # @param strict_loading [Boolean] 52 | # @param service [String, Symbol, nil] the service to use for the attachment 53 | # @return [void] 54 | def self.has_one_attached(name, dependent: :default, service: nil, strict_loading: false); end 55 | 56 | # Specifies the relation between multiple attachments and the model. 57 | # 58 | # class Gallery < ApplicationRecord 59 | # has_many_attached :photos 60 | # end 61 | # 62 | # There are no columns defined on the model side, Active Storage takes 63 | # care of the mapping between your records and the attachments. 64 | # 65 | # To avoid N+1 queries, you can include the attached blobs in your query like so: 66 | # 67 | # Gallery.where(user: Current.user).with_attached_photos 68 | # 69 | # Under the covers, this relationship is implemented as a +has_many+ association to a 70 | # ActiveStorage::Attachment record and a +has_many-through+ association to a 71 | # ActiveStorage::Blob record. These associations are available as +photos_attachments+ 72 | # and +photos_blobs+. But you shouldn't need to work with these associations directly in 73 | # most circumstances. 74 | # 75 | # The system has been designed to having you go through the ActiveStorage::Attached::Many 76 | # proxy that provides the dynamic proxy to the associations and factory methods, like +#attach+. 77 | # 78 | # If the +:dependent+ option isn't set, all the attachments will be purged 79 | # (i.e. destroyed) whenever the record is destroyed. 80 | # 81 | # If you need the attachment to use a service which differs from the globally configured one, 82 | # pass the +:service+ option. For instance: 83 | # 84 | # class Gallery < ActiveRecord::Base 85 | # has_many_attached :photos, service: :s3 86 | # end 87 | # 88 | # If you need to enable +strict_loading+ to prevent lazy loading of attachments, 89 | # pass the +:strict_loading+ option. You can do: 90 | # 91 | # class Gallery < ApplicationRecord 92 | # has_many_attached :photos, strict_loading: true 93 | # end 94 | # 95 | # @param name [String, Symbol] 96 | # @param dependent [Symbol] the action to take on the attachment when the record is destroyed 97 | # @param strict_loading [Boolean] 98 | # @param service [String, Symbol, nil] the service to use for the attachment 99 | # @return [void] 100 | def self.has_many_attached(name, dependent: :default, service: nil, strict_loading: false); end 101 | 102 | # @return [void] 103 | def self.attachment_changes(); end 104 | 105 | # @return [Boolean] 106 | def self.changed_for_autosave?; end 107 | 108 | # @param lock [BasicObject] 109 | # @return [self] 110 | def self.reload(lock = false); end 111 | end 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /spec/definitions/core/Kernel.yml: -------------------------------------------------------------------------------- 1 | --- 2 | Kernel.acts_like?: 3 | types: 4 | - Boolean 5 | skip: false 6 | Kernel.alias_attribute: 7 | types: 8 | - undefined 9 | skip: false 10 | Kernel.anonymous?: 11 | types: 12 | - Boolean 13 | skip: false 14 | Kernel.as_json: 15 | types: 16 | - undefined 17 | skip: false 18 | Kernel.attr_internal: 19 | types: 20 | - void 21 | skip: 22 | - 0.48.0 23 | - 0.49.0 24 | - 0.50.0 25 | - 0.51.2 26 | - 0.52.0 27 | Kernel.attr_internal_accessor: 28 | types: 29 | - void 30 | skip: 31 | - 0.48.0 32 | - 0.49.0 33 | - 0.50.0 34 | - 0.51.2 35 | - 0.52.0 36 | Kernel.attr_internal_reader: 37 | types: 38 | - void 39 | skip: 40 | - 0.48.0 41 | - 0.49.0 42 | - 0.50.0 43 | - 0.51.2 44 | - 0.52.0 45 | Kernel.attr_internal_writer: 46 | types: 47 | - void 48 | skip: 49 | - 0.48.0 50 | - 0.49.0 51 | - 0.50.0 52 | - 0.51.2 53 | - 0.52.0 54 | Kernel.blank?: 55 | types: 56 | - Boolean 57 | skip: false 58 | Kernel.cattr_accessor: 59 | types: 60 | - undefined 61 | skip: false 62 | Kernel.cattr_reader: 63 | types: 64 | - undefined 65 | skip: false 66 | Kernel.cattr_writer: 67 | types: 68 | - undefined 69 | skip: false 70 | Kernel.concern: 71 | types: 72 | - undefined 73 | skip: false 74 | Kernel.concerning: 75 | types: 76 | - undefined 77 | skip: false 78 | Kernel.deep_dup: 79 | types: 80 | - undefined 81 | skip: false 82 | Kernel.delegate: 83 | types: 84 | - undefined 85 | skip: false 86 | Kernel.delegate_missing_to: 87 | types: 88 | - undefined 89 | skip: false 90 | Kernel.deprecate: 91 | types: 92 | - undefined 93 | skip: false 94 | Kernel.duplicable?: 95 | types: 96 | - 'true' 97 | skip: 98 | - 0.48.0 99 | - 0.49.0 100 | - 0.50.0 101 | - 0.51.2 102 | - 0.52.0 103 | Kernel.enable_warnings: 104 | types: 105 | - undefined 106 | skip: false 107 | Kernel.html_safe?: 108 | types: 109 | - 'false' 110 | skip: 111 | - 0.48.0 112 | - 0.49.0 113 | - 0.50.0 114 | - 0.51.2 115 | - 0.52.0 116 | Kernel.in?: 117 | types: 118 | - Boolean 119 | skip: false 120 | Kernel.instance_values: 121 | types: 122 | - undefined 123 | skip: false 124 | Kernel.instance_variable_names: 125 | types: 126 | - undefined 127 | skip: false 128 | Kernel.mattr_accessor: 129 | types: 130 | - undefined 131 | skip: false 132 | Kernel.mattr_reader: 133 | types: 134 | - undefined 135 | skip: false 136 | Kernel.mattr_writer: 137 | types: 138 | - undefined 139 | skip: false 140 | Kernel.method_visibility: 141 | types: 142 | - undefined 143 | skip: false 144 | Kernel.module_parent: 145 | types: 146 | - undefined 147 | skip: false 148 | Kernel.module_parent_name: 149 | types: 150 | - undefined 151 | skip: false 152 | Kernel.module_parents: 153 | types: 154 | - undefined 155 | skip: false 156 | Kernel.presence: 157 | types: 158 | - nil 159 | - self 160 | skip: false 161 | Kernel.presence_in: 162 | types: 163 | - nil 164 | - self 165 | skip: false 166 | Kernel.present?: 167 | types: 168 | - Boolean 169 | skip: false 170 | Kernel.redefine_method: 171 | types: 172 | - undefined 173 | skip: false 174 | Kernel.redefine_singleton_method: 175 | types: 176 | - undefined 177 | skip: false 178 | Kernel.remove_possible_method: 179 | types: 180 | - undefined 181 | skip: false 182 | Kernel.remove_possible_singleton_method: 183 | types: 184 | - undefined 185 | skip: false 186 | Kernel.silence_redefinition_of_method: 187 | types: 188 | - undefined 189 | skip: false 190 | Kernel.silence_warnings: 191 | types: 192 | - undefined 193 | skip: false 194 | Kernel.suppress: 195 | types: 196 | - undefined 197 | skip: false 198 | Kernel.thread_cattr_accessor: 199 | types: 200 | - undefined 201 | skip: false 202 | Kernel.thread_cattr_reader: 203 | types: 204 | - undefined 205 | skip: false 206 | Kernel.thread_cattr_writer: 207 | types: 208 | - undefined 209 | skip: false 210 | Kernel.thread_mattr_accessor: 211 | types: 212 | - undefined 213 | skip: false 214 | Kernel.thread_mattr_reader: 215 | types: 216 | - undefined 217 | skip: false 218 | Kernel.thread_mattr_writer: 219 | types: 220 | - undefined 221 | skip: false 222 | Kernel.to_param: 223 | types: 224 | - undefined 225 | skip: false 226 | Kernel.to_query: 227 | types: 228 | - String 229 | skip: 230 | - 0.48.0 231 | - 0.49.0 232 | - 0.50.0 233 | - 0.51.2 234 | - 0.52.0 235 | Kernel.try: 236 | types: 237 | - generic 238 | - undefined 239 | skip: 240 | - 0.48.0 241 | - 0.49.0 242 | - 0.50.0 243 | - 0.51.2 244 | - 0.52.0 245 | - 0.55.alpha 246 | - 0.56.0 247 | - 0.56.1 248 | - 0.56.2 249 | - 0.56.2-post 250 | - 0.56.alpha 251 | - 0.57.0 252 | - 0.57.alpha 253 | - branch-castwide-master 254 | - branch-2025-09-22 255 | Kernel.try!: 256 | types: 257 | - generic 258 | - undefined 259 | skip: 260 | - 0.48.0 261 | - 0.49.0 262 | - 0.50.0 263 | - 0.51.2 264 | - 0.52.0 265 | - 0.55.alpha 266 | - 0.56.0 267 | - 0.56.1 268 | - 0.56.2 269 | - 0.56.2-post 270 | - 0.56.alpha 271 | - 0.57.0 272 | - 0.57.alpha 273 | - branch-castwide-master 274 | - branch-2025-09-22 275 | Kernel.with_options: 276 | types: 277 | - undefined 278 | skip: false 279 | Kernel.with_warnings: 280 | types: 281 | - undefined 282 | skip: false 283 | Kernel#class_eval: 284 | types: 285 | - undefined 286 | skip: false 287 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # solargraph-rails changelog 2 | 3 | ## Changes 4 | 5 | ### v1.2.4 6 | 7 | Features / fixes: 8 | - Enable support for Solargraph 0.74.x with annotation fixes 9 | 10 | Internal improvements: 11 | - CI improvements for easier Solargraph pre-release regression testing 12 | 13 | ### v1.2.3 14 | 15 | Internal improvements: 16 | - Add notional 0.56.2 release to support regression testing in main 17 | Solargraph project 18 | 19 | ### v1.2.2 20 | 21 | Features / fixes: 22 | - Fix bad gem release 23 | 24 | Internal improvements: 25 | - Fix checklist to avoid future 26 | 27 | ### v1.2.1 28 | 29 | Features / fixes: 30 | - Add annotation for ActiveRecord::Base#reload 31 | - Add annotation for Rails::Application.config 32 | - Add static Rails::Application.config method annotation 33 | - Retire earlier version expectations 34 | - Fix changelog typo 35 | 36 | Internal improvements: 37 | - CI/testing improvements: 38 | - Add PR comments, rubocop-yard 39 | - Update rbs collection in specs 40 | - Test current Solargraph pre-release PRs 41 | 42 | ### v1.2 43 | 44 | Most of these are courtesy of @grncdr, with integration work by 45 | @iftheshoefritz and @apiology 46 | 47 | Features / fixes: 48 | - Unlock support for Solargraph ~>0.56.0, with [major 49 | improvements](https://github.com/castwide/solargraph/blob/master/CHANGELOG.md) 50 | across the board 51 | - Support writers in Rails models 52 | - Support `gem_rbs_collection` for better quality Rails types 53 | - Use Solargraph::Pin::DelegatedMethod to define delegate methods 54 | - Include overloads and parameter types for ActiveRecord methods 55 | - Improve private relation support for ActiveRecord 56 | - Add find_by_column methods, clean up spec (Thanks, @ShadiestGoat!) 57 | - Annotation improvements driven by testing 58 | 59 | 60 | Internal improvements: 61 | - Many CI, linting and testing improvements 62 | - Extract Util.extract_option helper 63 | - Remove Rails 5 & Rails 6 specs 64 | - Configure test app to behave like a real app 65 | - Use ruby/setup-ruby action and test app Gemfile for CI 66 | - Add a plugin that generates yard docs after bundle install 67 | - Update test pin definitions and exclusions to be Solargraph and 68 | Rails version-focused in a single location 69 | - Working build matrix with heavy caching 70 | - Add typecheck workflow 71 | - Use Solargraph::Pin::DelegatedMethod to define delegate methods 72 | - Update README introduction 73 | - Update model spec to expect private relation types 74 | - Fix undefined closure for generated parameter pins 75 | - Add Rails 7.1, 7.2 and 8.0 to test matrix 76 | - README.md example bug fix (Thanks, @snuggs!) 77 | 78 | ### v1.1.2 79 | 80 | Features / fixes: 81 | 82 | - fix bad gemspec solargraph dependency 83 | - additional pre-packaged Rails type definitions (Thanks, @grncdr!) 84 | 85 | Internal improvements: 86 | 87 | - Use normal ruby source for extra YARD annotations (Thanks, @grncdr!) 88 | - Speed up CI for recent Solargraph version fresh builds 89 | - spec reliability fixes 90 | 91 | ### v1.1.1 92 | 93 | New maintainer: @apiology 94 | 95 | Features / fixes: 96 | 97 | - support tsvector type columns in migrations (#55) 98 | - support recent Solargraph releases 99 | - fix incorrect return type in Walker.normalize_ast 100 | - limit Solargraph versions to those that support global conventions for now pending new Solargraph release (<0.53 for now) 101 | - limit Solargraph versions to tested versions (>0.48) 102 | - additional built-in annotations for better completions and types 103 | - work around ActiveSupport 7.0.x core\_ext Logger bug 104 | 105 | Internal improvements: 106 | 107 | - revive and improve CI workflow for higher quality releases (Huge thanks to @grncdr!) 108 | - cache YARD gem annotations for faster GitHub workflows via RubyGems plugin 109 | - track definition skips by Solargraph version 110 | - remove testing of Rails 5/6; retain and revive Rails 7 testing 111 | - sync type definitions with current functionality 112 | - switch to more recent Solargraph releases in CI matrix 113 | - support recent Solargraph versions in specs 114 | - use `Solargraph::ApiMap.load_with_cache` when available in specs for eager type loading 115 | - use .Gemfile pattern to allow local untracked Gemfile changes 116 | - better support for updating definitions 117 | 118 | ### v1.1.0 119 | 120 | - remove pinned solargraph version 121 | 122 | ### v1.0.1 123 | 124 | https://github.com/alisnic/solargraph-arc was merged, with the following features: 125 | - fixes autocompletion for multi-level classes defined in 1 line `class Foo::Bar::Baz`. See https://github.com/castwide/solargraph/issues/506 126 | - autocomplete database columns by parsing db/schema.rb 127 | - autocomplete of model relations 128 | - parsing of `delegate` calls 129 | - completions for methods generated by Devise 130 | - better support for running solargraph outside bundle 131 | - better completion inside controllers. `request`, `response`, `params`, etc. 132 | - autocomplete inside routes.rb 133 | - autocomplete inside migrations 134 | - completions for methods generated by ActiveStorage 135 | - better ActiveRecord completions 136 | 137 | ### v1.0.0.pre.1 138 | 139 | https://github.com/alisnic/solargraph-arc was merged, with the following features: 140 | - fixes autocompletion for multi-level classes defined in 1 line `class Foo::Bar::Baz`. See https://github.com/castwide/solargraph/issues/506 141 | - autocomplete database columns by parsing db/schema.rb 142 | - autocomplete of model relations 143 | - parsing of `delegate` calls 144 | - completions for methods generated by Devise 145 | - better support for running solargraph outside bundle 146 | - better completion inside controllers. `request`, `response`, `params`, etc. 147 | - autocomplete inside routes.rb 148 | - autocomplete inside migrations 149 | - completions for methods generated by ActiveStorage 150 | - better ActiveRecord completions 151 | 152 | ### v0.3.0 153 | * Require String inflection monkeypatches directly to avoid error from ActiveSupport v7 154 | * Remove Gemfile.lock from version control 155 | 156 | ### v0.2.2.pre.4 157 | * Loosen the dependency requirements 158 | 159 | ### v0.2.2.pre.3 160 | * Update to solargraph 0.42.3 161 | * Determine return type of association using class_name argument 162 | 163 | ### v0.2.2.pre.2 164 | 165 | * Simplistic support for associations 166 | * Added dependency on ActiveSupport 6.1 for helpful String methods like `.camelize` and `.singularize` 167 | 168 | ### v0.2.1 169 | 170 | * Update Solargraph to 0.41 171 | 172 | ### v0.2.0 173 | First released version with no need to use --pre! 174 | 175 | * Updated dependencies with security warnings 176 | -------------------------------------------------------------------------------- /spec/rails8/public/400.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | The server cannot process the request due to a client error (400 Bad Request) 8 | 9 | 10 | 11 | 12 | 13 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 |
104 |
105 | 106 |
107 |
108 |

The server cannot process the request due to a client error. Please check the request and try again. If you’re the application owner check the logs for more information.

109 |
110 |
111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /spec/rails8/public/406-unsupported-browser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Your browser is not supported (406 Not Acceptable) 8 | 9 | 10 | 11 | 12 | 13 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 |
104 |
105 | 106 |
107 |
108 |

Your browser is not supported.
Please upgrade your browser to continue.

109 |
110 |
111 | 112 | 113 | 114 | 115 | --------------------------------------------------------------------------------