Class: Necropsy::Configuration

Inherits:
Object
  • Object
show all
Defined in:
lib/necropsy/configuration.rb

Constant Summary collapse

DEFAULT_FAIL_ON =
:high
DEFAULT_BASELINE =
'.necropsy_baseline.yml'
TOP_LEVEL_KEYS =
%w[
  analyzers frameworks entry_points ci quarantine bench cache rta resolution paths report logging implicit_callers
].freeze
NESTED_KEYS =
{
  'analyzers' => %w[static dynamic custom],
  'analyzers.dynamic' => %w[coverage coverband trace_point],
  'entry_points' => %w[extra],
  'ci' => %w[baseline fail_on],
  'quarantine' => %w[days],
  'bench' => %w[precision_threshold recall_threshold],
  'cache' => %w[enabled path],
  'rta' => %w[factory_methods],
  'resolution' => %w[ambiguity_limit],
  'paths' => %w[include exclude],
  'report' => %w[include exclude],
  'logging' => %w[verbose]
}.freeze
DYNAMIC_KEYS =
%w[source min_observation_days keys key pattern connect_timeout read_timeout].freeze
IMPLICIT_CALLER_KEYS =
%w[name_pattern owner_ancestors reason].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(root:, path:, data:) ⇒ Configuration

Returns a new instance of Configuration.



39
40
41
42
43
44
# File 'lib/necropsy/configuration.rb', line 39

def initialize(root:, path:, data:)
  @root = File.expand_path(root)
  @path = path
  @data = stringify_keys(data)
  validate!
end

Instance Attribute Details

#dataObject (readonly)

Returns the value of attribute data.



29
30
31
# File 'lib/necropsy/configuration.rb', line 29

def data
  @data
end

#pathObject (readonly)

Returns the value of attribute path.



29
30
31
# File 'lib/necropsy/configuration.rb', line 29

def path
  @path
end

#rootObject (readonly)

Returns the value of attribute root.



29
30
31
# File 'lib/necropsy/configuration.rb', line 29

def root
  @root
end

Class Method Details

.load(root:, path: nil) ⇒ Object



31
32
33
34
35
36
37
# File 'lib/necropsy/configuration.rb', line 31

def self.load(root:, path: nil)
  config_path = path ? File.expand_path(path, root) : File.join(root, '.necropsy.yml')
  data = File.exist?(config_path) ? YAML.safe_load_file(config_path, permitted_classes: [Symbol], aliases: true) : {}
  new(root: root, path: config_path, data: data || {})
rescue Psych::Exception => e
  raise Error, "Could not parse configuration #{config_path}: #{e.message}"
end

Instance Method Details

#ambiguity_limitObject



120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/necropsy/configuration.rb', line 120

def ambiguity_limit
  value = fetch('resolution', 'ambiguity_limit')
  return 4 if value.nil?
  return Float::INFINITY if value.to_s == 'unlimited'

  limit = Integer(value)
  return limit if limit.positive?

  raise Error, 'resolution.ambiguity_limit must be a positive integer or unlimited'
rescue ArgumentError, TypeError
  raise Error, 'resolution.ambiguity_limit must be a positive integer or unlimited'
end

#baseline_pathObject



73
74
75
# File 'lib/necropsy/configuration.rb', line 73

def baseline_path
  fetch('ci', 'baseline') || DEFAULT_BASELINE
end

#bench_precision_thresholdObject



94
95
96
# File 'lib/necropsy/configuration.rb', line 94

def bench_precision_threshold
  (fetch('bench', 'precision_threshold') || 0.85).to_f
end

#bench_recall_thresholdObject



98
99
100
101
# File 'lib/necropsy/configuration.rb', line 98

def bench_recall_threshold
  value = fetch('bench', 'recall_threshold')
  value&.to_f
end

#cache_enabled?Boolean

Returns:

  • (Boolean)


103
104
105
106
# File 'lib/necropsy/configuration.rb', line 103

def cache_enabled?
  value = fetch('cache', 'enabled')
  value.nil? || value != false
end

#cache_pathObject



108
109
110
# File 'lib/necropsy/configuration.rb', line 108

def cache_path
  fetch('cache', 'path') || '.necropsy_cache/scan.json'
end

