rails-route-checker 0.1.1 → 0.2.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 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