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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ad69825f00790c2dbac821ca39fc909f3e651e78
4
- data.tar.gz: b24150ef71ea4f975f1e85e19d18a58c9985a0b2
3
+ metadata.gz: b24a961c0772ee84e1c259c0a0ec3ae58e604a00
4
+ data.tar.gz: 0cc8d360ec7a9b00ba983438777c84b3e380e506
5
5
  SHA512:
6
- metadata.gz: 7e96ff4b2f655d30c21f5aacb947bcc85eb499e983986b35605154cf48664e62d8a2cc0e1745a775624661907c61aab957d967a82aa712fe605a25a951f01e43
7
- data.tar.gz: 55cf806407c1855d25171ce9c912ec7d2c0db6ea46cf5d4270ad1befbe6f35e241e6ae2ad5c5a8a2cf8f2ec3fbaeba28ace1f999757335ded1273ef1cb95955c
6
+ metadata.gz: 94985375668b8d095725af978851124f9212b05a84cca9ef754ac8b1a9b3b62a33ce9f61a898f252392d5220b3f9ea4a313a83cba0bf129ef49431ba38b54768
7
+ data.tar.gz: 802d00447e68e0ea6f7a302f2228de146e4612d57c251c067cf5857d9e5895a2abfd33bbe8cc392a14dab85cb7cf8a1df60dc9ebd84ce3a9d4183029bc566482
@@ -1,7 +1,8 @@
1
- require 'rails-route-checker/app_interface'
2
- require 'rails-route-checker/config_file'
3
- require 'rails-route-checker/loaded_app'
4
- require 'rails-route-checker/runner'
5
- require 'rails-route-checker/version'
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
- files = `find app -type f -iregex '.*\\.haml' -or -iregex '.*\\.erb' -or -iregex '.*\\.html'`.split("\n")
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
- defined_variables = []
46
-
47
- File.read(filename).each_line.each_with_index.map do |line, line_num|
48
- next if line =~ /^\s*-\s*#/
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 generate_undef_controller_path_calls
73
- `find app/controllers -type f -iregex '.*\\.rb'`.split("\n").map do |filename|
74
- controller = controller_from_ruby_file(filename)
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
- File.read(filename).each_line.each_with_index.map do |line, line_num|
77
- next if line =~ /^\s*#/
78
- next if line =~ /^\s*def\s/
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
- matches = line.scan(/(([a-zA-Z][a-zA-Z0-9_]*)_(?:path|url))[^a-z0-9_]/)
81
- ignores = line.scan(/(([a-zA-Z][a-zA-Z0-9_]*)_(?:path|url))(?: =|[!:])/).map(&:first)
82
- ignores += line.scan(/[.@:_'"]([a-zA-Z][a-zA-Z0-9_]+_(?:path|url))[^a-z0-9_]/).map(&:first)
84
+ RailsRouteChecker::Parsers::HamlParser.run(filename, filter: filter)
85
+ end.flatten.compact
86
+ end
83
87
 
84
- matches.reject! { |match| ignores.include?(match[0]) }
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
- matches.map do |match|
87
- next if match_in_whitelist?(filename, match)
88
- next if match_defined_in_ruby?(controller, match)
89
- { file: filename, line: line_num + 1, method: match[0] }
90
- end
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, match)
96
- full_match, possible_route_name = match
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?(full_match)
111
+ (options[:ignored_path_whitelist][filename] || []).include?(path_or_url)
99
112
  end
100
113
 
101
- def match_defined_in_view?(controller, defined_variables, match)
102
- full_match, possible_route_name = match
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
- return true if defined_variables.include?(full_match)
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, match)
109
- full_match, possible_route_name = match
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?(full_match)
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,13 @@
1
+ module RailsRouteChecker
2
+ module Parsers
3
+ module HamlParser
4
+ module Tree
5
+ class FilterNode < Node
6
+ def filter_type
7
+ @value[:name]
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ 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,13 @@
1
+ module RailsRouteChecker
2
+ module Parsers
3
+ module HamlParser
4
+ module Tree
5
+ class RootNode < Node
6
+ def file
7
+ @document.file
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module RailsRouteChecker
2
+ module Parsers
3
+ module HamlParser
4
+ module Tree
5
+ class ScriptNode < Node
6
+ def script
7
+ @value[:text]
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module RailsRouteChecker
2
+ module Parsers
3
+ module HamlParser
4
+ module Tree
5
+ class SilentScriptNode < Node
6
+ def script
7
+ @value[:text]
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ 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
@@ -1,3 +1,3 @@
1
1
  module RailsRouteChecker
2
- VERSION = '0.1.1'.freeze
2
+ VERSION = '0.2.0'.freeze
3
3
  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.1.1
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-10-28 00:00:00.000000000 Z
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