spektr 0.4.1 → 0.5.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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yaml +2 -2
  3. data/CHANGELOG.md +4 -0
  4. data/CODE_OF_CONDUCT.md +1 -72
  5. data/lib/spektr/app.rb +2 -48
  6. data/lib/spektr/checks/base.rb +85 -60
  7. data/lib/spektr/checks/basic_auth.rb +2 -1
  8. data/lib/spektr/checks/command_injection.rb +15 -10
  9. data/lib/spektr/checks/content_tag_xss.rb +17 -9
  10. data/lib/spektr/checks/cookie_serialization.rb +1 -1
  11. data/lib/spektr/checks/create_with.rb +3 -3
  12. data/lib/spektr/checks/csrf_setting.rb +4 -18
  13. data/lib/spektr/checks/default_routes.rb +3 -2
  14. data/lib/spektr/checks/deserialize.rb +9 -13
  15. data/lib/spektr/checks/detailed_exceptions.rb +3 -3
  16. data/lib/spektr/checks/dynamic_finders.rb +2 -2
  17. data/lib/spektr/checks/evaluation.rb +2 -2
  18. data/lib/spektr/checks/file_access.rb +3 -3
  19. data/lib/spektr/checks/file_disclosure.rb +1 -1
  20. data/lib/spektr/checks/filter_skipping.rb +3 -1
  21. data/lib/spektr/checks/json_encoding.rb +1 -0
  22. data/lib/spektr/checks/json_entity_escape.rb +8 -5
  23. data/lib/spektr/checks/json_parsing.rb +1 -1
  24. data/lib/spektr/checks/link_to_href.rb +8 -6
  25. data/lib/spektr/checks/mass_assignment.rb +7 -7
  26. data/lib/spektr/checks/send.rb +2 -2
  27. data/lib/spektr/checks/sqli.rb +6 -10
  28. data/lib/spektr/checks/xss.rb +10 -7
  29. data/lib/spektr/extractors/calls.rb +22 -0
  30. data/lib/spektr/extractors/methods.rb +28 -0
  31. data/lib/spektr/targets/base.rb +80 -81
  32. data/lib/spektr/targets/controller.rb +35 -24
  33. data/lib/spektr/targets/routes.rb +1 -1
  34. data/lib/spektr/targets/view.rb +8 -9
  35. data/lib/spektr/version.rb +1 -1
  36. data/lib/spektr/warning.rb +2 -2
  37. data/lib/spektr.rb +8 -8
  38. data/spektr.gemspec +3 -2
  39. metadata +23 -20
  40. data/lib/spektr/exp/assignment.rb +0 -20
  41. data/lib/spektr/exp/base.rb +0 -32
  42. data/lib/spektr/exp/const.rb +0 -7
  43. data/lib/spektr/exp/definition.rb +0 -32
  44. data/lib/spektr/exp/ivasign.rb +0 -7
  45. data/lib/spektr/exp/lvasign.rb +0 -7
  46. data/lib/spektr/exp/send.rb +0 -135
  47. data/lib/spektr/exp/xstr.rb +0 -12
  48. data/lib/spektr/processors/base.rb +0 -87
  49. data/lib/spektr/processors/class_processor.rb +0 -24
@@ -19,15 +19,15 @@ module Spektr
19
19
  methods = [:[], :chdir, :chroot, :delete, :entries, :foreach, :glob, :install, :lchmod, :lchown, :link, :load, :load_file, :makedirs, :move, :new, :open, :read, :readlines, :rename, :rmdir, :safe_unlink, :symlink, :syscopy, :sysopen, :truncate, :unlink]
20
20
  targets.each do |target|
21
21
  methods.each do |method|
22
- check_calls_for_user_input(@target.find_calls(method, target))
22
+ check_calls_for_user_input(@target.find_calls(method, target.to_sym))
23
23
  end
24
24
  end
25
25
  end
26
26
 
27
27
  def check_calls_for_user_input(calls)
28
28
  calls.each do |call|
