spektr 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yaml +32 -0
  3. data/.gitignore +9 -0
  4. data/.travis.yml +6 -0
  5. data/CHANGELOG.md +3 -0
  6. data/CODE_OF_CONDUCT.md +74 -0
  7. data/Gemfile +4 -0
  8. data/Gemfile.lock +134 -0
  9. data/Guardfile +45 -0
  10. data/LICENSE.txt +27 -0
  11. data/README.md +70 -0
  12. data/Rakefile +10 -0
  13. data/bin/console +14 -0
  14. data/bin/setup +8 -0
  15. data/bin/spektr +7 -0
  16. data/lib/spektr/app.rb +209 -0
  17. data/lib/spektr/checks/base.rb +151 -0
  18. data/lib/spektr/checks/basic_auth.rb +27 -0
  19. data/lib/spektr/checks/basic_auth_timing.rb +24 -0
  20. data/lib/spektr/checks/command_injection.rb +48 -0
  21. data/lib/spektr/checks/content_tag_xss.rb +54 -0
  22. data/lib/spektr/checks/cookie_serialization.rb +21 -0
  23. data/lib/spektr/checks/create_with.rb +27 -0
  24. data/lib/spektr/checks/csrf.rb +25 -0
  25. data/lib/spektr/checks/csrf_setting.rb +39 -0
  26. data/lib/spektr/checks/default_routes.rb +43 -0
  27. data/lib/spektr/checks/deserialize.rb +62 -0
  28. data/lib/spektr/checks/detailed_exceptions.rb +29 -0
  29. data/lib/spektr/checks/digest_dos.rb +28 -0
  30. data/lib/spektr/checks/dynamic_finders.rb +26 -0
  31. data/lib/spektr/checks/evaluation.rb +25 -0
  32. data/lib/spektr/checks/file_access.rb +38 -0
  33. data/lib/spektr/checks/file_disclosure.rb +25 -0
  34. data/lib/spektr/checks/filter_skipping.rb +29 -0
  35. data/lib/spektr/checks/header_dos.rb +20 -0
  36. data/lib/spektr/checks/i18n_xss.rb +20 -0
  37. data/lib/spektr/checks/json_encoding.rb +23 -0
  38. data/lib/spektr/checks/json_entity_escape.rb +30 -0
  39. data/lib/spektr/checks/json_parsing.rb +47 -0
  40. data/lib/spektr/checks/link_to_href.rb +35 -0
  41. data/lib/spektr/checks/mass_assignment.rb +42 -0
  42. data/lib/spektr/checks/send.rb +24 -0
  43. data/lib/spektr/checks/sqli.rb +52 -0
  44. data/lib/spektr/checks/xss.rb +49 -0
  45. data/lib/spektr/checks.rb +9 -0
  46. data/lib/spektr/cli.rb +53 -0
  47. data/lib/spektr/erubi.rb +78 -0
  48. data/lib/spektr/exp/assignment.rb +20 -0
  49. data/lib/spektr/exp/base.rb +32 -0
  50. data/lib/spektr/exp/const.rb +7 -0
  51. data/lib/spektr/exp/definition.rb +32 -0
  52. data/lib/spektr/exp/ivasign.rb +7 -0
  53. data/lib/spektr/exp/lvasign.rb +7 -0
  54. data/lib/spektr/exp/send.rb +135 -0
  55. data/lib/spektr/exp/xstr.rb +12 -0
  56. data/lib/spektr/processors/base.rb +80 -0
  57. data/lib/spektr/processors/class_processor.rb +25 -0
  58. data/lib/spektr/targets/base.rb +119 -0
  59. data/lib/spektr/targets/config.rb +6 -0
  60. data/lib/spektr/targets/controller.rb +74 -0
  61. data/lib/spektr/targets/model.rb +6 -0
  62. data/lib/spektr/targets/routes.rb +38 -0
  63. data/lib/spektr/targets/view.rb +34 -0
  64. data/lib/spektr/version.rb +3 -0
  65. data/lib/spektr/warning.rb +23 -0
  66. data/lib/spektr.rb +120 -0
  67. data/spektr.gemspec +49 -0
  68. metadata +362 -0
