module Railroader

Constants

ASTFile
CONFIG_FILES
Errors_Found_Exit_Code

Exit code returned when errors were found and the –exit-on-error option is set

Missing_Checks_Exit_Code

Exit code returned when user requests non-existent checks

No_App_Found_Exit_Code

Exit code returned when no Rails application is detected

Not_Latest_Version_Exit_Code

Exit code returned when railroader was outdated

Version
Warnings_Found_Exit_Code

This exit code is used when warnings are found and the –exit-on-warn option is set

Public Class Methods

add_external_checks(options) click to toggle source
# File lib/railroader.rb, line 520
def self.add_external_checks options
  options[:additional_checks_path].each do |path|
    Railroader::Checks.initialize_checks path
  end if options[:additional_checks_path]
end
check_for_missing_checks(included_checks, excluded_checks) click to toggle source
# File lib/railroader.rb, line 526
def self.check_for_missing_checks included_checks, excluded_checks
  missing = Railroader::Checks.missing_checks(included_checks || Set.new, excluded_checks || Set.new)

  unless missing.empty?
    raise MissingChecksError, "Could not find specified check#{missing.length > 1 ? 's' : ''}: #{missing.map {|c| "`#{c}`"}.join(', ')}"
  end
end
compare(options) click to toggle source

Compare JSON ouptut from a previous scan and return the diff of the two scans

# File lib/railroader.rb, line 445
def self.compare options
  require 'json'
  require 'railroader/differ'
  raise ArgumentError.new("Comparison file doesn't exist") unless File.exist? options[:previous_results_json]

  begin
    previous_results = JSON.parse(File.read(options[:previous_results_json]), :symbolize_names => true)[:warnings]
  rescue JSON::ParserError
    self.notify "Error parsing comparison file: #{options[:previous_results_json]}"
    exit!
  end

  tracker = run(options)

  new_results = JSON.parse(tracker.report.to_json, :symbolize_names => true)[:warnings]

  Railroader::Differ.new(new_results, previous_results).diff
end
config_file(custom_location, app_path) click to toggle source
# File lib/railroader.rb, line 153
def self.config_file custom_location, app_path
  app_config = File.expand_path(File.join(app_path, "config", "railroader.yml"))
  supported_locations = [File.expand_path(custom_location || ""), app_config] + CONFIG_FILES
  supported_locations.detect {|f| File.file?(f) }
end
debug(message) click to toggle source
# File lib/railroader.rb, line 440
def self.debug message
  $stderr.puts message if @debug
end
debug=(val) click to toggle source
# File lib/railroader.rb, line 534
def self.debug= val
  @debug = val
end
default_options() click to toggle source

Default set of options

# File lib/railroader.rb, line 160
def self.default_options
  { :assume_all_routes => true,
    :check_arguments => true,
    :collapse_mass_assignment => false,
    :combine_locations => true,
    :engine_paths => ["engines/*"],
    :exit_on_error => true,
    :exit_on_warn => true,
    :highlight_user_input => true,
    :html_style => "#{File.expand_path(File.dirname(__FILE__))}/railroader/format/style.css",
    :ignore_model_output => false,
    :ignore_redirect_to_model => true,
    :index_libs => true,
    :message_limit => 100,
    :min_confidence => 2,
    :output_color => true,
    :pager => true,
    :parallel_checks => true,
    :parser_timeout => 10,
    :relative_path => false,
    :report_progress => true,
    :safe_methods => Set.new,
    :skip_checks => Set.new,
  }
end
dump_config(options) click to toggle source

Output configuration to YAML

# File lib/railroader.rb, line 300
def self.dump_config options
  require 'yaml'
  if options[:create_config].is_a? String
    file = options[:create_config]
  else
    file = nil
  end

  options.delete :create_config

  options.each do |k, v|
    if v.is_a? Set
      options[k] = v.to_a
    end
  end

  if file
    File.open file, "w" do |f|
      YAML.dump options, f
    end
    notify "Output configuration to #{file}"
  else
    notify YAML.dump(options)
  end