29
- call.arguments.each do |argument|
30
- if user_input?(argument.type, argument.name, argument.ast)
29
+ call.arguments.arguments.each do |argument|
30
+ if user_input?(argument)
31
31
  warn! @target, self, call.location, "#{argument.name} is used for a filename, which enables an attacker to access arbitrary files."
32
32
  end
33
33
  end
@@ -12,7 +12,7 @@ module Spektr
12
12
  def run
13
13
  return unless super
14
14
  config = @app.production_config.find_calls(:serve_static_assets=).first
15
- if config && config.arguments.first.type == :true
15
+ if config && config.arguments.arguments.first.type == :true_node
16
16
  warn! "root", self, nil, "File existence disclosure vulnerability"
17
17
  end
18
18
  end
@@ -9,13 +9,15 @@ module Spektr
9
9
  end
10
10
 
11
11
  def run
12
+ # TODO: write a test for this
12
13
  return unless super
13
14
  calls = %w{ match get post put delete }.inject([]) do |memo, method|
14
15
  memo.concat @target.find_calls(method.to_sym)
15
16
  memo
16
17
  end
17
18
  calls.each do |call|
18
- if !call.arguments.empty? && (call.arguments.first.name.include?(":action") or call.arguments.first.name.include?("*action"))
19
+ arguments = call.arguments.arguments
20
+ if arguments && (arguments.first.name.include?(":action") or arguments.first.name.include?("*action"))
19
21
  warn! @target, self, call.location, "CVE-2011-2929 Rails versions before 3.0.10 have a vulnerability which allows filters to be bypassed"
20
22
  end
21
23
  end
@@ -9,6 +9,7 @@ module Spektr
9
9
  end
10
10
 
11
11
  def run
12
+ # TODO: write a test for this
12
13
  return unless super
13
14
  if app_version_between?("4.1.0", "4.1.10") || app_version_between?("4.2.0", "4.2.1")
14
15
  if calls = @target.find_calls(:to_json).any? || calls = @target.find_calls(:encode).any?
@@ -14,12 +14,16 @@ module Spektr
14
14
  if @app.production_config
15
15
  config = @app.production_config.find_calls(:escape_html_entities_in_json=).first
16
16
  end
17
- if config and config.receiver.expanded == "config.active_support" && config.arguments.first.type == :false
17
+ if config and full_receiver(config) == "config.active_support" && config.arguments.arguments.first.type == :false_node
18
18
  warn! @app.production_config.path, self, nil, "HTML entities in JSON are not escaped by default"
19
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?
20
+
21
+ if @target.find_calls(:escape_html_entities_in_json=, 'ActiveSupport'.to_sym).any?
22
+ warn! @target, self, calls.first.location, "HTML entities in JSON are not escaped by default"
23
+ end
24
+ calls = @target.find_calls(:escape_html_entities_in_json=, 'JSON::Encoding'.to_sym)
25
+ calls.each do |call|
26
+ if full_receiver(call) == 'ActiveSupport.JSON.Encoding'
23
27
  warn! @target, self, calls.first.location, "HTML entities in JSON are not escaped by default"
24
28
  end
25
29
  end
@@ -27,4 +31,3 @@ module Spektr
27
31
  end
28
32
  end
29
33
  end
30
-
@@ -24,7 +24,7 @@ module Spektr
24
24
 
25
25
  def uses_json_gem?
26
26
  @target.find_calls(:backend=).each do |call|
27
- if call.receiver.expanded == "ActiveSupport.JSON" && call.arguments.first&.name == :JSONGem
27
+ if full_receiver(call) == "ActiveSupport.JSON" && call.arguments.arguments.first&.name == :JSONGem
28
28
  warn! @target, self, call.location, "Remote Code Execution CVE_2013_0333"
29
29
  end
30
30
  end
@@ -14,18 +14,20 @@ module Spektr
14
14
  block_locations = []
15
15
  @target.find_calls_with_block(:link_to).each do |call|
16
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
17
+ next unless call.arguments.arguments.first
18
+ ::Spektr.logger.debug "#{@target.path} #{call.location.start_line} #{call.arguments.arguments.first.inspect}"
19
+ if user_input? call.arguments.arguments.first
20
20
  warn! @target, self, call.location, "Cross-Site Scripting: Unsafe user supplied value in link_to"
