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.
- checksums.yaml +4 -4
- data/bin/cql_ruby +139 -116
- data/lib/cql_ruby.rb +15 -15
- data/lib/cql_ruby/abstract_printer.rb +12 -12
- data/lib/cql_ruby/console_printer.rb +97 -97
- data/lib/cql_ruby/crumb_collector.rb +18 -18
- data/lib/cql_ruby/executor.rb +194 -148
- data/lib/cql_ruby/filter_evaluator.rb +178 -54
- data/lib/cql_ruby/filter_reader.rb +130 -81
- data/lib/cql_ruby/pattern_matcher.rb +57 -53
- metadata +2 -2
@@ -1,18 +1,18 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module CqlRuby
|
4
|
-
class CrumbCollector
|
5
|
-
#
|
6
|
-
# @param printer [
|
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
|
data/lib/cql_ruby/executor.rb
CHANGED
@@ -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 [
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
@
|
42
|
-
@
|
43
|
-
@
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
ancestors
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
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,
|
7
|
-
[
|
8
|
-
pass_type?(filter_reader, ancestors),
|
9
|
-
pass_nesting?(filter_reader, ancestors),
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
#
|
18
|
-
#
|
19
|
-
# @
|
20
|
-
#
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
filter_reader.
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
#
|
30
|
-
#
|
31
|
-
# @
|
32
|
-
#
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
filter_reader.
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|