cql_ruby 0.0.7 → 0.0.12

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,18 +1,18 @@
1
- # frozen_string_literal: true
2
-
3
- module CqlRuby
4
- class CrumbCollector
5
- #
6
- # @param printer [Cqlruby::AbstractPrinter]
7
- #
8
- def initialize(printer)
9
- super()
10
-
11
- @printer = printer
12
- end
13
-
14
- def add(crumb)
15
- @printer.print(crumb)
16
- end
17
- end
18
- end
1
+ # frozen_string_literal: true
2
+
3
+ module CqlRuby
4
+ class CrumbCollector
5
+ #
6
+ # @param printer [CqlRuby::AbstractPrinter]
7
+ #
8
+ def initialize(printer)
9
+ super()
10
+
11
+ @printer = printer
12
+ end
13
+
14
+ def add(crumb)
15
+ @printer.print(crumb)
16
+ end
17
+ end
18
+ end
@@ -1,148 +1,194 @@
1
- # frozen_string_literal: true
2
-
3
- require 'parser/current'
4
- require 'pathname'
5
-
6
- module CqlRuby
7
- class Config
8
- @@debug_level = 0
9
-
10
- class << self
11
- def debug_level=(lvl); @@debug_level = lvl; end
12
- def debug_level; @@debug_level; end
13
- def debug_level_1?; @@debug_level >= 1; end
14
- def debug_level_2?; @@debug_level >= 2; end
15
- def debug_level_3?; @@debug_level >= 3; end
16
- end
17
- end
18
- end
19
-
20
- #
21
- # Executes search and dumps results into the collector.
22
- #
23
- # @param collector [Cqlruby::CrumbCollector]
24
- # @param pattern [String]
25
- # @param path [String]
26
- # @param filters [Array<String>]
27
- #
28
- module CqlRuby
29
- class Executor
30
- def initialize(
31
- collector:,
32
- filter_reader:,
33
- pattern:,
34
- path:,
35
- filters: [],
36
- recursive: true
37
- )
38
- @collector = collector
39
- @filter_reader = filter_reader
40
- @pattern = pattern
41
- @path = path
42
- @filters = filters
43
- @recursive = recursive
44
- end
45
-
46
- def search_all
47
- files.flat_map do |file|
48
- CqlRuby.log "File check: #{file}" if CqlRuby::Config.debug_level_3?
49
- search(file)
50
- end
51
- end
52
-
53
- private
54
-
55
- def search(file)
56
- ast = Parser::CurrentRuby.parse(File.read(file))
57
- source_reader = CqlRuby::SourceReader.new(file)
58
- walk(ast, [], source_reader)
59
-
60
- nil
61
- rescue => e
62
- CqlRuby.log "File #{file} cannot be parsed: #{e}"
63
- end
64
-
65
- def walk(node, ancestors, source_reader)
66
- if node.is_a?(Parser::AST::Node)
67
- node.children.flat_map do |child|
68
- walk(child, ancestors.dup + [node], source_reader)
69
- end
70
- else
71
- if match?(node) && CqlRuby::FilterEvaluator.pass?(filter_reader, node, ancestors)
72
- collector.add(CqlRuby::Crumb.new(node, ancestors, source_reader))
73
- end
74
- end
75
-
76
- nil
77
- end
78
-
79
- def match?(target)
80
- CqlRuby::PatternMatcher.match?(pattern, target)
81
- end
82
-
83
- def files
84
- return [path] if File.file?(path)
85
-
86
- clean_path = Pathname(path).cleanpath.to_s
87
- clean_path += '/**' if recursive
88
- clean_path += '/*.rb'
89
-
90
- Dir.glob(clean_path)
91
- end
92
-
93
- attr_reader :collector
94
- attr_reader :filter_reader
95
- attr_reader :pattern
96
- attr_reader :path
97
- attr_reader :filters
98
- attr_reader :recursive
99
- end
100
- end
101
-
102
- CqlRuby::Crumb = Struct.new(:full_name, :ancestors, :source_reader) do
103
- def line_no
104
- ancestors.last.location.expression.line
105
- end
106
-
107
- def line_col_no
108
- ancestors.last.location.expression.column
109
- end
110
-
111
- def source
112
- source_reader.source_line(line_no)
113
- end
114
-
115
- def surrounding_line(offset)
116
- source_reader.source_line(line_no + offset)
117
- end
118
-
119
- def file_name
120
- source_reader.file
121
- end
122
-
123
- def expression_size
124
- ancestors.last.location.expression.size
125
- end
126
-
127
- def type
128
- ancestors.last.type
129
- end
130
- end
131
-
132
- CqlRuby::SourceReader = Struct.new(:file) do
133
- def initialize(*args)
134
- super
135
- end
136
-
137
- def source_line(n)
138
- return nil unless lines.size >= n
139
-
140
- lines[n - 1].chop
141
- end
142
-
143
- private
144
-
145
- def lines
146
- @lines ||= IO.readlines(file)
147
- end
148
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'parser/current'
4
+ require 'pathname'
5
+
6
+ module CqlRuby
7
+ class Config
8
+ @@debug_level = 0
9
+
10
+ class << self
11
+ def debug_level=(lvl); @@debug_level = lvl; end
12
+ def debug_level; @@debug_level; end
13
+ def debug_level_1?; @@debug_level >= 1; end
14
+ def debug_level_2?; @@debug_level >= 2; end
15
+ def debug_level_3?; @@debug_level >= 3; end
16
+ end
17
+ end
18
+ end
19
+
20
+ #
21
+ # Executes search and dumps results into the collector.
22
+ #
23
+ # @param collector [CqlRuby::CrumbCollector]
24
+ # @param pattern [String]
25
+ # @param path [String]
26
+ # @param filters [Array<String>]
27
+ #
28
+ module CqlRuby
29
+ class Executor
30
+ def initialize(
31
+ collector:,
32
+ filter_reader:,
33
+ pattern:,
34
+ path:,
35
+ filters: [],
36
+ recursive: true,
37
+ include: nil,
38
+ exclude: nil,
39
+ search_type: :token
40
+ )
41
+ @collector = collector
42
+ @filter_reader = filter_reader
43
+ @pattern = pattern
44
+ @path = path
45
+ @filters = filters
46
+ @recursive = recursive
47
+ @include = include
48
+ @exclude = exclude
49
+ @search_type = search_type
50
+ end
51
+
52
+ def search_all
53
+ files.flat_map do |file|
54
+ next if !@exclude.nil? && CqlRuby::PatternMatcher.match?(@exclude, file)
55
+ next unless @include.nil? || CqlRuby::PatternMatcher.match?(@include, file)
56
+
57
+ CqlRuby.log "File check: #{file}" if CqlRuby::Config.debug_level_3?
58
+ search(file)
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def search(file)
65
+ ast = Parser::CurrentRuby.parse(File.read(file))
66
+ source_reader = CqlRuby::SourceReader.new(file)
67
+ walk(ast, [], source_reader)
68
+
69
+ nil
70
+ rescue
71
+ CqlRuby.log "File #{file} cannot be parsed"
72
+ CqlRuby.log "Reason: #{$!}" if CqlRuby::Config.debug_level_1?
73
+ end
74
+
75
+ def walk(node, ancestors, source_reader)
76
+ if node.is_a?(Parser::AST::Node)
77
+ if search_for_node?
78
+ if match?(node.type) && CqlRuby::FilterEvaluator.pass?(filter_reader, ancestors, node)
79
+ collector.add(CqlRuby::Crumb.new(node, ancestors, source_reader))
80
+ end
81
+ end
82
+
83
+ node.children.flat_map do |child|
84
+ walk(child, ancestors.dup + [node], source_reader)
85
+ end
86
+ else
87
+ if search_for_token? && match?(node) && CqlRuby::FilterEvaluator.pass?(filter_reader, ancestors, node)
88
+ collector.add(CqlRuby::Crumb.new(node, ancestors, source_reader))
89
+ end
90
+ end
91
+
92
+ nil
93
+ end
94
+
95
+ def match?(target)
96
+ CqlRuby::PatternMatcher.match?(pattern, target)
97
+ end
98
+
99
+ def files
100
+ return [path] if File.file?(path)
101
+
102
+ clean_path = Pathname(path).cleanpath.to_s
103
+ clean_path += '/**' if recursive
104
+ clean_path += '/*.rb'
105
+
106
+ Dir.glob(clean_path)
107
+ end
108
+
109
+ def search_for_token?
110
+ @search_type == :token
111
+ end
112
+
113
+ def search_for_node?
114
+ @search_type == :node
115
+ end
116
+
117
+ attr_reader :collector
118
+ attr_reader :filter_reader
119
+ attr_reader :pattern
120
+ attr_reader :path
121
+ attr_reader :filters
122
+ attr_reader :recursive
123
+ end
124
+ end
125
+
126
+ module CqlRuby
127
+ class Crumb
128
+ def initialize(node, ancestors, source_reader)
129
+ @node = node
130
+ @ancestors = ancestors
131
+ @source_reader = source_reader
132
+ end
133
+
134
+ def line_no
135
+ anchor.location.expression.line
136
+ end
137
+
138
+ def line_col_no
139
+ anchor.location.expression.column
140
+ end
141
+
142
+ def source
143
+ source_reader.source_line(line_no)
144
+ end
145
+
146
+ def surrounding_line(offset)
147
+ source_reader.source_line(line_no + offset)
148
+ end
149
+
150
+ def file_name
151
+ source_reader.file
152
+ end
153
+
154
+ def expression_size
155
+ anchor.location.expression.size
156
+ end
157
+
158
+ def type
159
+ anchor.type
160
+ end
161
+
162
+ private
163
+
164
+ def anchor
165
+ if node.is_a?(Parser::AST::Node)
166
+ node
167
+ else
168
+ ancestors.last
169
+ end
170
+ end
171
+
172
+ attr_reader :node
173
+ attr_reader :ancestors
174
+ attr_reader :source_reader
175
+ end
176
+ end
177
+
178
+ CqlRuby::SourceReader = Struct.new(:file) do
179
+ def initialize(*args)
180
+ super
181
+ end
182
+
183
+ def source_line(n)
184
+ return nil unless lines.size >= n
185
+
186
+ lines[n - 1].chop
187
+ end
188
+
189
+ private
190
+
191
+ def lines
192
+ @lines ||= IO.readlines(file)
193
+ end
194
+ end
@@ -1,54 +1,178 @@
1
- # frozen_string_literal: true
2
-
3
- module CqlRuby
4
- class FilterEvaluator
5
- class << self
6
- def pass?(filter_reader, node, ancestors)
7
- [
8
- pass_type?(filter_reader, ancestors),
9
- pass_nesting?(filter_reader, ancestors),
10
- ].all?
11
- end
12
-
13
- private
14
-
15
- #
16
- # @param [Cqlruby::FilterReader] filter_reader
17
- # @param [Array<Parser::AST::Node>] ancestors
18
- #
19
- # @return [Boolean]
20
- #
21
- def pass_type?(filter_reader, ancestors)
22
- return true unless filter_reader.restrict_types?
23
-
24
- filter_reader.allowed_types.include?(ancestors.last.type)
25
- end
26
-
27
- #
28
- # @param [Cqlruby::FilterReader] filter_reader
29
- # @param [Array<Parser::AST::Node>] ancestors
30
- #
31
- # @return [Boolean]
32
- #
33
- def pass_nesting?(filter_reader, ancestors)
34
- return true unless filter_reader.restrict_nesting?
35
-
36
- filter_reader.nest_under.all? do |nest_rule|
37
- ancestors.reverse.any? do |ancestor|
38
- next false unless ancestor.type.to_s == nest_rule.type
39
- next true unless nest_rule.restrict_name?
40
-
41
- # TODO Make a proper matcher class.
42
- if %w[class module].include?(nest_rule.type)
43
- CqlRuby::PatternMatcher.match?(nest_rule.name, ancestor.children[0].children[1])
44
- elsif %[def].include?(nest_rule.type)
45
- CqlRuby::PatternMatcher.match?(nest_rule.name, ancestor.children[0])
46
- else
47
- raise 'Unknown type.'
48
- end
49
- end
50
- end
51
- end
52
- end
53
- end
54
- end
1
+ # frozen_string_literal: true
2
+
3
+ module CqlRuby
4
+ class FilterEvaluator
5
+ class << self
6
+ def pass?(filter_reader, ancestors, node)
7
+ [
8
+ pass_type?(filter_reader, ancestors),
9
+ pass_nesting?(filter_reader, ancestors),
10
+ pass_has?(filter_reader, ancestors, node),
11
+ pass_pattern?(filter_reader, ancestors, node),
12
+ ].all?
13
+ end
14
+
15
+ private
16
+
17
+ #
18
+ # @param [CqlRuby::FilterReader] filter_reader
19
+ # @param [Array<Parser::AST::Node>] ancestors
20
+ #
21
+ # @return [Boolean]
22
+ #
23
+ def pass_type?(filter_reader, ancestors)
24
+ return true unless filter_reader.restrict_types?
25
+
26
+ filter_reader.allowed_types.include?(ancestors.last.type)
27
+ end
28
+
29
+ #
30
+ # @param [CqlRuby::FilterReader] filter_reader
31
+ # @param [Array<Parser::AST::Node>] ancestors
32
+ #
33
+ # @return [Boolean]
34
+ #
35
+ def pass_nesting?(filter_reader, ancestors)
36
+ return true unless filter_reader.restrict_nesting?
37
+
38
+ filter_reader.nest_under.all? do |nest_rule|
39
+ ancestors.reverse.any? do |ancestor|
40
+ next false unless ancestor.type.to_s == nest_rule.type
41
+ next true unless nest_rule.restrict_name?
42
+
43
+ if %w[class module].include?(nest_rule.type)
44
+ CqlRuby::PatternMatcher.match?(nest_rule.name, ancestor.children[0].children[1])
45
+ elsif %[def].include?(nest_rule.type)
46
+ CqlRuby::PatternMatcher.match?(nest_rule.name, ancestor.children[0])
47
+ else
48
+ raise 'Unknown type.'
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ #
55
+ # @param [CqlRuby::FilterReader] filter_reader
56
+ # @param [Array<Parser::AST::Node>] ancestors
57
+ # @param [Any<Parser::AST::Node, Symbol>] node
58
+ #
59
+ def pass_has?(filter_reader, ancestors, node)
60
+ return true unless filter_reader.restrict_children?
61
+
62
+ filter_reader.has_leaves.all? do |has_rule|
63
+ anchor_node = if node.is_a?(Symbol)
64
+ # TODO: Expand this to other wrappers (loops, conditions, etc).
65
+ try_get_class(ancestors) || try_get_module(ancestors) || try_get_def(ancestors)
66
+ else
67
+ node
68
+ end
69
+ next false unless anchor_node
70
+
71
+ has_node_with_name?(anchor_node, has_rule)
72
+ end
73
+ end
74
+
75
+ #
76
+ # @param [CqlRuby::FilterReader] filter_reader
77
+ # @param [Array<Parser::AST::Node>] ancestors
78
+ # @param [Any<Parser::AST::Node, Symbol>] node
79
+ #
80
+ def pass_pattern?(filter_reader, ancestors, node)
81
+ return true unless filter_reader.restrict_pattern?
82
+
83
+ filter_reader.patterns.all? do |pattern|
84
+ pattern_ancestors = pattern.ancestors.dup
85
+ ancestor_idx = ancestors.size - 1
86
+
87
+ while !pattern_ancestors.empty? && ancestor_idx >= 0
88
+ if CqlRuby::PatternMatcher.match?(pattern_ancestors.last, ancestors[ancestor_idx].type)
89
+ pattern_ancestors.pop
90
+ end
91
+
92
+ ancestor_idx -= 1
93
+ end
94
+ return false unless pattern_ancestors.empty?
95
+
96
+ pattern_descendants = pattern.descendants.dup
97
+ match_descendant_pattern?(pattern_descendants, node)
98
+ end
99
+ end
100
+
101
+ #
102
+ # @param [Array<String>] pattern_descendants
103
+ # @param [Parser::AST::Node] node
104
+ #
105
+ def match_descendant_pattern?(pattern_descendants, node)
106
+ return true if pattern_descendants.empty?
107
+ # If we're at the end and we're still expecting a type - no match.
108
+ return false unless node.is_a?(Parser::AST::Node)
109
+
110
+ node.children.any? do |child|
111
+ next false unless child.is_a?(Parser::AST::Node)
112
+ if CqlRuby::PatternMatcher.match?(pattern_descendants.first, child.type)
113
+ match_descendant_pattern?(pattern_descendants[1..], child)
114
+ else
115
+ match_descendant_pattern?(pattern_descendants.dup, child)
116
+ end
117
+ end
118
+ end
119
+
120
+ #
121
+ # @param [Array<Parser::AST::Node>] ancestors
122
+ #
123
+ def try_get_class(ancestors)
124
+ return nil unless ancestors.size >= 2
125
+ return nil unless ancestors[-1].type == :const
126
+ return nil unless ancestors[-2].type == :class
127
+
128
+ ancestors[-2].children[2]
129
+ end
130
+
131
+ #
132
+ # @param [Array<Parser::AST::Node>] ancestors
133
+ #
134
+ def try_get_module(ancestors)
135
+ return nil unless ancestors.size >= 2
136
+ return nil unless ancestors[-1].type == :const
137
+ return nil unless ancestors[-2].type == :module
138
+
139
+ ancestors[-2].children[1]
140
+ end
141
+
142
+ #
143
+ # @param [Array<Parser::AST::Node>] ancestors
144
+ #
145
+ def try_get_def(ancestors)
146
+ return nil unless ancestors.size >= 1
147
+ return nil unless ancestors[-1].type == :def
148
+
149
+ ancestors[-1].children[2]
150
+ end
151
+
152
+ #
153
+ # @param [Parser::AST::Node] anchor_node
154
+ # @param [CqlRuby::NodeSpec]
155
+ #
156
+ def has_node_with_name?(anchor_node, has_rule)
157
+ return false unless anchor_node.is_a?(Parser::AST::Node)
158
+
159
+ fn_children_with_type = ->(node) { node.children.map { |child| [child, node.type] } }
160
+ to_visit = fn_children_with_type.call(anchor_node)
161
+
162
+ until to_visit.empty?
163
+ current_node, current_type = to_visit.shift
164
+
165
+ if current_node.is_a?(Parser::AST::Node)
166
+ to_visit += fn_children_with_type.call(current_node)
167
+ else
168
+ if current_type == has_rule.type.to_sym && CqlRuby::PatternMatcher.match?(has_rule.name, current_node)
169
+ return true
170
+ end
171
+ end
172
+ end
173
+
174
+ false
175
+ end
176
+ end
177
+ end
178
+ end