end
ensure_latest() click to toggle source
# File lib/railroader.rb, line 326
def self.ensure_latest
  current = Railroader::Version
  latest = Gem.latest_version_for('railroader').to_s
  if current != latest
    "Railroader #{current} is not the latest version #{latest}"
  end
end
filter_warnings(tracker, options) click to toggle source
# File lib/railroader.rb, line 490
def self.filter_warnings tracker, options
  require 'railroader/report/ignore/config'

  app_tree = Railroader::AppTree.from_options(options)

  if options[:ignore_file]
    file = options[:ignore_file]
  elsif app_tree.exists? "config/railroader.ignore"
    file = app_tree.expand_path("config/railroader.ignore")
  elsif app_tree.exists? "config/brakeman.ignore"
    file = app_tree.expand_path("config/brakeman.ignore")
  elsif not options[:interactive_ignore]
    return
  end

  notify "Filtering warnings..."

  if options[:interactive_ignore]
    require 'railroader/report/ignore/interactive'
    config = InteractiveIgnorer.new(file, tracker.warnings).start
  else
    notify "[Notice] Using '#{file}' to filter warnings"
    config = IgnoreConfig.new(file, tracker.warnings)
    config.read_from_file
    config.filter_ignored
  end

  tracker.ignored_filter = config
end
get_output_formats(options) click to toggle source

Determine output formats based on options or options

# File lib/railroader.rb, line 188
def self.get_output_formats options
  # Set output format
  if options[:output_format] && options[:output_files] && options[:output_files].size > 1
    raise ArgumentError, "Cannot specify output format if multiple output files specified"
  end
  if options[:output_format]
    get_formats_from_output_format options[:output_format]
  elsif options[:output_files]
    get_formats_from_output_files options[:output_files]
  else
    begin
      self.load_railroader_dependency 'terminal-table', :allow_fail
      return [:to_s]
    rescue LoadError
      return [:to_json]
    end
  end
end
list_checks(options) click to toggle source

Output list of checks (for `-k` option)

# File lib/railroader.rb, line 278
def self.list_checks options
  require 'railroader/scanner'

  add_external_checks options

  if options[:list_optional_checks]
    $stderr.puts "Optional Checks:"
    checks = Checks.optional_checks
  else
    $stderr.puts "Available Checks:"
    checks = Checks.checks
  end

  format_length = 30

  $stderr.puts "-" * format_length
  checks.each do |check|
    $stderr.printf("%-#{format_length}s%s\n", check.name, check.description)
  end
end
load_options(line_options) click to toggle source

Load options from YAML file

# File lib/railroader.rb, line 111
def self.load_options line_options
  custom_location = line_options[:config_file]
  quiet = line_options[:quiet]
  app_path = line_options[:app_path]

  # Load configuration file
  if config = config_file(custom_location, app_path)
    require 'date' # https://github.com/dtao/safe_yaml/issues/80
    self.load_railroader_dependency 'safe_yaml/load'
    options = SafeYAML.load_file config, :deserialize_symbols => true

    if options
      options.each { |k, v| options[k] = Set.new v if v.is_a? Array }

      # After parsing the yaml config file for options, convert any string keys into symbols.
      options.keys.select {|k| k.is_a? String}.map {|k| k.to_sym }.each {|k| options[k] = options[k.to_s]; options.delete(k.to_s) }

      unless line_options[:allow_check_paths_in_config]
        if options.include? :additional_checks_path
          options.delete :additional_checks_path

          notify "[Notice] Ignoring additional check paths in config file. Use --allow-check-paths-in-config to allow" unless (options[:quiet] || quiet)
        end
      end

      # notify if options[:quiet] and quiet is nil||false
      notify "[Notice] Using configuration in #{config}" unless (options[:quiet] || quiet)
      options
    else
      notify "[Notice] Empty configuration file: #{config}" unless quiet
      {}
    end
  else
    {}
  end
end
load_railroader_dependency(name, allow_fail = false) click to toggle source
# File lib/railroader.rb, line 464
def self.load_railroader_dependency name, allow_fail = false
  return if @loaded_dependencies.include? name

  unless @vendored_paths
    path_load = "#{File.expand_path(File.dirname(__FILE__))}/../bundle/load.rb"

    if File.exist? path_load
      require path_load
    end

    @vendored_paths = true
  end

  begin
    require name
  rescue LoadError => e
    if allow_fail
      raise e
    else
      $stderr.puts e.message
      $stderr.puts "Please install the appropriate dependency: #{name}."
      exit!(-1)
    end
  end