21
21
  end
22
22
  end
23
23
 
24
24
  @target.find_calls(:link_to).each do |call|
25
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]
26
+ next unless call.arguments
27
+ ::Spektr.logger.debug "#{@target.path} #{call.location.start_line} #{call.arguments.arguments[1].inspect}"
28
+ next unless call.arguments.arguments[1]
29
+ next if call.arguments.arguments[1].name =~ /_url$|_path$/
30
+ if user_input? call.arguments.arguments[1]
29
31
  warn! @target, self, call.location, "Cross-Site Scripting: Unsafe user supplied value in link_to"
30
32
  end
31
33
  end
@@ -16,23 +16,23 @@ module Spektr
16
16
  calls = []
17
17
  model_names.each do |receiver|
18
18
  [:new, :build, :create].each do |method|
19
- calls.concat @target.find_calls(method, receiver)
19
+ calls.concat @target.find_calls(method, receiver.to_sym)
20
20
  end
21
21
  end
22
22
  calls.each do |call|
23
- argument = call.arguments.first
23
+ argument = call.arguments&.arguments&.first
24
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)
25
+ ::Spektr.logger.debug "Mass assignment check at #{call.location.start_line}"
26
+ if user_input?(argument)
27
27
  # we check for permit! separately
28
- next if argument.ast.children[1] == :permit!
28
+ next if argument.name == :permit!
29
29
  # check for permit with arguments
30
- next if argument.ast.children[1] == :permit && argument.ast.children[2]
30
+ next if argument.name == :permit && argument.arguments
31
31
  warn! @target, self, call.location, "Mass assignment"
32
32
  end
33
33
  end
34
34
  @target.find_calls(:permit!).each do |call|
35
- if call.arguments.none?
35
+ unless call.arguments
36
36
  warn! @target, self, call.location, "permit! allows any keys, use it with caution!", :medium
37
37
  end
38
38
  end
@@ -12,8 +12,8 @@ module Spektr
12
12
  return unless super
13
13
  [:send, :try, :__send__, :public_send].each do |method|
14
14
  @target.find_calls(method).each do |call|
15
- argument = call.arguments.first
16
- if user_input?(argument.type, argument.name, argument.ast)
15
+ argument = call.arguments.arguments.first
16
+ if user_input?(argument)
17
17
  warn! @target, self, call.location, "User supplied value in send"
18
18
  end
19
19
  end
@@ -15,27 +15,23 @@ module Spektr
15
15
  :average, :count, :maximum, :minimum, :sum, :exists?,
16
16
  :find_by, :find_by!, :find_or_create_by, :find_or_create_by!,
17
17
  :find_or_initialize_by, :from, :group, :having, :join, :lock,
18
- :where, :not, :select, :rewhere, :reselect, :update_all
18
+ :where, :not, :select, :rewhere, :reselect, :update_all, :find_by_sql
19
19
 
20
20
  ].each do |m|
21
21
  @target.find_calls(m).each do |call|
22
- check_argument(call.arguments.first, m, call)
22
+ check_argument(call.arguments&.arguments&.first, m, call)
23
23
  end
24
24
  end
25
25
  [:calculate].each do |m|
26
26
  @target.find_calls(m).each do |call|
27
- check_argument(call.arguments[1], m, call)
27
+ check_argument(call.arguments.arguments[1], m, call)
28
28
  end
29
29
  end
30
30
 
31
31
  [:delete_by, :destroy_by].each do |m|
32
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)
33
+ if call.arguments.arguments.first
34
+ check_argument(call.arguments.arguments.first, m, call)
39
35
  end
40
36
  end
41
37
  end
@@ -43,7 +39,7 @@ module Spektr
43
39
 
44
40
  def check_argument(argument, method, call)
45
41
  return if argument.nil?
46
- if user_input?(argument.type, argument.name, argument.ast, argument)
42
+ if user_input?(argument)
47
43
  warn! @target, self, call.location, "Possible SQL Injection at #{method}"
48
44
  end
49
45
  end
