diff options
Diffstat (limited to 'railties/lib/rails')
26 files changed, 130 insertions, 63 deletions
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 293a736bfd..a200a1005c 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -472,7 +472,8 @@ module Rails ActiveSupport::EncryptedConfiguration.new( config_path: Rails.root.join(path), key_path: Rails.root.join(key_path), - env_key: env_key + env_key: env_key, + raise_if_missing_key: config.require_master_key ) end diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index cbc04f8a48..f02aef94e0 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -16,7 +16,8 @@ module Rails :ssl_options, :public_file_server, :session_options, :time_zone, :reload_classes_only_on_change, :beginning_of_week, :filter_redirect, :x, :enable_dependency_loading, - :read_encrypted_secrets, :log_level, :content_security_policy_report_only + :read_encrypted_secrets, :log_level, :content_security_policy_report_only, + :require_master_key attr_reader :encoding, :api_only @@ -56,6 +57,7 @@ module Rails @read_encrypted_secrets = false @content_security_policy = nil @content_security_policy_report_only = false + @require_master_key = false end def load_defaults(target_version) @@ -100,6 +102,7 @@ module Rails if respond_to?(:active_support) active_support.use_authenticated_message_encryption = true + active_support.use_sha1_digests = true end if respond_to?(:action_controller) diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb index 3d938be951..c4b188aeee 100644 --- a/railties/lib/rails/application/finisher.rb +++ b/railties/lib/rails/application/finisher.rb @@ -58,7 +58,7 @@ module Rails end # This needs to happen before eager load so it happens - # in exactly the same point regardless of config.cache_classes + # in exactly the same point regardless of config.eager_load initializer :run_prepare_callbacks do |app| app.reloader.prepare! end diff --git a/railties/lib/rails/commands/credentials/credentials_command.rb b/railties/lib/rails/commands/credentials/credentials_command.rb index 8085f07c2b..385d3976da 100644 --- a/railties/lib/rails/commands/credentials/credentials_command.rb +++ b/railties/lib/rails/commands/credentials/credentials_command.rb @@ -33,8 +33,7 @@ module Rails def show require_application_and_environment! - say Rails.application.credentials.read.presence || - "No credentials have been added yet. Use bin/rails credentials:edit to change that." + say Rails.application.credentials.read.presence || missing_credentials_message end private @@ -67,6 +66,14 @@ module Rails Rails::Generators::CredentialsGenerator.new end + + def missing_credentials_message + if Rails.application.credentials.key.nil? + "Missing master key to decrypt credentials. See bin/rails credentials:help" + else + "No credentials have been added yet. Use bin/rails credentials:edit to change that." + end + end end end end diff --git a/railties/lib/rails/commands/encrypted/encrypted_command.rb b/railties/lib/rails/commands/encrypted/encrypted_command.rb index 898094f1a4..912c453f09 100644 --- a/railties/lib/rails/commands/encrypted/encrypted_command.rb +++ b/railties/lib/rails/commands/encrypted/encrypted_command.rb @@ -37,9 +37,9 @@ module Rails def show(file_path) require_application_and_environment! + encrypted = Rails.application.encrypted(file_path, key_path: options[:key]) - say Rails.application.encrypted(file_path, key_path: options[:key]).read.presence || - "File '#{file_path}' does not exist. Use bin/rails encrypted:edit #{file_path} to change that." + say encrypted.read.presence || missing_encrypted_message(key: encrypted.key, key_path: options[:key], file_path: file_path) end private @@ -72,6 +72,14 @@ module Rails Rails::Generators::EncryptedFileGenerator.new end + + def missing_encrypted_message(key:, key_path:, file_path:) + if key.nil? + "Missing '#{key_path}' to decrypt data. See bin/rails encrypted:help" + else + "File '#{file_path}' does not exist. Use bin/rails encrypted:edit #{file_path} to change that." + end + end end end end diff --git a/railties/lib/rails/commands/secrets/secrets_command.rb b/railties/lib/rails/commands/secrets/secrets_command.rb index 73a88767e2..a36ccf314c 100644 --- a/railties/lib/rails/commands/secrets/secrets_command.rb +++ b/railties/lib/rails/commands/secrets/secrets_command.rb @@ -56,7 +56,7 @@ module Rails private def deprecate_in_favor_of_credentials_and_exit say "Encrypted secrets is deprecated in favor of credentials. Run:" - say "bin/rails credentials --help" + say "bin/rails credentials:help" exit 1 end diff --git a/railties/lib/rails/commands/server/server_command.rb b/railties/lib/rails/commands/server/server_command.rb index 703ec59087..e546fe3e4b 100644 --- a/railties/lib/rails/commands/server/server_command.rb +++ b/railties/lib/rails/commands/server/server_command.rb @@ -27,7 +27,7 @@ module Rails app = super if app.is_a?(Class) ActiveSupport::Deprecation.warn(<<-MSG.squish) - Use `Rails::Application` subclass to start the server is deprecated and will be removed in Rails 6.0. + Using `Rails::Application` subclass to start the server is deprecated and will be removed in Rails 6.0. Please change `run #{app}` to `run Rails.application` in config.ru. MSG end diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index b9ae24de59..863a914912 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -84,6 +84,9 @@ module Rails class_option :skip_system_test, type: :boolean, default: false, desc: "Skip system test files" + class_option :skip_bootsnap, type: :boolean, default: false, + desc: "Skip bootsnap gem" + class_option :dev, type: :boolean, default: false, desc: "Setup the #{name} with Gemfile pointing to your Rails checkout" @@ -298,7 +301,7 @@ module Rails # %w( mysql postgresql sqlite3 oracle frontbase ibm_db sqlserver jdbcmysql jdbcsqlite3 jdbcpostgresql ) case options[:database] when "mysql" then ["mysql2", ["~> 0.4.4"]] - when "postgresql" then ["pg", ["~> 0.18"]] + when "postgresql" then ["pg", [">= 0.18", "< 2.0"]] when "oracle" then ["activerecord-oracle_enhanced-adapter", nil] when "frontbase" then ["ruby-frontbase", nil] when "sqlserver" then ["activerecord-sqlserver-adapter", nil] @@ -312,11 +315,13 @@ module Rails def convert_database_option_for_jruby if defined?(JRUBY_VERSION) - case options[:database] - when "postgresql" then options[:database].replace "jdbcpostgresql" - when "mysql" then options[:database].replace "jdbcmysql" - when "sqlite3" then options[:database].replace "jdbcsqlite3" + opt = options.dup + case opt[:database] + when "postgresql" then opt[:database] = "jdbcpostgresql" + when "mysql" then opt[:database] = "jdbcmysql" + when "sqlite3" then opt[:database] = "jdbcsqlite3" end + self.options = opt.freeze end end @@ -435,6 +440,10 @@ module Rails !options[:skip_listen] && os_supports_listen_out_of_the_box? end + def depend_on_bootsnap? + !options[:skip_bootsnap] && !options[:dev] + end + def os_supports_listen_out_of_the_box? RbConfig::CONFIG["host_os"] =~ /darwin|linux/ end @@ -456,16 +465,6 @@ module Rails end end - def run_active_storage - unless skip_active_storage? - if bundle_install? - rails_command "active_storage:install", capture: options[:quiet] - else - log("Active Storage installation was skipped. Please run `bin/rails active_storage:install` to install Active Storage files.") - end - end - end - def empty_directory_with_keep_file(destination, config = {}) empty_directory(destination, config) keep_file(destination) diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb index 2728459968..f7fd30a5fb 100644 --- a/railties/lib/rails/generators/generated_attribute.rb +++ b/railties/lib/rails/generators/generated_attribute.rb @@ -75,7 +75,7 @@ module Rails when :date then :date_select when :text then :text_area when :boolean then :check_box - else + else :text_field end end @@ -91,7 +91,7 @@ module Rails when :text then "MyText" when :boolean then false when :references, :belongs_to then nil - else + else "" end end diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index bf4570db90..fd9da7803f 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -463,7 +463,6 @@ module Rails public_task :apply_rails_template, :run_bundle public_task :run_webpack, :generate_spring_binstubs - public_task :run_active_storage def run_after_bundle_callbacks @after_bundle_callbacks.each(&:call) diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile.tt b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt index e3ed3e7c11..23bb89f4ce 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile.tt +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt @@ -29,9 +29,11 @@ ruby <%= "'#{RUBY_VERSION}'" -%> # Use Capistrano for deployment # gem 'capistrano-rails', group: :development +<% if depend_on_bootsnap? -%> # Reduces boot times through caching; required in config/boot.rb gem 'bootsnap', '>= 1.1.0', require: false +<%- end -%> <%- if options.api? -%> # Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible # gem 'rack-cors' diff --git a/railties/lib/rails/generators/rails/app/templates/bin/yarn.tt b/railties/lib/rails/generators/rails/app/templates/bin/yarn.tt index b4e4d95286..3d5051ec4c 100644 --- a/railties/lib/rails/generators/rails/app/templates/bin/yarn.tt +++ b/railties/lib/rails/generators/rails/app/templates/bin/yarn.tt @@ -1,7 +1,7 @@ APP_ROOT = File.expand_path('..', __dir__) Dir.chdir(APP_ROOT) do begin - exec "yarnpkg #{ARGV.join(' ')}" + exec %w(yarnpkg) + ARGV rescue Errno::ENOENT $stderr.puts "Yarn executable was not detected in the system." $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/application.rb.tt index 9e03e86771..d1a09f9c3c 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb.tt @@ -27,8 +27,9 @@ module <%= app_const_base %> config.load_defaults <%= Rails::VERSION::STRING.to_f %> # Settings in config/environments/* take precedence over those specified here. - # Application configuration should go into files in config/initializers - # -- all .rb files in that directory are automatically loaded. + # Application configuration can go into files in config/initializers + # -- all .rb files in that directory are automatically loaded after loading + # the framework and any gems in your application. <%- if options.api? -%> # Only loads a smaller set of middleware suitable for API only apps. diff --git a/railties/lib/rails/generators/rails/app/templates/config/boot.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/boot.rb.tt index b9e460cef3..42d46b8175 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/boot.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/boot.rb.tt @@ -1,4 +1,6 @@ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) require 'bundler/setup' # Set up gems listed in the Gemfile. +<% if depend_on_bootsnap? -%> require 'bootsnap/setup' # Speed up boot time by caching expensive operations. +<%- end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt index b383228dc0..a87649b50f 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt @@ -14,7 +14,7 @@ Rails.application.configure do # Enable/disable caching. By default caching is disabled. # Run rails dev:cache to toggle caching. - if Rails.root.join('tmp/caching-dev.txt').exist? + if Rails.root.join('tmp', 'caching-dev.txt').exist? config.action_controller.perform_caching = true config.cache_store = :memory_store @@ -46,6 +46,9 @@ Rails.application.configure do # Raise an error on page load if there are pending migrations. config.active_record.migration_error = :page_load + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + <%- end -%> <%- unless options.skip_sprockets? -%> # Debug mode disables concatenation and preprocessing of assets. diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/content_security_policy.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/content_security_policy.rb.tt index 656ded4069..c82324ae4d 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/content_security_policy.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/content_security_policy.rb.tt @@ -1,17 +1,19 @@ +# Be sure to restart your server when you modify this file. + # Define an application-wide content security policy # For further information see the following documentation # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy -Rails.application.config.content_security_policy do |p| - p.default_src :self, :https - p.font_src :self, :https, :data - p.img_src :self, :https, :data - p.object_src :none - p.script_src :self, :https - p.style_src :self, :https, :unsafe_inline +Rails.application.config.content_security_policy do |policy| + policy.default_src :self, :https + policy.font_src :self, :https, :data + policy.img_src :self, :https, :data + policy.object_src :none + policy.script_src :self, :https, :unsafe_inline + policy.style_src :self, :https, :unsafe_inline # Specify URI for violation reports - # p.report_uri "/csp-violation-report-endpoint" + # policy.report_uri "/csp-violation-report-endpoint" end # Report CSP violations to a specified URI diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_2.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_2.rb.tt index 25dcddb27a..b4ef455802 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_2.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_2.rb.tt @@ -10,7 +10,7 @@ # This is needed for recyclable cache keys. # Rails.application.config.active_record.cache_versioning = true -# Use AES 256 GCM authenticated encryption for encrypted cookies. +# Use AES-256-GCM authenticated encryption for encrypted cookies. # Existing cookies will be converted on read then written with the new scheme. # Rails.application.config.action_dispatch.use_authenticated_cookie_encryption = true @@ -25,3 +25,6 @@ # Store boolean values are in sqlite3 databases as 1 and 0 instead of 't' and # 'f' after migrating old data. # Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true + +# Use SHA-1 instead of MD5 to generate non-sensitive digests, such as the ETag header. +# Rails.application.config.active_support.use_sha1_digests = true diff --git a/railties/lib/rails/generators/rails/credentials/credentials_generator.rb b/railties/lib/rails/generators/rails/credentials/credentials_generator.rb index 01a5b502f9..9103b1122e 100644 --- a/railties/lib/rails/generators/rails/credentials/credentials_generator.rb +++ b/railties/lib/rails/generators/rails/credentials/credentials_generator.rb @@ -36,7 +36,8 @@ module Rails ActiveSupport::EncryptedConfiguration.new( config_path: "config/credentials.yml.enc", key_path: "config/master.key", - env_key: "RAILS_MASTER_KEY" + env_key: "RAILS_MASTER_KEY", + raise_if_missing_key: true ) end diff --git a/railties/lib/rails/generators/rails/encrypted_file/encrypted_file_generator.rb b/railties/lib/rails/generators/rails/encrypted_file/encrypted_file_generator.rb index ddce5f6fe2..4ce2fc1d86 100644 --- a/railties/lib/rails/generators/rails/encrypted_file/encrypted_file_generator.rb +++ b/railties/lib/rails/generators/rails/encrypted_file/encrypted_file_generator.rb @@ -24,7 +24,7 @@ module Rails def add_encrypted_file_silently(file_path, key_path, template = encrypted_file_template) unless File.exist?(file_path) - setup = { content_path: file_path, key_path: key_path, env_key: "RAILS_MASTER_KEY" } + setup = { content_path: file_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true } ActiveSupport::EncryptedFile.new(setup).write(template) end end diff --git a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb index b6c13b41ae..e2e8b18eab 100644 --- a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb +++ b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb @@ -23,7 +23,7 @@ module TestUnit # :nodoc: template template_file, File.join("test/controllers", controller_class_path, "#{controller_file_name}_controller_test.rb") - unless options.api? || options[:system_tests].nil? + if !options.api? && options[:system_tests] template "system_test.rb", File.join("test/system", class_path, "#{file_name.pluralize}_test.rb") end end diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb index d8f361f524..d5c9973c6b 100644 --- a/railties/lib/rails/info.rb +++ b/railties/lib/rails/info.rb @@ -99,7 +99,7 @@ module Rails end property "Database schema version" do - ActiveRecord::Migrator.current_version rescue nil + ActiveRecord::Base.connection.migration_context.current_version rescue nil end end end diff --git a/railties/lib/rails/mailers_controller.rb b/railties/lib/rails/mailers_controller.rb index 66636e5d6b..0b0e802358 100644 --- a/railties/lib/rails/mailers_controller.rb +++ b/railties/lib/rails/mailers_controller.rb @@ -6,9 +6,9 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc: prepend_view_path ActionDispatch::DebugExceptions::RESCUES_TEMPLATE_PATH before_action :require_local!, unless: :show_previews? - before_action :find_preview, only: :preview + before_action :find_preview, :set_locale, only: :preview - helper_method :part_query + helper_method :part_query, :locale_query def index @previews = ActionMailer::Preview.all @@ -84,4 +84,12 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc: def part_query(mime_type) request.query_parameters.merge(part: mime_type).to_query end + + def locale_query(locale) + request.query_parameters.merge(locale: locale).to_query + end + + def set_locale + I18n.locale = params[:locale] || I18n.default_locale + end end diff --git a/railties/lib/rails/railtie/configuration.rb b/railties/lib/rails/railtie/configuration.rb index 48853129bc..70274b948c 100644 --- a/railties/lib/rails/railtie/configuration.rb +++ b/railties/lib/rails/railtie/configuration.rb @@ -55,7 +55,7 @@ module Rails ActiveSupport.on_load(:before_configuration, yield: true, &block) end - # Third configurable block to run. Does not run if +config.cache_classes+ + # Third configurable block to run. Does not run if +config.eager_load+ # set to false. def before_eager_load(&block) ActiveSupport.on_load(:before_eager_load, yield: true, &block) diff --git a/railties/lib/rails/templates/rails/mailers/email.html.erb b/railties/lib/rails/templates/rails/mailers/email.html.erb index 89c1129f90..2a41c29602 100644 --- a/railties/lib/rails/templates/rails/mailers/email.html.erb +++ b/railties/lib/rails/templates/rails/mailers/email.html.erb @@ -95,11 +95,25 @@ </dd> <% end %> + <dt>Format:</dt> <% if @email.multipart? %> <dd> - <select onchange="formatChanged(this);"> - <option <%= request.format == Mime[:html] ? 'selected' : '' %> value="?<%= part_query('text/html') %>">View as HTML email</option> - <option <%= request.format == Mime[:text] ? 'selected' : '' %> value="?<%= part_query('text/plain') %>">View as plain-text email</option> + <select id="part" onchange="refreshBody();"> + <option <%= request.format == Mime[:html] ? 'selected' : '' %> value="<%= part_query('text/html') %>">View as HTML email</option> + <option <%= request.format == Mime[:text] ? 'selected' : '' %> value="<%= part_query('text/plain') %>">View as plain-text email</option> + </select> + </dd> + <% else %> + <dd id="mime_type" data-mime-type="<%= part_query(@email.mime_type) %>"><%= @email.mime_type == 'text/html' ? 'HTML email' : 'plain-text email' %></dd> + <% end %> + + <% if I18n.available_locales.count > 1 %> + <dt>Locale:</dt> + <dd> + <select id="locale" onchange="refreshBody();"> + <% I18n.available_locales.each do |locale| %> + <option <%= I18n.locale == locale ? 'selected' : '' %> value="<%= locale_query(locale) %>"><%= locale %></option> + <% end %> </select> </dd> <% end %> @@ -116,15 +130,27 @@ <% end %> <script> - function formatChanged(form) { - var part_name = form.options[form.selectedIndex].value - var iframe =document.getElementsByName('messageBody')[0]; - iframe.contentWindow.location.replace(part_name); + function refreshBody() { + var part_select = document.querySelector('select#part'); + var locale_select = document.querySelector('select#locale'); + var iframe = document.getElementsByName('messageBody')[0]; + var part_param = part_select ? + part_select.options[part_select.selectedIndex].value : + document.querySelector('#mime_type').dataset.mimeType; + var locale_param = locale_select ? locale_select.options[locale_select.selectedIndex].value : null; + var fresh_location; + if (locale_param) { + fresh_location = '?' + part_param + '&' + locale_param; + } else { + fresh_location = '?' + part_param; + } + iframe.contentWindow.location = fresh_location; if (history.replaceState) { - var url = location.pathname.replace(/\.(txt|html)$/, ''); - var format = /html/.test(part_name) ? '.html' : '.txt'; - window.history.replaceState({}, '', url + format); + var url = location.pathname.replace(/\.(txt|html)$/, ''); + var format = /html/.test(part_param) ? '.html' : '.txt'; + var state_to_replace = locale_param ? (url + format + '?' + locale_param) : (url + format); + window.history.replaceState({}, '', state_to_replace); } } </script> diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb index 732c5c1e1f..76c28ac85e 100644 --- a/railties/lib/rails/test_help.rb +++ b/railties/lib/rails/test_help.rb @@ -29,10 +29,6 @@ if defined?(ActiveRecord::Base) end ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path - - def create_fixtures(*fixture_set_names, &block) - FixtureSet.create_fixtures(ActiveSupport::TestCase.fixture_path, fixture_set_names, {}, &block) - end end # :enddoc: diff --git a/railties/lib/rails/test_unit/reporter.rb b/railties/lib/rails/test_unit/reporter.rb index 7d3164f1eb..28b93cee5a 100644 --- a/railties/lib/rails/test_unit/reporter.rb +++ b/railties/lib/rails/test_unit/reporter.rb @@ -64,11 +64,17 @@ module Rails end def format_line(result) - "%s#%s = %.2f s = %s" % [result.class, result.name, result.time, result.result_code] + klass = result.respond_to?(:klass) ? result.klass : result.class + "%s#%s = %.2f s = %s" % [klass, result.name, result.time, result.result_code] end def format_rerun_snippet(result) - location, line = result.method(result.name).source_location + location, line = if result.respond_to?(:source_location) + result.source_location + else + result.method(result.name).source_location + end + "#{executable} #{relative_path_for(location)}:#{line}" end |