rails-route-checker 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/rails-route-checker.rb +6 -5
- data/lib/rails-route-checker/app_interface.rb +62 -50
- data/lib/rails-route-checker/parsers/erb_parser.rb +52 -0
- data/lib/rails-route-checker/parsers/haml_parser.rb +28 -0
- data/lib/rails-route-checker/parsers/haml_parser/document.rb +47 -0
- data/lib/rails-route-checker/parsers/haml_parser/ruby_extractor.rb +151 -0
- data/lib/rails-route-checker/parsers/haml_parser/tree/filter_node.rb +13 -0
- data/lib/rails-route-checker/parsers/haml_parser/tree/node.rb +126 -0
- data/lib/rails-route-checker/parsers/haml_parser/tree/root_node.rb +13 -0
- data/lib/rails-route-checker/parsers/haml_parser/tree/script_node.rb +13 -0
- data/lib/rails-route-checker/parsers/haml_parser/tree/silent_script_node.rb +13 -0
- data/lib/rails-route-checker/parsers/haml_parser/tree/tag_node.rb +56 -0
- data/lib/rails-route-checker/parsers/loader.rb +55 -0
- data/lib/rails-route-checker/parsers/ruby_parser.rb +57 -0
- data/lib/rails-route-checker/version.rb +1 -1
- metadata +14 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b24a961c0772ee84e1c259c0a0ec3ae58e604a00
|
4
|
+
data.tar.gz: 0cc8d360ec7a9b00ba983438777c84b3e380e506
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 94985375668b8d095725af978851124f9212b05a84cca9ef754ac8b1a9b3b62a33ce9f61a898f252392d5220b3f9ea4a313a83cba0bf129ef49431ba38b54768
|
7
|
+
data.tar.gz: 802d00447e68e0ea6f7a302f2228de146e4612d57c251c067cf5857d9e5895a2abfd33bbe8cc392a14dab85cb7cf8a1df60dc9ebd84ce3a9d4183029bc566482
|
data/lib/rails-route-checker.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
require_relative 'rails-route-checker/app_interface'
|
2
|
+
require_relative 'rails-route-checker/config_file'
|
3
|
+
require_relative 'rails-route-checker/loaded_app'
|
4
|
+
require_relative 'rails-route-checker/runner'
|
5
|
+
require_relative 'rails-route-checker/parsers/loader'
|
6
|
+
require_relative 'rails-route-checker/version'
|
6
7
|
|
7
8
|
module RailsRouteChecker; end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module RailsRouteChecker
|
2
2
|
class AppInterface
|
3
3
|
def initialize(**opts)
|
4
|
-
@options = opts
|
4
|
+
@options = { ignored_controllers: [], ignored_paths: [], ignored_path_whitelist: {} }.merge(opts)
|
5
5
|
end
|
6
6
|
|
7
7
|
def routes_without_actions
|
@@ -38,77 +38,89 @@ module RailsRouteChecker
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def generate_undef_view_path_calls
|
41
|
-
|
41
|
+
generate_undef_view_path_calls_erb + generate_undef_view_path_calls_haml
|
42
|
+
end
|
43
|
+
|
44
|
+
def generate_undef_view_path_calls_erb
|
45
|
+
files = `find app -type f -iregex '.*\\.erb'`.split("\n")
|
46
|
+
return [] if files.none?
|
47
|
+
|
48
|
+
RailsRouteChecker::Parsers::Loader.load_parser(:erb)
|
49
|
+
|
42
50
|
files.map do |filename|
|
43
51
|
controller = controller_from_view_file(filename)
|
44
52
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
skip_first = false
|
50
|
-
if line =~ /^\s*-/
|
51
|
-
line_match = line.match(/^\s*-\s*([a-zA-Z0-9_]+_(?:path|url))\s*=/)
|
52
|
-
defined_variables << line_match[1] if line_match
|
53
|
-
skip_first = true
|
54
|
-
end
|
55
|
-
|
56
|
-
matches = line.scan(/(([a-zA-Z][a-zA-Z0-9_]*)_(?:path|url))[^a-z0-9_]/)
|
57
|
-
matches.shift if skip_first
|
58
|
-
ignores = line.scan(/(([a-zA-Z][a-zA-Z0-9_]*)_(?:path|url))(?: =|[!:])/).map(&:first)
|
59
|
-
ignores += line.scan(/[.@:_'"]([a-zA-Z][a-zA-Z0-9_]+_(?:path|url))[^a-z0-9_]/).map(&:first)
|
60
|
-
|
61
|
-
matches.reject! { |match| ignores.include?(match[0]) }
|
62
|
-
|
63
|
-
matches.map do |match|
|
64
|
-
next if match_in_whitelist?(filename, match)
|
65
|
-
next if match_defined_in_view?(controller, defined_variables, match)
|
66
|
-
{ file: filename, line: line_num + 1, method: match[0] }
|
67
|
-
end
|
53
|
+
filter = lambda do |path_or_url|
|
54
|
+
return false if match_in_whitelist?(filename, path_or_url)
|
55
|
+
return false if match_defined_in_view?(controller, path_or_url)
|
56
|
+
true
|
68
57
|
end
|
58
|
+
|
59
|
+
RailsRouteChecker::Parsers::ErbParser.run(filename, filter: filter)
|
69
60
|
end.flatten.compact
|
70
61
|
end
|
71
62
|
|
72
|
-
def
|
73
|
-
`find app
|
74
|
-
|
63
|
+
def generate_undef_view_path_calls_haml
|
64
|
+
files = `find app -type f -iregex '.*\\.haml'`.split("\n")
|
65
|
+
return [] if files.none?
|
66
|
+
|
67
|
+
unless RailsRouteChecker::Parsers::Loader.haml_available?
|
68
|
+
puts 'WARNING: There are Haml files in your codebase, ' \
|
69
|
+
"but the Haml parser for rails-route-checker couldn't load!"
|
70
|
+
return []
|
71
|
+
end
|
72
|
+
|
73
|
+
RailsRouteChecker::Parsers::Loader.load_parser(:haml)
|
74
|
+
|
75
|
+
files.map do |filename|
|
76
|
+
controller = controller_from_view_file(filename)
|
75
77
|
|
76
|
-
|
77
|
-
|
78
|
-
|
78
|
+
filter = lambda do |path_or_url|
|
79
|
+
return false if match_in_whitelist?(filename, path_or_url)
|
80
|
+
return false if match_defined_in_view?(controller, path_or_url)
|
81
|
+
true
|
82
|
+
end
|
79
83
|
|
80
|
-
|
81
|
-
|
82
|
-
|
84
|
+
RailsRouteChecker::Parsers::HamlParser.run(filename, filter: filter)
|
85
|
+
end.flatten.compact
|
86
|
+
end
|
83
87
|
|
84
|
-
|
88
|
+
def generate_undef_controller_path_calls
|
89
|
+
files = `find app/controllers -type f -iregex '.*\\.rb'`.split("\n")
|
90
|
+
return [] if files.none?
|
85
91
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
92
|
+
RailsRouteChecker::Parsers::Loader.load_parser(:ruby)
|
93
|
+
|
94
|
+
files.map do |filename|
|
95
|
+
controller = controller_from_ruby_file(filename)
|
96
|
+
next unless controller # controller will be nil if it's an ignored controller
|
97
|
+
|
98
|
+
filter = lambda do |path_or_url|
|
99
|
+
return false if match_in_whitelist?(filename, path_or_url)
|
100
|
+
return false if match_defined_in_ruby?(controller, path_or_url)
|
101
|
+
return true
|
91
102
|
end
|
103
|
+
|
104
|
+
RailsRouteChecker::Parsers::RubyParser.run(filename, filter: filter)
|
92
105
|
end.flatten.compact
|
93
106
|
end
|
94
107
|
|
95
|
-
def match_in_whitelist?(filename,
|
96
|
-
|
108
|
+
def match_in_whitelist?(filename, path_or_url)
|
109
|
+
possible_route_name = path_or_url.sub(/_(?:url|path)$/, '')
|
97
110
|
return true if options[:ignored_paths].include?(possible_route_name)
|
98
|
-
(options[:ignored_path_whitelist][filename] || []).include?(
|
111
|
+
(options[:ignored_path_whitelist][filename] || []).include?(path_or_url)
|
99
112
|
end
|
100
113
|
|
101
|
-
def match_defined_in_view?(controller,
|
102
|
-
|
114
|
+
def match_defined_in_view?(controller, path_or_url)
|
115
|
+
possible_route_name = path_or_url.sub(/_(?:url|path)$/, '')
|
103
116
|
return true if loaded_app.all_route_names.include?(possible_route_name)
|
104
|
-
|
105
|
-
controller && controller[:helpers].include?(full_match)
|
117
|
+
controller && controller[:helpers].include?(path_or_url)
|
106
118
|
end
|
107
119
|
|
108
|
-
def match_defined_in_ruby?(controller,
|
109
|
-
|
120
|
+
def match_defined_in_ruby?(controller, path_or_url)
|
121
|
+
possible_route_name = path_or_url.sub(/_(?:url|path)$/, '')
|
110
122
|
return true if loaded_app.all_route_names.include?(possible_route_name)
|
111
|
-
controller && controller[:instance_methods].include?(
|
123
|
+
controller && controller[:instance_methods].include?(path_or_url)
|
112
124
|
end
|
113
125
|
|
114
126
|
def controller_from_view_file(filename)
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module RailsRouteChecker
|
2
|
+
module Parsers
|
3
|
+
module ErbParser
|
4
|
+
class << self
|
5
|
+
def run(filename, **opts)
|
6
|
+
file_source = opts[:source] || File.read(filename)
|
7
|
+
|
8
|
+
next_ruby_source_line_num = 1
|
9
|
+
ruby_source = ''
|
10
|
+
source_map = {}
|
11
|
+
|
12
|
+
file_source.split("\n").each_with_index do |line, line_num|
|
13
|
+
ruby_lines = process_line(line)
|
14
|
+
next unless ruby_lines.any?
|
15
|
+
|
16
|
+
ruby_source += ruby_lines.join("\n") + "\n"
|
17
|
+
ruby_lines.length.times do |i|
|
18
|
+
source_map[next_ruby_source_line_num + i] = line_num + 1
|
19
|
+
end
|
20
|
+
next_ruby_source_line_num += ruby_lines.length
|
21
|
+
end
|
22
|
+
|
23
|
+
opts[:source] = ruby_source
|
24
|
+
opts[:source_map] = source_map
|
25
|
+
|
26
|
+
RailsRouteChecker::Parsers::RubyParser.run(filename, **opts)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def process_line(line)
|
32
|
+
lookup_index = 0
|
33
|
+
ruby_lines = []
|
34
|
+
|
35
|
+
while lookup_index < line.length
|
36
|
+
opening = line.index('<%=', lookup_index)
|
37
|
+
is_write_opening = opening
|
38
|
+
opening ||= line.index('<%', lookup_index)
|
39
|
+
break unless opening
|
40
|
+
|
41
|
+
closing = line.index('%>', opening + 2)
|
42
|
+
break unless closing
|
43
|
+
|
44
|
+
ruby_lines << line[(opening + (is_write_opening ? 3 : 2))..(closing - 1)]
|
45
|
+
lookup_index = closing + 2
|
46
|
+
end
|
47
|
+
ruby_lines
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative 'haml_parser/document'
|
2
|
+
require_relative 'haml_parser/tree/node'
|
3
|
+
require_relative 'haml_parser/tree/filter_node'
|
4
|
+
require_relative 'haml_parser/tree/root_node'
|
5
|
+
require_relative 'haml_parser/tree/script_node'
|
6
|
+
require_relative 'haml_parser/tree/silent_script_node'
|
7
|
+
require_relative 'haml_parser/tree/tag_node'
|
8
|
+
require_relative 'haml_parser/ruby_extractor'
|
9
|
+
|
10
|
+
module RailsRouteChecker
|
11
|
+
module Parsers
|
12
|
+
module HamlParser
|
13
|
+
class << self
|
14
|
+
def run(filename, **opts)
|
15
|
+
file_source = opts[:source] || File.read(filename)
|
16
|
+
|
17
|
+
document = RailsRouteChecker::Parsers::HamlParser::Document.new(file_source)
|
18
|
+
extracted_ruby = RailsRouteChecker::Parsers::HamlParser::RubyExtractor.extract(document)
|
19
|
+
|
20
|
+
opts[:source] = extracted_ruby.source
|
21
|
+
opts[:source_map] = extracted_ruby.source_map
|
22
|
+
|
23
|
+
RailsRouteChecker::Parsers::RubyParser.run(filename, **opts)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module RailsRouteChecker
|
2
|
+
module Parsers
|
3
|
+
module HamlParser
|
4
|
+
class Document
|
5
|
+
attr_reader :tree, :source, :source_lines
|
6
|
+
|
7
|
+
def initialize(source)
|
8
|
+
@source = source.force_encoding(Encoding::UTF_8)
|
9
|
+
@source_lines = @source.split(/\r\n|\r|\n/)
|
10
|
+
|
11
|
+
version = Gem::Version.new(Haml::VERSION).approximate_recommendation
|
12
|
+
original_tree = case version
|
13
|
+
when '~> 4.0', '~> 4.1' then Haml::Parser.new(@source, Haml::Options.new).parse
|
14
|
+
when '~> 5.0' then Haml::Parser.new(options).call(@source)
|
15
|
+
else raise "Cannot handle Haml version: #{version}"
|
16
|
+
end
|
17
|
+
|
18
|
+
@tree = process_tree(original_tree)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def process_tree(original_tree)
|
24
|
+
if Gem::Requirement.new('~> 4.0.0').satisfied_by?(Gem.loaded_specs['haml'].version)
|
25
|
+
original_tree.children.pop
|
26
|
+
end
|
27
|
+
|
28
|
+
convert_tree(original_tree)
|
29
|
+
end
|
30
|
+
|
31
|
+
def convert_tree(haml_node, parent = nil)
|
32
|
+
node_class_name = "#{haml_node.type.to_s.split(/[-_ ]/).collect(&:capitalize).join}Node"
|
33
|
+
node_class_name = 'Node' unless RailsRouteChecker::Parsers::HamlParser::Tree.const_defined?(node_class_name)
|
34
|
+
|
35
|
+
new_node = RailsRouteChecker::Parsers::HamlParser::Tree.const_get(node_class_name).new(self, haml_node)
|
36
|
+
new_node.parent = parent
|
37
|
+
|
38
|
+
new_node.children = haml_node.children.map do |child|
|
39
|
+
convert_tree(child, new_node)
|
40
|
+
end
|
41
|
+
|
42
|
+
new_node
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
module RailsRouteChecker
|
2
|
+
module Parsers
|
3
|
+
module HamlParser
|
4
|
+
class RubyExtractor
|
5
|
+
RubySource = Struct.new(:source, :source_map)
|
6
|
+
|
7
|
+
def self.extract(document)
|
8
|
+
new(document).extract
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(document)
|
12
|
+
@document = document
|
13
|
+
end
|
14
|
+
|
15
|
+
def extract
|
16
|
+
@source_lines = []
|
17
|
+
@source_map = {}
|
18
|
+
@line_count = 0
|
19
|
+
@indent_level = 0
|
20
|
+
@output_count = 0
|
21
|
+
|
22
|
+
visit_children(document.tree)
|
23
|
+
|
24
|
+
RubySource.new(@source_lines.join("\n"), @source_map)
|
25
|
+
end
|
26
|
+
|
27
|
+
def visit_tag(node)
|
28
|
+
additional_attributes = node.dynamic_attributes_sources
|
29
|
+
|
30
|
+
additional_attributes.each do |attributes_code|
|
31
|
+
attributes_code = attributes_code.gsub(/\s*\n\s*/, ' ').strip
|
32
|
+
add_line("{}.merge(#{attributes_code.strip})", node)
|
33
|
+
end
|
34
|
+
|
35
|
+
if node.hash_attributes? && node.dynamic_attributes_sources.empty?
|
36
|
+
normalized_attr_source = node.dynamic_attributes_source[:hash].gsub(/\s*\n\s*/, ' ')
|
37
|
+
|
38
|
+
add_line(normalized_attr_source, node)
|
39
|
+
end
|
40
|
+
|
41
|
+
code = node.script.strip
|
42
|
+
add_line(code, node) unless code.empty?
|
43
|
+
end
|
44
|
+
|
45
|
+
def visit_script(node)
|
46
|
+
code = node.text
|
47
|
+
add_line(code.strip, node)
|
48
|
+
|
49
|
+
start_block = anonymous_block?(code) || start_block_keyword?(code)
|
50
|
+
|
51
|
+
@indent_level += 1 if start_block
|
52
|
+
|
53
|
+
yield
|
54
|
+
|
55
|
+
return unless start_block
|
56
|
+
@indent_level -= 1
|
57
|
+
add_line('end', node)
|
58
|
+
end
|
59
|
+
|
60
|
+
def visit_filter(node)
|
61
|
+
return unless node.filter_type == 'ruby'
|
62
|
+
|
63
|
+
node.text.split("\n").each_with_index do |line, index|
|
64
|
+
add_line(line, node.line + index + 1, false)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def visit(node)
|
69
|
+
block_called = false
|
70
|
+
|
71
|
+
block = lambda do |descend = :children|
|
72
|
+
block_called = true
|
73
|
+
visit_children(node) if descend == :children
|
74
|
+
end
|
75
|
+
|
76
|
+
case node.type
|
77
|
+
when :tag
|
78
|
+
visit_tag(node)
|
79
|
+
when :script, :silent_script
|
80
|
+
visit_script(node, &block)
|
81
|
+
when :filter
|
82
|
+
visit_filter(node)
|
83
|
+
end
|
84
|
+
|
85
|
+
visit_children(node) unless block_called
|
86
|
+
end
|
87
|
+
|
88
|
+
def visit_children(parent)
|
89
|
+
parent.children.each { |node| visit(node) }
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
attr_reader :document
|
95
|
+
|
96
|
+
def add_line(code, node_or_line, discard_blanks = true)
|
97
|
+
return if code.empty? && discard_blanks
|
98
|
+
|
99
|
+
indent_level = @indent_level
|
100
|
+
|
101
|
+
if node_or_line.respond_to?(:line)
|
102
|
+
indent_level -= 1 if mid_block_keyword?(code)
|
103
|
+
end
|
104
|
+
|
105
|
+
indent = (' ' * 2 * indent_level)
|
106
|
+
|
107
|
+
@source_lines << indent_code(code, indent)
|
108
|
+
|
109
|
+
original_line =
|
110
|
+
node_or_line.respond_to?(:line) ? node_or_line.line : node_or_line
|
111
|
+
|
112
|
+
(code.count("\n") + 1).times do
|
113
|
+
@line_count += 1
|
114
|
+
@source_map[@line_count] = original_line
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def indent_code(code, indent)
|
119
|
+
codes = code.split("\n")
|
120
|
+
codes.map { |c| indent + c }.join("\n")
|
121
|
+
end
|
122
|
+
|
123
|
+
def anonymous_block?(text)
|
124
|
+
text =~ /\bdo\s*(\|\s*[^\|]*\s*\|)?(\s*#.*)?\z/
|
125
|
+
end
|
126
|
+
|
127
|
+
START_BLOCK_KEYWORDS = %w[if unless case begin for until while].freeze
|
128
|
+
def start_block_keyword?(text)
|
129
|
+
START_BLOCK_KEYWORDS.include?(block_keyword(text))
|
130
|
+
end
|
131
|
+
|
132
|
+
MID_BLOCK_KEYWORDS = %w[else elsif when rescue ensure].freeze
|
133
|
+
def mid_block_keyword?(text)
|
134
|
+
MID_BLOCK_KEYWORDS.include?(block_keyword(text))
|
135
|
+
end
|
136
|
+
|
137
|
+
LOOP_KEYWORDS = %w[for until while].freeze
|
138
|
+
def block_keyword(text)
|
139
|
+
# Need to handle 'for'/'while' since regex stolen from HAML parser doesn't
|
140
|
+
keyword = text[/\A\s*([^\s]+)\s+/, 1]
|
141
|
+
return keyword if keyword && LOOP_KEYWORDS.include?(keyword)
|
142
|
+
|
143
|
+
keyword = text.scan(Haml::Parser::BLOCK_KEYWORD_REGEX)[0]
|
144
|
+
return unless keyword
|
145
|
+
|
146
|
+
keyword[0] || keyword[1]
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module RailsRouteChecker
|
2
|
+
module Parsers
|
3
|
+
module HamlParser
|
4
|
+
module Tree
|
5
|
+
class Node
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
attr_accessor :children, :parent
|
9
|
+
attr_reader :line, :type
|
10
|
+
|
11
|
+
def initialize(document, parse_node)
|
12
|
+
@line = parse_node.line
|
13
|
+
@document = document
|
14
|
+
@value = parse_node.value
|
15
|
+
@type = parse_node.type
|
16
|
+
end
|
17
|
+
|
18
|
+
def each
|
19
|
+
return to_enum(__callee__) unless block_given?
|
20
|
+
|
21
|
+
node = self
|
22
|
+
loop do
|
23
|
+
yield node
|
24
|
+
break unless (node = node.next_node)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def directives
|
29
|
+
directives = []
|
30
|
+
directives << predecessor.directives if predecessor
|
31
|
+
directives.flatten
|
32
|
+
end
|
33
|
+
|
34
|
+
def source_code
|
35
|
+
next_node_line =
|
36
|
+
if next_node
|
37
|
+
next_node.line - 1
|
38
|
+
else
|
39
|
+
@document.source_lines.count + 1
|
40
|
+
end
|
41
|
+
|
42
|
+
@document.source_lines[@line - 1...next_node_line]
|
43
|
+
.join("\n")
|
44
|
+
.gsub(/^\s*\z/m, '')
|
45
|
+
end
|
46
|
+
|
47
|
+
def inspect
|
48
|
+
"#<#{self.class.name}>"
|
49
|
+
end
|
50
|
+
|
51
|
+
def lines
|
52
|
+
return [] unless @value && text
|
53
|
+
|
54
|
+
text.split(/\r\n|\r|\n/)
|
55
|
+
end
|
56
|
+
|
57
|
+
def line_numbers
|
58
|
+
return (line..line) unless @value && text
|
59
|
+
|
60
|
+
(line..line + lines.count)
|
61
|
+
end
|
62
|
+
|
63
|
+
def predecessor
|
64
|
+
siblings.previous(self) || parent
|
65
|
+
end
|
66
|
+
|
67
|
+
def successor
|
68
|
+
next_sibling = siblings.next(self)
|
69
|
+
return next_sibling if next_sibling
|
70
|
+
|
71
|
+
parent.successor if parent
|
72
|
+
end
|
73
|
+
|
74
|
+
def next_node
|
75
|
+
children.first || successor
|
76
|
+
end
|
77
|
+
|
78
|
+
def subsequents
|
79
|
+
siblings.subsequents(self)
|
80
|
+
end
|
81
|
+
|
82
|
+
def text
|
83
|
+
@value[:text].to_s
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def siblings
|
89
|
+
@siblings ||= Siblings.new(parent ? parent.children : [self])
|
90
|
+
end
|
91
|
+
|
92
|
+
class Siblings < SimpleDelegator
|
93
|
+
def next(node)
|
94
|
+
subsequents(node).first
|
95
|
+
end
|
96
|
+
|
97
|
+
def previous(node)
|
98
|
+
priors(node).last
|
99
|
+
end
|
100
|
+
|
101
|
+
def priors(node)
|
102
|
+
position = position(node)
|
103
|
+
if position.zero?
|
104
|
+
[]
|
105
|
+
else
|
106
|
+
siblings[0..(position - 1)]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def subsequents(node)
|
111
|
+
siblings[(position(node) + 1)..-1]
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
alias siblings __getobj__
|
117
|
+
|
118
|
+
def position(node)
|
119
|
+
siblings.index(node)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module RailsRouteChecker
|
2
|
+
module Parsers
|
3
|
+
module HamlParser
|
4
|
+
module Tree
|
5
|
+
class TagNode < Node
|
6
|
+
def dynamic_attributes_sources
|
7
|
+
@dynamic_attributes_sources ||=
|
8
|
+
if Gem::Version.new(Haml::VERSION) < Gem::Version.new('5')
|
9
|
+
@value[:attributes_hashes]
|
10
|
+
else
|
11
|
+
Array(@value[:dynamic_attributes].to_literal).reject(&:empty?)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def dynamic_attributes_source
|
16
|
+
@dynamic_attributes_source ||=
|
17
|
+
attributes_source.reject { |key| key == :static }
|
18
|
+
end
|
19
|
+
|
20
|
+
def attributes_source
|
21
|
+
@attr_source ||=
|
22
|
+
begin
|
23
|
+
_explicit_tag, static_attrs, rest =
|
24
|
+
source_code.scan(/\A\s*(%[-:\w]+)?([-:\w\.\#]*)(.*)/m)[0]
|
25
|
+
|
26
|
+
attr_types = {
|
27
|
+
'{' => [:hash, %w[{ }]],
|
28
|
+
'(' => [:html, %w[( )]],
|
29
|
+
'[' => [:object_ref, %w[[ ]]]
|
30
|
+
}
|
31
|
+
|
32
|
+
attr_source = { static: static_attrs }
|
33
|
+
while rest
|
34
|
+
type, chars = attr_types[rest[0]]
|
35
|
+
break unless type
|
36
|
+
break if attr_source[type]
|
37
|
+
|
38
|
+
attr_source[type], rest = Haml::Util.balance(rest, *chars)
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_source
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def hash_attributes?
|
46
|
+
!dynamic_attributes_source[:hash].nil?
|
47
|
+
end
|
48
|
+
|
49
|
+
def script
|
50
|
+
(@value[:value] if @value[:parse]) || ''
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module RailsRouteChecker
|
2
|
+
module Parsers
|
3
|
+
module Loader
|
4
|
+
class << self
|
5
|
+
def load_parser(type)
|
6
|
+
case type
|
7
|
+
when :ruby
|
8
|
+
load_basic_parser(:ruby)
|
9
|
+
when :erb
|
10
|
+
load_basic_parser(:ruby)
|
11
|
+
load_basic_parser(:erb)
|
12
|
+
when :haml
|
13
|
+
if haml_available?
|
14
|
+
load_basic_parser(:ruby)
|
15
|
+
load_haml_parser
|
16
|
+
end
|
17
|
+
else
|
18
|
+
raise "Unrecognised parser attempting to be loaded: #{type}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def haml_available?
|
23
|
+
return @haml_available if defined?(@haml_available)
|
24
|
+
@haml_available = gem_installed?('haml')
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def gem_installed?(name, version_requirement = nil)
|
30
|
+
Gem::Dependency.new(name, version_requirement).matching_specs.any?
|
31
|
+
end
|
32
|
+
|
33
|
+
def load_basic_parser(parser_name)
|
34
|
+
if_unloaded(parser_name) do
|
35
|
+
require_relative "#{parser_name}_parser"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def load_haml_parser
|
40
|
+
if_unloaded(:haml) do
|
41
|
+
require 'haml'
|
42
|
+
require_relative 'haml_parser'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def if_unloaded(parser)
|
47
|
+
@loaded_parsers ||= {}
|
48
|
+
return false if @loaded_parsers[parser]
|
49
|
+
yield
|
50
|
+
@loaded_parsers[parser] = true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'ripper'
|
2
|
+
|
3
|
+
module RailsRouteChecker
|
4
|
+
module Parsers
|
5
|
+
module RubyParser
|
6
|
+
class << self
|
7
|
+
def run(filename, **opts)
|
8
|
+
file_source = opts[:source] || File.read(filename)
|
9
|
+
|
10
|
+
items = []
|
11
|
+
|
12
|
+
deep_iterator(Ripper.sexp(file_source)) do |item, extra_data|
|
13
|
+
scope = extra_data[:scope]
|
14
|
+
next unless %i[vcall fcall].include?(scope[-2])
|
15
|
+
next unless scope[-1] == :@ident
|
16
|
+
next unless item.end_with?('_path', '_url')
|
17
|
+
|
18
|
+
next if opts[:filter].respond_to?(:call) && !opts[:filter].call(item)
|
19
|
+
|
20
|
+
line = extra_data[:position][0]
|
21
|
+
line = opts[:source_map][line] || 'unknown' if opts[:source_map]
|
22
|
+
|
23
|
+
items << { file: filename, line: line, method: item }
|
24
|
+
end
|
25
|
+
|
26
|
+
items
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def deep_iterator(list, current_scope = [], current_line_num = [], &block)
|
32
|
+
if list.is_a?(Array)
|
33
|
+
if list[0].is_a?(Symbol)
|
34
|
+
current_scope << list[0]
|
35
|
+
|
36
|
+
if list[-1].is_a?(Array) && list[-1].length == 2 && list[-1].all? { |item| item.is_a?(Integer) }
|
37
|
+
current_line_num = list[-1]
|
38
|
+
list = list[0..-2]
|
39
|
+
end
|
40
|
+
|
41
|
+
list[1..-1].each do |item|
|
42
|
+
deep_iterator(item, current_scope, current_line_num, &block)
|
43
|
+
end
|
44
|
+
current_scope.pop
|
45
|
+
else
|
46
|
+
list.each do |item|
|
47
|
+
deep_iterator(item, current_scope, current_line_num, &block)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
elsif !list.nil?
|
51
|
+
yield(list, { scope: current_scope, position: current_line_num })
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails-route-checker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dave Allie
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-12-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -68,6 +68,18 @@ files:
|
|
68
68
|
- lib/rails-route-checker/app_interface.rb
|
69
69
|
- lib/rails-route-checker/config_file.rb
|
70
70
|
- lib/rails-route-checker/loaded_app.rb
|
71
|
+
- lib/rails-route-checker/parsers/erb_parser.rb
|
72
|
+
- lib/rails-route-checker/parsers/haml_parser.rb
|
73
|
+
- lib/rails-route-checker/parsers/haml_parser/document.rb
|
74
|
+
- lib/rails-route-checker/parsers/haml_parser/ruby_extractor.rb
|
75
|
+
- lib/rails-route-checker/parsers/haml_parser/tree/filter_node.rb
|
76
|
+
- lib/rails-route-checker/parsers/haml_parser/tree/node.rb
|
77
|
+
- lib/rails-route-checker/parsers/haml_parser/tree/root_node.rb
|
78
|
+
- lib/rails-route-checker/parsers/haml_parser/tree/script_node.rb
|
79
|
+
- lib/rails-route-checker/parsers/haml_parser/tree/silent_script_node.rb
|
80
|
+
- lib/rails-route-checker/parsers/haml_parser/tree/tag_node.rb
|
81
|
+
- lib/rails-route-checker/parsers/loader.rb
|
82
|
+
- lib/rails-route-checker/parsers/ruby_parser.rb
|
71
83
|
- lib/rails-route-checker/runner.rb
|
72
84
|
- lib/rails-route-checker/version.rb
|
73
85
|
- rails-route-checker.gemspec
|