@@ -15,28 +15,31 @@ module Spektr
15
15
  calls = @target.find_calls(:safe_expr_append=)
16
16
  calls.concat(@target.find_calls(:raw))
17
17
  calls.each do |call|
18
- call.arguments.each do |argument|
19
- if user_input?(argument.type, argument.name, argument.ast)
18
+ ::Spektr.logger.debug "Checking arguments in #{@target.path} at line #{call.location.start_line}"
19
+ call.arguments.arguments.each do |argument|
20
+ if user_input?(argument)
20
21
  warn! @target, self, call.location, "Cross-Site Scripting: Unescaped user input"
21
22
  end
22
23
  if model_attribute?(argument)
23
- warn! @target, self, call.location, "Cross-Site Scripting: Unescaped model attribute #{argument.name}"
24
+ warn! @target, self, call.location, "Cross-Site Scripting: Unescaped model attribute"
24
25
  end
25
26
  end
26
27
  end
27
28
  calls.each do |call|
28
- call.arguments.each do |argument|
29
- if user_input?(argument.type, argument.name, argument.ast)
29
+ ::Spektr.logger.debug "Checking arguments in #{@target.path} at line #{call.location.start_line}"
30
+ call.arguments.arguments.each do |argument|
31
+ if user_input?(argument)
30
32
  warn! @target, self, call.location, "Cross-Site Scripting: Unescaped user input"
31
33
  end
32
34
  if model_attribute?(argument)
33
- warn! @target, self, call.location, "Cross-Site Scripting: Unescaped model attribute #{argument.name}"
35
+ warn! @target, self, call.location, "Cross-Site Scripting: Unescaped model attribute"
34
36
  end
35
37
  end
36
38
  end
37
39
  calls = @target.find_calls(:html_safe)
38
40
  calls.each do |call|
39
- if user_input?(call.receiver.type, call.receiver.name, call.receiver.ast, call.receiver)
41
+ ::Spektr.logger.debug "Checking arguments in #{@target.path} at line #{call.location.start_line}"
42
+ if user_input?(call.receiver)
40
43
  warn! @target, self, call.location, "Cross-Site Scripting: Unescaped user input"
41
44
  end
42
45
  if model_attribute?(call.receiver)
@@ -0,0 +1,22 @@
1
+ module Spektr
2
+ module Extractors
3
+ class Calls < Prism::Visitor
4
+ attr_accessor :result
5
+
6
+ def initialize(name:)
7
+ @name = name
8
+ @result = []
9
+ end
10
+
11
+ def call(ast)
12
+ ast.value.accept(self)
13
+ self
14
+ end
15
+
16
+ def visit_call_node(node)
17
+ @result << node if node.name == @name
18
+ super
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,28 @@
1
+ module Spektr
2
+ module Extractors
3
+ class Methods < Prism::Visitor
4
+ attr_accessor :result
5
+
6
+ def initialize(visibility: :all)
7
+ @visibility = visibility
8
+ @current_visibility = :public
9
+ @result = []
10
+ end
11
+
12
+ def call(ast)
13
+ ast.value.accept(self)
14
+ self
15
+ end
16
+
17
+ def visit_call_node(node)
18
+ @current_visibility = node.name if %i[private protected public].include?(node.name)
19
+ super
20
+ end
21
+
22
+ def visit_def_node(node)
23
+ @result << node if @visibility == :all || @current_visibility == @visibility
24
+ super
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,118 +1,117 @@
1
1
  module Spektr
2
2
  module Targets
3
- class Base
4
- attr_accessor :path, :name, :options, :ast, :parent, :processor
3
+ class Base < Prism::Visitor
4
+ attr_accessor :path, :name, :options, :ast, :parent, :parent_modules, :methods, :calls, :interpolated_xstrings, :lvars
5
5
 
6
6
  def initialize(path, content)
7
7
  Spektr.logger.debug "loading #{path}"
8
- @ast = Spektr::App.parser.parse(content)
8
+ @ast = Prism.parse(content)
9
9
  @path = path
10
10
  return unless @ast
