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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yaml +2 -2
- data/CHANGELOG.md +4 -0
- data/CODE_OF_CONDUCT.md +1 -72
- data/lib/spektr/app.rb +2 -48
- data/lib/spektr/checks/base.rb +85 -60
- data/lib/spektr/checks/basic_auth.rb +2 -1
- data/lib/spektr/checks/command_injection.rb +15 -10
- data/lib/spektr/checks/content_tag_xss.rb +17 -9
- data/lib/spektr/checks/cookie_serialization.rb +1 -1
- data/lib/spektr/checks/create_with.rb +3 -3
- data/lib/spektr/checks/csrf_setting.rb +4 -18
- data/lib/spektr/checks/default_routes.rb +3 -2
- data/lib/spektr/checks/deserialize.rb +9 -13
- data/lib/spektr/checks/detailed_exceptions.rb +3 -3
- data/lib/spektr/checks/dynamic_finders.rb +2 -2
- data/lib/spektr/checks/evaluation.rb +2 -2
- data/lib/spektr/checks/file_access.rb +3 -3
- data/lib/spektr/checks/file_disclosure.rb +1 -1
- data/lib/spektr/checks/filter_skipping.rb +3 -1
- data/lib/spektr/checks/json_encoding.rb +1 -0
- data/lib/spektr/checks/json_entity_escape.rb +8 -5
- data/lib/spektr/checks/json_parsing.rb +1 -1
- data/lib/spektr/checks/link_to_href.rb +8 -6
- data/lib/spektr/checks/mass_assignment.rb +7 -7
- data/lib/spektr/checks/send.rb +2 -2
- data/lib/spektr/checks/sqli.rb +6 -10
- data/lib/spektr/checks/xss.rb +10 -7
- data/lib/spektr/extractors/calls.rb +22 -0
- data/lib/spektr/extractors/methods.rb +28 -0
- data/lib/spektr/targets/base.rb +80 -81
- data/lib/spektr/targets/controller.rb +35 -24
- data/lib/spektr/targets/routes.rb +1 -1
- data/lib/spektr/targets/view.rb +8 -9
- data/lib/spektr/version.rb +1 -1
- data/lib/spektr/warning.rb +2 -2
- data/lib/spektr.rb +8 -8
- data/spektr.gemspec +3 -2
- metadata +23 -20
- data/lib/spektr/exp/assignment.rb +0 -20
- data/lib/spektr/exp/base.rb +0 -32
- data/lib/spektr/exp/const.rb +0 -7
- data/lib/spektr/exp/definition.rb +0 -32
- data/lib/spektr/exp/ivasign.rb +0 -7
- data/lib/spektr/exp/lvasign.rb +0 -7
- data/lib/spektr/exp/send.rb +0 -135
- data/lib/spektr/exp/xstr.rb +0 -12
- data/lib/spektr/processors/base.rb +0 -87
- 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
|
|
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 == :
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
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.
|
|
19
|
-
if user_input? call.arguments.
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
23
|
+
argument = call.arguments&.arguments&.first
|
|
24
24
|
next if argument.nil?
|
|
25
|
-
::Spektr.logger.debug "Mass assignment check at #{call.location.
|
|
26
|
-
if user_input?(argument
|
|
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.
|
|
28
|
+
next if argument.name == :permit!
|
|
29
29
|
# check for permit with arguments
|
|
30
|
-
next if argument.
|
|
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
|
-
|
|
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
|
data/lib/spektr/checks/send.rb
CHANGED
|
@@ -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
|
|
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
|
data/lib/spektr/checks/sqli.rb
CHANGED
|
@@ -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
|
|
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
|
|
42
|
+
if user_input?(argument)
|
|
47
43
|
warn! @target, self, call.location, "Possible SQL Injection at #{method}"
|
|
48
44
|
end
|
|
49
45
|
end
|
data/lib/spektr/checks/xss.rb
CHANGED
|
@@ -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
|
-
|
|
19
|
-
|
|
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
|
|
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
|
-
|
|
29
|
-
|
|
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
|
|
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
|
-
|
|
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
|
data/lib/spektr/targets/base.rb
CHANGED
|
@@ -1,118 +1,117 @@
|
|
|
1
1
|
module Spektr
|
|
2
2
|
module Targets
|
|
3
|
-
class Base
|
|
4
|
-
attr_accessor :path, :name, :options, :ast, :parent, :
|
|
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 =
|
|
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
|
-
|
|
13
|
-
@
|
|
14
|
-
|
|
15
|
-
@name = @path.split('/').last if @name.blank?
|
|
23
|
+
def method_definitions
|
|
24
|
+
@method_definitions ||= find_methods(node: @ast)
|
|
25
|
+
end
|
|
16
26
|
|
|
17
|
-
|
|
18
|
-
@
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
33
|
-
|
|
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
|
|
59
|
+
@methods.find{|method| method.name == name }
|
|
43
60
|
end
|
|
44
61
|
|
|
45
|
-
def
|
|
46
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
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
|
|
82
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
|
16
|
-
@actions
|
|
17
|
-
Action.new(
|
|
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
|
-
|
|
23
|
-
result
|
|
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
|
|
44
|
-
attr_accessor :controller, :template
|
|
44
|
+
class Action
|
|
45
|
+
attr_accessor :node, :name, :controller, :template
|
|
45
46
|
|
|
46
|
-
def initialize(
|
|
47
|
-
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
data/lib/spektr/targets/view.rb
CHANGED
|
@@ -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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
data/lib/spektr/version.rb
CHANGED