#custom_analyzersObject



54
55
56
# File 'lib/necropsy/configuration.rb', line 54

def custom_analyzers
  Array(fetch('analyzers', 'custom')).compact
end

#dynamic_config(name) ⇒ Object



50
51
52
# File 'lib/necropsy/configuration.rb', line 50

def dynamic_config(name)
  fetch('analyzers', 'dynamic', name.to_s) || {}
end

#entry_point_patternsObject



58
59
60
# File 'lib/necropsy/configuration.rb', line 58

def entry_point_patterns
  Array(fetch('entry_points', 'extra')).compact.map(&:to_s)
end

#exclude_pathsObject



137
138
139
# File 'lib/necropsy/configuration.rb', line 137

def exclude_paths
  Array(fetch('paths', 'exclude')).map(&:to_s)
end

#factory_methodsObject



116
117
118
# File 'lib/necropsy/configuration.rb', line 116

def factory_methods
  Array(fetch('rta', 'factory_methods') || %w[build create build_stubbed]).map(&:to_s)
end

#fail_onObject

Raises:



77
78
79
80
81
82
# File 'lib/necropsy/configuration.rb', line 77

def fail_on
  level = (fetch('ci', 'fail_on') || DEFAULT_FAIL_ON).to_sym
  return level if CONFIDENCE_LEVELS.key?(level)

  raise Error, "Invalid ci.fail_on confidence level: #{level}"
end

#frameworksObject



62
63
64
# File 'lib/necropsy/configuration.rb', line 62

def frameworks
  Array(data['frameworks']).map(&:to_s)
end

#implicit_callersObject



149
150
151
152
153
154
155
156
157
# File 'lib/necropsy/configuration.rb', line 149

def implicit_callers
  @implicit_callers ||= Array(data['implicit_callers']).map do |entry|
    {
      name_pattern: Regexp.new(entry.fetch('name_pattern')),
      owner_ancestors: Array(entry['owner_ancestors']).map(&:to_s),
      reason: entry['reason']&.to_s
    }
  end
end

#include_pathsObject



133
134
135
# File 'lib/necropsy/configuration.rb', line 133

def include_paths
  Array(fetch('paths', 'include')).map(&:to_s)
end

#min_observation_daysObject



84
85
86
87
88
# File 'lib/necropsy/configuration.rb', line 84

def min_observation_days
  coverage_days = fetch('analyzers', 'dynamic', 'coverage', 'min_observation_days')
  coverband_days = fetch('analyzers', 'dynamic', 'coverband', 'min_observation_days')
  (coverage_days || coverband_days || 30).to_i
end

#quarantine_daysObject



90
91
92
# File 'lib/necropsy/configuration.rb', line 90

def quarantine_days
  (fetch('quarantine', 'days') || 30).to_i
end

#rails_enabled?Boolean

Returns:

  • (Boolean)


66
67
68
69
70
71
# File 'lib/necropsy/configuration.rb', line 66

def rails_enabled?
  return true if frameworks.include?('rails')

  gemfiles = [File.join(root, 'Gemfile.lock'), File.join(root, 'Gemfile')]
  gemfiles.any? { |file| File.exist?(file) && File.read(file).match?(/(?:^|\s)rails(?:\s|\z|,)/) }
end

#report_exclude_pathsObject



145
146
147
# File 'lib/necropsy/configuration.rb', line 145

def report_exclude_paths
  Array(fetch('report', 'exclude')).map(&:to_s)
end

#report_include_pathsObject



141
142
143
# File 'lib/necropsy/configuration.rb', line 141

def report_include_paths
  Array(fetch('report', 'include')).map(&:to_s)
end

#scan_cache_keyObject



112
113
114
# File 'lib/necropsy/configuration.rb', line 112

def scan_cache_key
  data.except('cache', 'report')
end

#static_analyzersObject



46
47
48
# File 'lib/necropsy/configuration.rb', line 46

def static_analyzers
  Array(fetch('analyzers', 'static') || %w[name_resolution cha rta]).map(&:to_s)
end

#verbose?Boolean

Returns:

  • (Boolean)


159
160
161
# File 'lib/necropsy/configuration.rb', line 159

def verbose?
  fetch('logging', 'verbose') == true
end