11
+ @parent = ""
12
+ @parent_modules = []
13
+ @methods = []
14
+ @lvars = []
15
+ @ivars = []
16
+ @calls = []
17
+ @interpolated_xstrings = []
18
+ @ast.value.accept(self)
19
+ @name = @path.split('/').last if @name&.blank?
20
+ @name = @name.prepend("#{@parent_modules.map(&:name).join('::')}::") if @name && @parent_modules.any?
21
+ end
11
22
 
12
- @processor = Spektr::Processors::Base.new
13
- @processor.process(@ast)
14
- @name = @processor.name
15
- @name = @path.split('/').last if @name.blank?
23
+ def method_definitions
24
+ @method_definitions ||= find_methods(node: @ast)
25
+ end
16
26
 
17
- @current_method_type = :public
18
- @parent = @processor.parent_name
27
+ def public_methods
28
+ @public_methods ||= find_methods(node: @ast, visibility: :public)
19
29
  end
20
30
 
21
31
  def find_calls(name, receiver = nil)
22
- calls = find(:send, name, @ast).map { |ast| Exp::Send.new(ast) }
23
- if receiver
24
- calls.select! { |call| call.receiver&.expanded == receiver }
25
- elsif receiver == false
26
- calls.select! { |call| call.receiver.nil? }
32
+ if name.is_a? Regexp
33
+ operator = :=~
34
+ else
35
+ operator = :==
36
+ end
37
+ @calls.select do |node|
38
+ if receiver.nil?
39
+ node.name.send(operator, name)
40
+ elsif receiver == false
41
+ node.name.send(operator, name) && node.receiver.nil?
42
+ else
43
+ node_receiver = node.receiver.name if node.receiver.respond_to?(:name)
44
+ if node.receiver.respond_to?(:parent)
45
+ node_receiver = node_receiver.to_s.prepend("#{node.receiver.parent.name}::").to_sym
46
+ end
47
+ node.name.send(operator, name) && node.receiver && receiver == node_receiver
48
+ end
27
49
  end
28
- calls
29
50
  end
30
51
 
31
52
  def find_calls_with_block(name, _receiver = nil)
32
- blocks = find(:block, nil, @ast)
33
- blocks.each_with_object([]) do |block, memo|
34
- if block.children.first.children[1] == name
35
- result = find(:send, name, block).map { |ast| Exp::Send.new(ast) }
36
- memo << result.first
37
- end
53
+ find_calls(name).select do |call|
54
+ call.block
38
55
  end
39
56
  end
40
57
 
41
58
  def find_method(name)
42
- find(:def, name, @ast).last
59
+ @methods.find{|method| method.name == name }
43
60
  end
44
61
 
45
- def find_xstr
46
- find(:xstr, nil, @ast).map { |ast| Exp::Xstr.new(ast) }
62
+ def find_methods(node:, visibility: :all)
63
+ Spektr::Extractors::Methods.new(visibility:).call(node).result
47
64
  end
48
65
 
49
- def find(type, name, ast, result = [])
50
- return result unless ast.is_a? Parser::AST::Node
51
66
 
52
- name_index = case type
53
- when :def
54
- 0
55
- else
56
- 1
57
- end
58
- if node_matches?(ast.type, ast.children[name_index], type, name)
59
- result << ast
60
- elsif ast.children.any?
61
- ast.children.each do |child|
62
- result = find(type, name, child, result)
63
- end
64
- end
65
- result
67
+ def visit_call_node(node)
68
+ @calls << node
69
+ super
66
70
  end
67
71
 
68
- def node_matches?(node_type, node_name, type, name)
69
- if node_type == type
70
- if name.is_a? Regexp
71
- return node_name =~ name
72
- elsif name.nil?
73
- return true
74
- else
75
- return node_name == name
72
+ def visit_class_node(node)
73
+ @name = node.name.to_s
74
+ case node.superclass
75
+ when Prism::CallNode
76
+ @parent = node.superclass.receiver.name.to_s
77
+ when Prism::ConstantPathNode, Prism::ConstantReadNode
78
+ @parent = node.superclass.name.to_s
79
+ @parent.prepend("#{node.superclass.parent.name}::") if node.superclass.respond_to?(:parent)
80
+ if node.superclass.respond_to?(:parent) && node.superclass.parent.respond_to?(:parent)
81
+ @parent.prepend("#{node.superclass.parent.parent.name}::")
76
82
  end
