cql_ruby 0.0.7 → 0.0.12

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.
@@ -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