@@ -0,0 +1,25 @@
1
+ module Spektr
2
+ class Checks
3
+ class FileDisclosure < Base
4
+
5
+ def initialize(app, target)
6
+ super
7
+ @name = "File existence disclosure"
8
+ @type = "Information Disclosure"
9
+ @targets = ["Spektr::Targets::Base"]
10
+ end
11
+
12
+ def run
13
+ return unless super
14
+ config = @app.production_config.find_calls(:serve_static_assets=).first
15
+ if config && config.arguments.first.type == :true
16
+ warn! "root", self, nil, "File existence disclosure vulnerability"
17
+ end
18
+ end
19
+
20
+ def should_run?
21
+ app_version_between?("2.0.0", "2.3.18") || app_version_between?("3.0.0", "3.2.20") || app_version_between?("4.0.0", "4.0.11") || app_version_between?("4.1.0", "4.1.7")
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ module Spektr
2
+ class Checks
3
+ class FilterSkipping < Base
4
+ def initialize(app, target)
5
+ super
6
+ @name = "Default routes filter skipping"
7
+ @type = "Default Routes"
8
+ @targets = ["Spektr::Targets::Routes"]
9
+ end
10
+
11
+ def run
12
+ return unless super
13
+ calls = %w{ match get post put delete }.inject([]) do |memo, method|
14
+ memo.concat @target.find_calls(method.to_sym)
15
+ memo
16
+ end
17
+ calls.each do |call|
18
+ if !call.arguments.empty? && (call.arguments.first.name.include?(":action") or call.arguments.first.name.include?("*action"))
19
+ warn! @target, self, call.location, "CVE-2011-2929 Rails versions before 3.0.10 have a vulnerability which allows filters to be bypassed"
20
+ end
21
+ end
22
+ end
23
+
24
+ def should_run?
25
+ app_version_between?("3.0.0", "3.0.9")
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,20 @@
1
+ module Spektr
2
+ class Checks
3
+ class HeaderDos < Base
4
+
5
+ def initialize(app, target)
6
+ super
7
+ @name = "HTTP MIME type header DoS (CVE-2013-6414)"
8
+ @type = "Denial of Service"
9
+ @targets = ["Spektr::Targets::Base"]
10
+ end
11
+
12
+ def run
13
+ return unless super
14
+ if app_version_between?("3.0.0", "3.2.15")
15
+ warn! "root", self, nil, "CVE_2013_6414"
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ module Spektr
2
+ class Checks
3
+ class I18nXss < Base
4
+
5
+ def initialize(app, target)
6
+ super
7
+ @name = "XSS in i18n (CVE-2013-4491)"
8
+ @type = "Cross-Site Scripting"
9
+ @targets = ["Spektr::Targets::Base"]
10
+ end
11
+
12
+ def run
13
+ return unless super
14
+ if app_version_between?("3.0.6", "3.2.15") || app_version_between?("4.0.0", "4.0.1")
15
+ warn! "root", self, nil, "I18n has a Cross-Site Scripting vulnerability (CVE_2013_4491)"
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,23 @@
1
+ module Spektr
2
+ class Checks
3
+ class JsonEncoding < Base
4
+ def initialize(app, target)
5
+ super
6
+ @name = "XSS by missing JSON encoding"
7
+ @type = "Cross-Site Scripting"
8
+ @targets = ["Spektr::Targets::Base", "Spektr::Targets::Controller", "Spektr::Targets::Routes", "Spektr::Targets::View"]
9
+ end
10
+
11
+ def run
12
+ return unless super
13
+ if app_version_between?("4.1.0", "4.1.10") || app_version_between?("4.2.0", "4.2.1")
14
+ if calls = @target.find_calls(:to_json).any? || calls = @target.find_calls(:encode).any?
15
+ warn! @target, self, calls.first.location, "Cross-Site Scripting CVE_2015_3226"
16
+ else
17
+ warn! "root", self, nil, "Cross-Site Scripting CVE_2015_3226"
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,30 @@
1
+ module Spektr
2
+ class Checks
3
+ class JsonEntityEscape < Base
4
+
5
+ def initialize(app, target)
6
+ super
7
+ @name = "HTML escaping is disabled for JSON output"
8
+ @type = "Cross-Site Scripting"
9
+ @targets = ["Spektr::Targets::Config", "Spektr::Targets::Base"]
10
+ end
11
+
12
+ def run
13
+ return unless super
14
+ if @app.production_config
15
+ config = @app.production_config.find_calls(:escape_html_entities_in_json=).first
16
+ end
17
+ if config and config.receiver.expanded == "config.active_support" && config.arguments.first.type == :false
18
+ warn! @app.production_config.path, self, nil, "HTML entities in JSON are not escaped by default"
19
+ end
20
+ ['ActiveSupport', 'ActiveSupport.JSON.Encoding'].each do |receiver|
21
+ calls = @target.find_calls(:escape_html_entities_in_json=, receiver)
22
+ if calls.any?
23
+ warn! @target, self, calls.first.location, "HTML entities in JSON are not escaped by default"
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
@@ -0,0 +1,47 @@
1
+ module Spektr
2
+ class Checks
3
+ class JsonParsing < Base
4
+ def initialize(app, target)
5
+ super
6
+ @name = "JSON parsing vulnerability"
7
+ @type = "Remote Code Execution"
8
+ @targets = ["Spektr::Targets::Base"]
9
+ end
10
+
11
+ def run
12
+ return unless super
13
+ check_cve_2013_0333
14
+ check_cve_2013_0269
15
+ end
16
+
17
+ def check_cve_2013_0333
18
+ return unless app_version_between?("0.0.0", "2.3.15") || app_version_between?("3.0.0", "3.0.19")
19
+ if @app.has_gem?("yajl")
20
+ warn! "root", self, nil, "Remote Code Execution CVE_2013_0333"
21
+ end
22
+ uses_json_gem?
23
+ end
24
+
25
+ def uses_json_gem?
26
+ @target.find_calls(:backend=).each do |call|
27
+ if call.receiver.expanded == "ActiveSupport.JSON" && call.arguments.first&.name == :JSONGem
28
+ warn! @target, self, call.location, "Remote Code Execution CVE_2013_0333"
29
+ end
30
+ end
31
+ end
32
+
33
+ def check_cve_2013_0269
34
+ ["json", "json_pure"].each do |gem_name|
35
+ if g = @app.gem_specs&.find { |g| g.name == gem_name }
36
+ if version_between?("1.7.0", "1.7.6", g.version)
37
+ warn! "Gemfile", self, nil, "Unsafe Object Creation Vulnerability in the #{g.name} gem"
38
+ end
39
+ if version_between?("0", "1.5.4", g.version) || version_between?("1.6.0", "1.6.7", g.version)
40
+ warn! "Gemfile", self, nil, "Unsafe Object Creation Vulnerability in the #{g.name} gem"
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,35 @@
1
+ module Spektr
2
+ class Checks
3
+ class LinkToHref < Base
4
+ def initialize(app, target)
5
+ super
6
+ @name = "XSS in href param of link_to"
7
+ @type = "Cross-Site Scripting"
8
+ @targets = ["Spektr::Targets::Base", "Spektr::Targets::Controller", "Spektr::Targets::View"]
9
+ end
10
+
11
+ # TODO: check for user supplied model attributes too
12
+ def run
13
+ return unless super
14
+ block_locations = []
15
+ @target.find_calls_with_block(:link_to).each do |call|
16
+ block_locations << call.location
17
+ next unless call.arguments.first
18
+ ::Spektr.logger.debug "#{@target.path} #{call.location.line} #{call.arguments.first.inspect}"
19
+ if user_input? call.arguments.first.type, call.arguments.first.name, call.arguments.first.ast, call.arguments.first
20
+ warn! @target, self, call.location, "Cross-Site Scripting: Unsafe user supplied value in link_to"
21
+ end
22
+ end
23
+
24
+ @target.find_calls(:link_to).each do |call|
25
+ next if block_locations.include? call.location
26
+ ::Spektr.logger.debug "#{@target.path} #{call.location.line} #{call.arguments[1].inspect}"
27
+ next unless call.arguments[1] || call.arguments[1]&.name =~ /_url$|_path$/
28
+ if user_input? call.arguments[1].type, call.arguments[1].name, call.arguments[1].ast, call.arguments[1]
29
+ warn! @target, self, call.location, "Cross-Site Scripting: Unsafe user supplied value in link_to"
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,42 @@
1
+ module Spektr
2
+ class Checks
3
+ class MassAssignment < Base
4
+
5
+ # TODO: Make this better
6
+ def initialize(app, target)
7
+ super
8
+ @name = "Mass Assignment"
9
+ @type = "Input Validation"
10
+ @targets = ["Spektr::Targets::Controller"]
11
+ end
12
+
13
+ def run
14
+ return unless super
15
+ model_names = @app.models.collect(&:name)
16
+ calls = []
17
+ model_names.each do |receiver|
18
+ [:new, :build, :create].each do |method|
19
+ calls.concat @target.find_calls(method, receiver)
20
+ end
21
+ end
22
+ calls.each do |call|
23
+ argument = call.arguments.first
24
+ next if argument.nil?
25
+ ::Spektr.logger.debug "Mass assignment check at #{call.location.line}"
26
+ if user_input?(argument.type, argument.name, call.ast)
27
+ # we check for permit! separately
28
+ next if argument.ast.children[1] == :permit!
29
+ # check for permit with arguments
30
+ next if argument.ast.children[1] == :permit && argument.ast.children[2]
31
+ warn! @target, self, call.location, "Mass assignment"
32
+ end
33
+ end
34
+ @target.find_calls(:permit!).each do |call|
35
+ if call.arguments.none?
36
+ warn! @target, self, call.location, "permit! allows any keys, use it with caution!", :medium
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,24 @@
1
+ module Spektr
2
+ class Checks
3
+ class Send < Base
4
+ def initialize(app, target)
5
+ super
6
+ @name = "Dangerous send"
7
+ @type = "Dangerous send"
8
+ @targets = ["Spektr::Targets::Base", "Spektr::Targets::Model", "Spektr::Targets::Controller", "Spektr::Targets::Routes", "Spektr::Targets::View"]
9
+ end
10
+
11
+ def run
12
+ return unless super
13
+ [:send, :try, :__send__, :public_send].each do |method|
14
+ @target.find_calls(method).each do |call|
15
+ argument = call.arguments.first
16
+ if user_input?(argument.type, argument.name, argument.ast)
17
+ warn! @target, self, call.location, "User supplied value in send"
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,52 @@
1
+ module Spektr
2
+ class Checks
3
+ class Sqli < Base
4
+ def initialize(app, target)
5
+ super
6
+ @name = "SQL Injection"
7
+ @name = "SQL Injection"
8
+ @targets = ["Spektr::Targets::Base", "Spektr::Targets::Controller", "Spektr::Targets::Model"]
9
+ end
10
+
11
+ def run
12
+ return unless super
13
+
14
+ [
15
+ :average, :count, :maximum, :minimum, :sum, :exists?,
16
+ :find_by, :find_by!, :find_or_create_by, :find_or_create_by!,
17
+ :find_or_initialize_by, :from, :group, :having, :join, :lock,
18
+ :where, :not, :select, :rewhere, :reselect, :update_all
19
+
20
+ ].each do |m|
21
+ @target.find_calls(m).each do |call|
22
+ check_argument(call.arguments.first, m, call)
23
+ end
24
+ end
25
+ [:calculate].each do |m|
26
+ @target.find_calls(m).each do |call|
27
+ check_argument(call.arguments[1], m, call)
28
+ end
29
+ end
30
+
31
+ [:delete_by, :destroy_by].each do |m|
32
+ @target.find_calls(m).each do |call|
33
+ if call.arguments.first
34
+ check_argument(call.arguments.first, m, call)
35
+ end
36
+ call.options.values.each do |option|
37
+ check_argument(@target.ast_to_exp(option.key), m, call)
38
+ check_argument(@target.ast_to_exp(option.value), m, call)
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ def check_argument(argument, method, call)
45
+ return if argument.nil?
46
+ if user_input?(argument.type, argument.name, argument.ast, argument)
47
+ warn! @target, self, call.location, "Possible SQL Injection at #{method}"
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,49 @@
1
+ module Spektr
2
+ class Checks
3
+ class Xss < Base
4
+ def initialize(app, target)
5
+ super
6
+ @name = "XSS"
7
+ @type = "Cross-Site Scripting"
8
+ @targets = ["Spektr::Targets::Base", "Spektr::Targets::Controller", "Spektr::Targets::View"]
9
+ end
10
+
11
+ # TODO: tests for haml, xml, js
12
+ # TODO: add check for raw calls
13
+ def run
14
+ return unless super
15
+ calls = @target.find_calls(:safe_expr_append=)
16
+ calls.concat(@target.find_calls(:raw))
17
+ calls.each do |call|
18
+ call.arguments.each do |argument|
19
+ if user_input?(argument.type, argument.name, argument.ast)
20
+ warn! @target, self, call.location, "Cross-Site Scripting: Unescaped user input"
21
+ end
22
+ if model_attribute?(argument)
23
+ warn! @target, self, call.location, "Cross-Site Scripting: Unescaped model attribute #{argument.name}"
24
+ end
25
+ end
26
+ end
27
+ calls.each do |call|
28
+ call.arguments.each do |argument|
29
+ if user_input?(argument.type, argument.name, argument.ast)
30
+ warn! @target, self, call.location, "Cross-Site Scripting: Unescaped user input"
31
+ end
32
+ if model_attribute?(argument)
33
+ warn! @target, self, call.location, "Cross-Site Scripting: Unescaped model attribute #{argument.name}"
34
+ end
35
+ end
36
+ end
37
+ calls = @target.find_calls(:html_safe)
38
+ calls.each do |call|
39
+ if user_input?(call.receiver.type, call.receiver.name, call.receiver.ast, call.receiver)
40
+ warn! @target, self, call.location, "Cross-Site Scripting: Unescaped user input"
41
+ end
42
+ if model_attribute?(call.receiver)
43
+ warn! @target, self, call.location, "Cross-Site Scripting: Unescaped model attribute #{call.receiver.name}"
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,9 @@
1
+ module Spektr
2
+ class Checks
3
+ def self.load(only = false)
4
+ Checks.constants.select do |c|
5
+ Checks.const_get(c).is_a?(Class) && (!only || only && only.to_s == c.to_s)
6
+ end.map { |c| Checks.const_get(c) }
7
+ end
8
+ end
9
+ end
data/lib/spektr/cli.rb ADDED
@@ -0,0 +1,53 @@
1
+ require 'tty/option'
2
+ require 'json'
3
+ module Spektr
4
+ class Cli
5
+ include TTY::Option
6
+ usage do
7
+ program 'Spektr'
8
+ command 'scan'
9
+ desc 'Find vulnerabilities in ruby code'
10
+ end
11
+
12
+ argument :root do
13
+ optional
14
+ desc 'Path to application root'
15
+ end
16
+
17
+ flag :output_format do
18
+ long '--output_format string'
19
+ desc 'output format terminal or json'
20
+ default 'terminal'
21
+ end
22
+
23
+ flag :check do
24
+ long '--check string'
25
+ desc 'run this single check'
26
+ end
27
+
28
+ flag :debug do
29
+ long '--debug'
30
+ short '-d'
31
+ desc 'output debug logs to STDOUT'
32
+ end
33
+
34
+ flag :help do
35
+ short '-h'
36
+ long '--help'
37
+ desc 'Print usage'
38
+ end
39
+
40
+ def scan
41
+ if params[:help]
42
+ print help
43
+ exit
44
+ else
45
+ report = Spektr.run(params[:root], params[:output_format], params[:debug], params[:check])
46
+ case params[:output_format]
47
+ when 'json'
48
+ puts JSON.pretty_generate report
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,78 @@
1
+ require "erubi"
2
+ # This is a copy of module ActionView::Template::Handlers::ERB::Erubi
3
+ module Spektr
4
+ class Erubi < ::Erubi::Engine
5
+ def initialize(input, properties = {})
6
+ @newline_pending = 0
7
+
8
+ # Dup properties so that we don't modify argument
9
+ properties = Hash[properties]
10
+ properties[:preamble] = ""
11
+ properties[:postamble] = "@output_buffer.to_s"
12
+ properties[:bufvar] = "@output_buffer"
13
+ properties[:escapefunc] = ""
14
+
15
+ super
16
+ end
17
+
18
+ def evaluate(action_view_erb_handler_context)
19
+ src = @src
20
+ view = Class.new(ActionView::Base) {
21
+ include action_view_erb_handler_context._routes.url_helpers
22
+ class_eval("define_method(:_template) { |local_assigns, output_buffer| #{src} }", defined?(@filename) ? @filename : "(erubi)", 0)
23
+ }.empty
24
+ view._run(:_template, nil, {}, ActionView::OutputBuffer.new)
25
+ end
26
+
27
+ private
28
+ def add_text(text)
29
+ return if text.empty?
30
+
31
+ if text == "\n"
32
+ @newline_pending += 1
33
+ else
34
+ src << "@output_buffer.safe_append='"
35
+ src << "\n" * @newline_pending if @newline_pending > 0
36
+ src << text.gsub(/['\\]/, '\\\\\&')
37
+ src << "'.freeze;"
38
+
39
+ @newline_pending = 0
40
+ end
41
+ end
42
+
43
+ BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
44
+
45
+ def add_expression(indicator, code)
46
+ flush_newline_if_pending(src)
47
+
48
+ if (indicator == "==") || @escape
49
+ src << "@output_buffer.safe_expr_append="
50
+ else
51
+ src << "@output_buffer.append="
52
+ end
53
+
54
+ if BLOCK_EXPR.match?(code)
55
+ src << " " << code
56
+ else
57
+ src << "(" << code << ");"
58
+ end
59
+ end
60
+
61
+ def add_code(code)
62
+ flush_newline_if_pending(src)
63
+ super
64
+ end
65
+
66
+ def add_postamble(_)
67
+ flush_newline_if_pending(src)
68
+ super
69
+ end
70
+
71
+ def flush_newline_if_pending(src)
72
+ if @newline_pending > 0
73
+ src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;"
74
+ @newline_pending = 0
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,20 @@
1
+ module Spektr
2
+ module Exp
3
+ module Assignment
4
+ def user_input?
5
+ if ast.children[1].type == :send
6
+ _send = Send.new(ast.children[1])
7
+ name = if _send.receiver && _send.receiver.type == :send
8
+ _send.receiver.name
9
+ else
10
+ nil
11
+ end
12
+ if [:params, :cookies, :request].include? name
13
+ return true
14
+ end
15
+ end
16
+ false
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,32 @@
1
+ module Spektr
2
+ module Exp
3
+ class Base
4
+ attr_accessor :ast, :name, :type, :options, :arguments, :body, :location
5
+
6
+ def initialize(ast)
7
+ @ast = ast
8
+ @type = ast.type
9
+ @location = ast.location
10
+ @name = ast.children.first
11
+ @options = {}
12
+ @arguments = []
13
+ @body = []
14
+ end
15
+
16
+ def send?
17
+ is_a? Send
18
+ end
19
+
20
+ include AST::Processor::Mixin
21
+
22
+ def process(ast)
23
+ return unless ast.respond_to?(:to_ast)
24
+ super
25
+ end
26
+
27
+ def handler_missing(node)
28
+ # puts "handler missing for #{node.type}"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,7 @@
1
+ module Spektr
2
+ module Exp
3
+ class Const < Base
4
+ include Spektr::Exp::Assignment
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,32 @@
1
+ module Spektr
2
+ module Exp
3
+ class Definition < Base
4
+ attr_accessor :private, :protected
5
+
6
+ def initialize(ast)
7
+ super
8
+ process @ast.children
9
+ end
10
+
11
+ def process(ast)
12
+ ast&.each do |node|
13
+ next unless Parser::AST::Node === node
14
+ case node.type
15
+ when :args
16
+ node.children.each do |argument|
17
+ @arguments << argument.children.first
18
+ end
19
+ when :begin
20
+ process(node.children)
21
+ when :lvasgn
22
+ @body << Lvasign.new(node)
23
+ when :ivasgn
24
+ @body << Ivasign.new(node)
25
+ when :send
26
+ @body << Send.new(node)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,7 @@
1
+ module Spektr
2
+ module Exp
3
+ class Ivasign < Base
4
+ include Spektr::Exp::Assignment
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Spektr
2
+ module Exp
3
+ class Lvasign < Base
4
+ include Spektr::Exp::Assignment
5
+ end
6
+ end
7
+ end