77
83
  end
78
- false
84
+ if node.is_a?(Prism::ClassNode) && node.constant_path && node.constant_path.respond_to?(:parent)
85
+ @parent = node.constant_path.parent.name.to_s
86
+ end
87
+ @parent = @parent.prepend("#{@parent_modules.map(&:name).join('::')}::") if @parent_modules.any?
88
+ super
79
89
  end
80
90
 
81
- def find_methods(ast:, result: [], type: :all)
82
- return result unless ast.is_a?(Parser::AST::Node)
91
+ def visit_module_node(node)
92
+ @parent_modules << node.constant_path.parent.name if node.constant_path && node.constant_path.respond_to?(:parent)
93
+ @parent_modules << node
94
+ super
95
+ end
83
96
 
84
- if ast.type == :send && %i[private public protected].include?(ast.children.last)
85
- @current_method_type = ast.children.last
86
- end
87
- if ast.type == :def && [:all, @current_method_type].include?(type)
88
- result << ast
89
- elsif ast.children.any?
90
- ast.children.map do |child|
91
- result = find_methods(ast: child, result: result, type: type)
92
- end
93
- end
94
- result
97
+ def visit_def_node(node)
98
+ @methods << node
99
+ super
95
100
  end
96
101
 
97
- def ast_to_exp(ast)
98
- case ast.type
99
- when :send
100
- Exp::Send.new(ast)
101
- when :def
102
- Exp::Definition.new(ast)
103
- when :ivasgn, :ivar
104
- Exp::Ivasgin.new(ast)
105
- when :lvasign, :lvar
106
- Exp::Lvasign.new(ast)
107
- when :const
108
- Exp::Const.new(ast)
109
- when :xstr
110
- Exp::Xstr.new(ast)
111
- when :sym, :int, :str
112
- Exp::Base.new(ast)
113
- else
114
- raise "Unknown type #{ast.type} #{ast.inspect}"
115
- end
102
+ def visit_interpolated_x_string_node(node)
103
+ @interpolated_xstrings << node
104
+ super
105
+ end
106
+
107
+ def visit_local_variable_write_node(node)
108
+ @lvars << node
109
+ super
110
+ end
111
+
112
+ def visit_instance_variable_write_node(node)
113
+ @ivars << node
114
+ super
116
115
  end
117
116
  end
118
117
  end
@@ -1,27 +1,28 @@
1
1
  module Spektr
2
2
  module Targets
3
3
  class Controller < Base
4
- attr_accessor :actions
5
4
 
6
5
  def initialize(path, content)
7
6
  super
8
- find_actions
9
7
  end
10
8
 
11
9
  def concern?
12
10
  !name.match('Controller')
13
11
  end
14
12
 
15
- def find_actions
16
- @actions = find_methods(ast: @ast, type: :public).map do |ast|
17
- Action.new(ast, self)
13
+ def actions
14
+ @actions ||= public_methods.map do |node|
15
+ Action.new(node, self)
18
16
  end
19
17
  end
20
18
 
19
+ def find_action(action)
20
+ @actions.find{|a| a.name == action }
21
+ end
22
+
21
23
  def find_parent(controllers)
22
- parent_name = @processor.parent_name
23
- result = find_in_set(parent_name, controllers)
24
- result ||= find_in_set(processor.parent_name_with_modules, controllers)
24
+ result = find_in_set(@parent, controllers)
25
+ # result ||= find_in_set(processor.parent_name_with_modules, controllers)
25
26
  return nil if result&.name == name
26
27
 
27
28
  result
@@ -40,33 +41,43 @@ module Spektr
40
41
  result
41
42
  end
42
43
 
43
- class Action < Spektr::Exp::Definition
44
- attr_accessor :controller, :template
44
+ class Action
45
+ attr_accessor :node, :name, :controller, :template
45
46
 
46
- def initialize(ast, controller)
47
- super(ast)
47
+ def initialize(node, controller)
48
+ @node = node
49
+ @name = node.name
48
50
  @template = nil