end
notify(message) click to toggle source
# File lib/railroader.rb, line 436
def self.notify message
  $stderr.puts message unless @quiet
end
quiet=(val) click to toggle source
# File lib/railroader.rb, line 538
def self.quiet= val
  @quiet = val
end
rescan(tracker, files, options = {}) click to toggle source

Rescan a subset of files in a Rails application.

A full scan must have been run already to use this method. The returned Tracker object from Railroader.run is used as a starting point for the rescan.

Options may be given as a hash with the same values as Railroader.run. Note that these options will be merged into the Tracker.

This method returns a RescanReport object with information about the scan. However, the Tracker object will also be modified as the scan is run.

# File lib/railroader.rb, line 425
def self.rescan tracker, files, options = {}
  require 'railroader/rescanner'

  tracker.options.merge! options

  @quiet = !!tracker.options[:quiet]
  @debug = !!tracker.options[:debug]

  Rescanner.new(tracker.options, tracker.processor, files).recheck
end
run(options) click to toggle source

Run Railroader scan. Returns Tracker object.

Options:

* :app_path - path to root of Rails app (required)
* :additional_checks_path - array of additional directories containing additional out-of-tree checks to run
* :additional_libs_path - array of additional application relative lib directories (ex. app/mailers) to process
* :assume_all_routes - assume all methods are routes (default: true)
* :check_arguments - check arguments of methods (default: true)
* :collapse_mass_assignment - report unprotected models in single warning (default: false)
* :combine_locations - combine warning locations (default: true)
* :config_file - configuration file
* :escape_html - escape HTML by default (automatic)
* :exit_on_error - only affects Commandline module (default: true)
* :exit_on_warn - only affects Commandline module (default: true)
* :github_repo - github repo to use for file links (user/repo[/path][@ref])
* :highlight_user_input - highlight user input in reported warnings (default: true)
* :html_style - path to CSS file
* :ignore_model_output - consider models safe (default: false)
* :index_libs - add libraries to call index (default: true)
* :interprocedural - limited interprocedural processing of method calls (default: false)
* :message_limit - limit length of messages
* :min_confidence - minimum confidence (0-2, 0 is highest)
* :output_files - files for output
* :output_formats - formats for output (:to_s, :to_tabs, :to_csv, :to_html)
* :parallel_checks - run checks in parallel (default: true)
* :parser_timeout - set timeout for parsing an individual file (default: 10 seconds)
* :print_report - if no output file specified, print to stdout (default: false)
* :quiet - suppress most messages (default: true)
* :rails3 - force Rails 3 mode (automatic)
* :report_routes - show found routes on controllers (default: false)
* :run_checks - array of checks to run (run all if not specified)
* :safe_methods - array of methods to consider safe
* :skip_libs - do not process lib/ directory (default: false)
* :skip_checks - checks not to run (run all if not specified)
* :absolute_paths - show absolute path of each file (default: false)
* :summary_only - only output summary section of report for plain/table (:summary_only, :no_summary, true)

Alternatively, just supply a path as a string.

# File lib/railroader.rb, line 67
def self.run options
  options = set_options options

  @quiet = !!options[:quiet]
  @debug = !!options[:debug]

  if @quiet
    options[:report_progress] = false
  end

  scan options
end
scan(options) click to toggle source

Run a scan. Generally called from Railroader.run instead of directly.

# File lib/railroader.rb, line 335
def self.scan options
  # Load scanner
  notify "Loading scanner..."

  begin
    require 'railroader/scanner'
  rescue LoadError
    raise NoRailroaderError, "Cannot find lib/ directory."
  end

  add_external_checks options

  # Start scanning
  scanner = Scanner.new options
  tracker = scanner.tracker

  check_for_missing_checks options[:run_checks], options[:skip_checks]

  notify "Processing application in #{tracker.app_path}"
  scanner.process

  if options[:parallel_checks]
    notify "Running checks in parallel..."
  else
    notify "Runnning checks..."
  end

  tracker.run_checks

  self.filter_warnings tracker, options

  if options[:output_files]
    notify "Generating report..."

    write_report_to_files tracker, options[:output_files]
  elsif options[:print_report]
    notify "Generating report..."

    write_report_to_formats tracker, options[:output_formats]
  end

  tracker