51
+ @controller = controller
49
52
  split = []
50
- if controller.parent
53
+ if controller.parent && controller.parent != 'ApplicationController'
51
54
  split = controller.parent.split('::').map { |e| e.delete_suffix('Controller') }.map(&:downcase)
52
55
  if split.size > 1
53
56
  split.pop
54
57
  @template = "#{split.join('/')}/#{@template}"
55
58
  end
56
59
  end
57
- split = split.concat(controller.name.delete_suffix('Controller').split('::').map(&:downcase)).uniq
60
+
61
+ split = split.concat(controller.name.split('::').map do |n|
62
+ n.delete_suffix('Controller')
63
+ end.map(&:downcase)).uniq
58
64
  split.delete('application')
59
65
  @template = File.join(*split, name.to_s)
60
- @body.each do |exp|
61
- if exp.send? && (exp.name == :render && exp.arguments.any?)
62
- if exp.arguments.first.type == :sym
63
- @template = File.join(controller.name.delete_suffix('Controller').underscore,
64
- exp.arguments.first.name.to_s)
65
- elsif exp.arguments.first.type == :str
66
- @template = exp.arguments.first.name
67
- end
68
- end
69
- end
66
+ # TODO: set template from render
67
+ # @body.each do |exp|
68
+ # if exp.send? && exp.name == :render && exp.arguments.any?
69
+ # if exp.arguments.first.type == :sym
70
+ # @template = File.join(controller.name.delete_suffix('Controller').underscore,
71
+ # exp.arguments.first.name.to_s)
72
+ # elsif exp.arguments.first.type == :str
73
+ # @template = exp.arguments.first.name
74
+ # end
75
+ # end
76
+ # end
77
+ end
78
+
79
+ def body
80
+ @node.body.respond_to?(:body) ? @node.body.body : @node.body
70
81
  end
71
82
  end
72
83
  end
@@ -13,7 +13,7 @@ module Spektr
13
13
  end
14
14
  end
15
15
 
16
- class Action < Spektr::Exp::Definition
16
+ class Action
17
17
  attr_accessor :controller, :template
18
18
  def initialize(ast, controller)
19
19
  super(ast)
@@ -1,30 +1,29 @@
1
1
  module Spektr
2
2
  module Targets
3
3
  class View < Base
4
- TEMPLATE_EXTENSIONS = /.*\.(erb|rhtml|haml|slim)$/
4
+ TEMPLATE_EXTENSIONS = /.*\.(erb|rhtml|haml|slim|herb)$/
5
5
  attr_accessor :view_path
6
6
 
7
7
  def initialize(path, content)
8
+ super
9
+ @calls = []
8
10
  Spektr.logger.debug "loading #{path}"
9
11
  @view_path = nil
10
12
  @path = path
11
13
  if match_data = path.match(%r{views/(.+?)\.})
12
14
  @view_path = match_data[1]
13
15
  end
14
- begin
15
- @ast = Spektr::App.parser.parse(source(content))
16
- rescue Parser::SyntaxError => e
17
- @ast = Spektr::App.parser.parse('')
18
- ::Spektr.logger.error "Parser::SyntaxError when parsing #{@view_path}: #{e.message}"
19
- end
20
- @name = @view_path # @ast.children.first.children.last.to_s
16
+ @ast = Prism.parse(source(content))
17
+ @ast.value.accept(self)
18
+ @name = @view_path
21
19
  end
22
20
 
23
21
  def source(content)
24
22
  type = @path.match(TEMPLATE_EXTENSIONS)[1].to_sym
25
23
  case type
26
- when :erb, :rhtml
24
+ when :erb, :rhtml, :herb
27
25
  Erubi.new(content, trim_mode: '-').src
26
+ # Herb.extract_ruby(content)
28
27
  when :haml
29
28
  Haml::Engine.new(content).precompiled
30
29
  when :slim
@@ -1,3 +1,3 @@
1
1
  module Spektr
2
- VERSION = '0.4.1'
2
+ VERSION = '0.5.0'
3
3
  end