end
set_options(options) click to toggle source

Sets up options for run, checks given application path

# File lib/railroader.rb, line 81
def self.set_options options
  if options.is_a? String
    options = { :app_path => options }
  end

  if options[:quiet] == :command_line
    command_line = true
    options.delete :quiet
  end

  options = default_options.merge(load_options(options)).merge(options)

  if options[:quiet].nil? and not command_line
    options[:quiet] = true
  end

  if options[:rails4]
    options[:rails3] = true
  elsif options[:rails5]
    options[:rails3] = true
    options[:rails4] = true
  end

  options[:output_formats] = get_output_formats options
  options[:github_url] = get_github_url options

  options
end

Private Class Methods

get_formats_from_output_files(output_files) click to toggle source
# File lib/railroader.rb, line 233
def self.get_formats_from_output_files output_files
  output_files.map do |output_file|
    case output_file
    when /\.html$/i
      :to_html
    when /\.csv$/i
      :to_csv
    when /\.pdf$/i
      :to_pdf
    when /\.tabs$/i
      :to_tabs
    when /\.json$/i
      :to_json
    when /\.md$/i
      :to_markdown
    when /(\.cc|\.codeclimate)$/i
      :to_codeclimate
    when /\.plain$/i
      :to_text
    when /\.table$/i
      :to_table
    else
      :to_text
    end
  end
end
get_formats_from_output_format(output_format) click to toggle source
# File lib/railroader.rb, line 207
def self.get_formats_from_output_format output_format
  case output_format
  when :html, :to_html
    [:to_html]
  when :csv, :to_csv
    [:to_csv]
  when :pdf, :to_pdf
    [:to_pdf]
  when :tabs, :to_tabs
    [:to_tabs]
  when :json, :to_json
    [:to_json]
  when :markdown, :to_markdown
    [:to_markdown]
  when :cc, :to_cc, :codeclimate, :to_codeclimate
    [:to_codeclimate]
  when :plain, :to_plain, :text, :to_text, :to_s
    [:to_text]
  when :table, :to_table
    [:to_table]
  else
    [:to_text]
  end
end
get_github_url(options) click to toggle source
# File lib/railroader.rb, line 261
def self.get_github_url options
  if github_repo = options[:github_repo]
    full_repo, ref = github_repo.split '@', 2
    name, repo, path = full_repo.split '/', 3
    unless name && repo && !(name.empty? || repo.empty?)
      raise ArgumentError, "Invalid GitHub repository format"
    end
    path.chomp '/' if path
    ref ||= 'master'
    ['https://github.com', name, repo, 'blob', ref, path].compact.join '/'
  else
    nil
  end
end
write_report_to_files(tracker, output_files) click to toggle source
# File lib/railroader.rb, line 379
def self.write_report_to_files tracker, output_files
  require 'fileutils'
  tracker.options[:output_color] = false unless tracker.options[:output_color] == :force

  output_files.each_with_index do |output_file, idx|
    dir = File.dirname(output_file)
    unless Dir.exist? dir
      FileUtils.mkdir_p(dir)
    end

    File.open output_file, "w" do |f|
      f.write tracker.report.format(tracker.options[:output_formats][idx])
    end
    notify "Report saved in '#{output_file}'"
  end
end
write_report_to_formats(tracker, output_formats) click to toggle source
# File lib/railroader.rb, line 397
def self.write_report_to_formats tracker, output_formats
  unless $stdout.tty? or tracker.options[:output_color] == :force
    tracker.options[:output_color] = false
  end

  if not $stdout.tty? or not tracker.options[:pager] or output_formats.length > 1 # does this ever happen??
    output_formats.each do |output_format|
      puts tracker.report.format(output_format)
    end
  else
    require "railroader/report/pager"

    Railroader::Pager.new(tracker).page_report(tracker.report, output_formats.first)
  end
end