starscope 1.5.3 → 1.5.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -9
- data/.travis.yml +2 -2
- data/CHANGELOG.md +11 -1
- data/bin/starscope +2 -0
- data/lib/starscope/db.rb +266 -264
- data/lib/starscope/exportable.rb +225 -221
- data/lib/starscope/fragment_extractor.rb +18 -16
- data/lib/starscope/langs/erb.rb +34 -32
- data/lib/starscope/langs/golang.rb +176 -174
- data/lib/starscope/langs/javascript.rb +96 -94
- data/lib/starscope/langs/ruby.rb +109 -91
- data/lib/starscope/matcher.rb +1 -2
- data/lib/starscope/output.rb +37 -35
- data/lib/starscope/queryable.rb +31 -29
- data/lib/starscope/version.rb +1 -1
- data/starscope.gemspec +4 -4
- data/test/fixtures/sample_ruby.rb +1 -0
- data/test/unit/exportable_test.rb +17 -4
- data/test/unit/fragment_extractor_test.rb +6 -1
- metadata +40 -40
@@ -1,22 +1,24 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
module Starscope
|
2
|
+
class FragmentExtractor
|
3
|
+
def initialize(lang, frags)
|
4
|
+
@child = Starscope::Lang.const_get(lang)
|
5
|
+
@frags = frags
|
6
|
+
end
|
6
7
|
|
7
|
-
|
8
|
-
|
8
|
+
def extract(path, text)
|
9
|
+
text = @frags.map { |f| f.delete(:frag).strip }.join("\n")
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
extractor_metadata = @child.extract(path, text) do |tbl, name, args|
|
12
|
+
args.merge!(@frags[args[:line_no] - 1]) if args[:line_no]
|
13
|
+
yield tbl, name, args
|
14
|
+
end
|
14
15
|
|
15
|
-
|
16
|
-
|
17
|
-
|
16
|
+
# TODO: translate metadata?
|
17
|
+
extractor_metadata
|
18
|
+
end
|
18
19
|
|
19
|
-
|
20
|
-
|
20
|
+
def name
|
21
|
+
@child.name
|
22
|
+
end
|
21
23
|
end
|
22
24
|
end
|
data/lib/starscope/langs/erb.rb
CHANGED
@@ -1,44 +1,46 @@
|
|
1
|
-
module Starscope
|
2
|
-
module
|
3
|
-
|
1
|
+
module Starscope
|
2
|
+
module Lang
|
3
|
+
module ERB
|
4
|
+
VERSION = 1
|
4
5
|
|
5
|
-
|
6
|
-
|
6
|
+
ERB_START = /<%(?:-|={1,4})?/
|
7
|
+
ERB_END = /-?%>/
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
def self.extract(path, contents, &block)
|
13
|
-
multiline = false # true when parsing a multiline <% ... %> block
|
14
|
-
|
15
|
-
contents.lines.each_with_index do |line, line_no|
|
16
|
-
line_no += 1 # zero-index to one-index
|
9
|
+
def self.match_file(name)
|
10
|
+
name.end_with?('.erb')
|
11
|
+
end
|
17
12
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
13
|
+
def self.extract(_path, contents)
|
14
|
+
multiline = false # true when parsing a multiline <% ... %> block
|
15
|
+
|
16
|
+
contents.lines.each_with_index do |line, line_no|
|
17
|
+
line_no += 1 # zero-index to one-index
|
18
|
+
|
19
|
+
if multiline
|
20
|
+
term = line.index(ERB_END)
|
21
|
+
if term
|
22
|
+
yield Starscope::DB::FRAGMENT, :Ruby, frag: line[0...term], line_no: line_no
|
23
|
+
line = line[term + 1..-1]
|
24
|
+
multiline = false
|
25
|
+
else
|
26
|
+
yield Starscope::DB::FRAGMENT, :Ruby, frag: line, line_no: line_no
|
27
|
+
end
|
26
28
|
end
|
27
|
-
end
|
28
29
|
|
29
|
-
|
30
|
+
next if multiline
|
30
31
|
|
31
|
-
|
32
|
-
|
33
|
-
|
32
|
+
line.scan(/#{ERB_START}(.*?)#{ERB_END}/) do |match|
|
33
|
+
yield Starscope::DB::FRAGMENT, :Ruby, frag: match[0], line_no: line_no
|
34
|
+
end
|
34
35
|
|
35
|
-
|
36
|
+
line.gsub!(/<%.*?%>/, '')
|
36
37
|
|
37
|
-
|
38
|
-
|
38
|
+
match = /#{ERB_START}(.*)$/.match(line)
|
39
|
+
next unless match
|
39
40
|
|
40
|
-
|
41
|
-
|
41
|
+
yield Starscope::DB::FRAGMENT, :Ruby, frag: match[1], line_no: line_no
|
42
|
+
multiline = true
|
43
|
+
end
|
42
44
|
end
|
43
45
|
end
|
44
46
|
end
|
@@ -1,206 +1,208 @@
|
|
1
|
-
module Starscope
|
2
|
-
module
|
3
|
-
|
1
|
+
module Starscope
|
2
|
+
module Lang
|
3
|
+
module Golang
|
4
|
+
VERSION = 1
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
6
|
+
FUNC_CALL = /([\w\.]*?\w)\(/
|
7
|
+
END_OF_BLOCK = /^\s*\}\s*$/
|
8
|
+
END_OF_GROUP = /^\s*\)\s*$/
|
9
|
+
STRING_LITERAL = /".+?"/
|
10
|
+
BUILTIN_FUNCS = %w(new make len close copy delete int int8 int16 int32 int64
|
11
|
+
uint uint8 uint16 uint32 uint64 string byte).freeze
|
12
|
+
CONTROL_KEYS = %w(if for switch case).freeze
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
def self.extract(path, contents, &block)
|
18
|
-
stack = []
|
19
|
-
scope = []
|
20
|
-
contents.lines.each_with_index do |line, line_no|
|
21
|
-
line_no += 1 # zero-index to one-index
|
22
|
-
|
23
|
-
# strip single-line comments like // foo
|
24
|
-
match = %r{//}.match(line)
|
25
|
-
line = match.pre_match if match
|
26
|
-
# strip single-line comments like foo /* foo */ foo
|
27
|
-
match = %r{/\*.*\*/}.match(line)
|
28
|
-
line = match.pre_match + match.post_match if match
|
29
|
-
# strip end-of-line comment starters like foo /* foo \n
|
30
|
-
match = %r{/\*}.match(line)
|
31
|
-
line = match.pre_match if match
|
32
|
-
ends_with_comment = !match.nil?
|
14
|
+
def self.match_file(name)
|
15
|
+
name.end_with?('.go')
|
16
|
+
end
|
33
17
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
stack.pop
|
40
|
-
end
|
18
|
+
def self.extract(_path, contents, &block)
|
19
|
+
stack = []
|
20
|
+
scope = []
|
21
|
+
contents.lines.each_with_index do |line, line_no|
|
22
|
+
line_no += 1 # zero-index to one-index
|
41
23
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
24
|
+
# strip single-line comments like // foo
|
25
|
+
match = %r{//}.match(line)
|
26
|
+
line = match.pre_match if match
|
27
|
+
# strip single-line comments like foo /* foo */ foo
|
28
|
+
match = %r{/\*.*\*/}.match(line)
|
29
|
+
line = match.pre_match + match.post_match if match
|
30
|
+
# strip end-of-line comment starters like foo /* foo \n
|
31
|
+
match = %r{/\*}.match(line)
|
32
|
+
line = match.pre_match if match
|
33
|
+
ends_with_comment = !match.nil?
|
51
34
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
end_block(line_no, scope, stack, &block)
|
58
|
-
when /(.+)\s+\w+/
|
59
|
-
parse_def(Regexp.last_match(1), line_no, scope, &block)
|
60
|
-
end
|
61
|
-
when :interface
|
62
|
-
case line
|
63
|
-
when END_OF_BLOCK
|
64
|
-
end_block(line_no, scope, stack, &block)
|
65
|
-
when /(\w+)\(.*\)\s+/
|
66
|
-
yield :defs, scope + [Regexp.last_match(1)], line_no: line_no
|
67
|
-
end
|
68
|
-
when :def
|
69
|
-
case line
|
70
|
-
when END_OF_GROUP
|
35
|
+
# if we're in a block comment, wait for it to end
|
36
|
+
if stack[-1] == :comment
|
37
|
+
match = %r{\*/(.*)}.match(line)
|
38
|
+
next unless match
|
39
|
+
line = match[1]
|
71
40
|
stack.pop
|
72
|
-
when /(.+)\s*=.*/
|
73
|
-
parse_def(Regexp.last_match(1), line_no, scope, &block)
|
74
|
-
parse_call(line, line_no, scope, &block)
|
75
|
-
else
|
76
|
-
parse_def(line, line_no, scope, &block)
|
77
41
|
end
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
42
|
+
|
43
|
+
if stack[-1] != :import && !line.start_with?('import')
|
44
|
+
# strip string literals like "foo" unless they're part of an import
|
45
|
+
pos = 0
|
46
|
+
while (match = STRING_LITERAL.match(line, pos))
|
47
|
+
eos = find_end_of_string(line, match.begin(0))
|
48
|
+
line = line[0..match.begin(0)] + line[eos..-1]
|
49
|
+
pos = match.begin(0) + 2
|
50
|
+
end
|
85
51
|
end
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
52
|
+
|
53
|
+
# poor-man's parser
|
54
|
+
case stack[-1]
|
55
|
+
when :struct
|
56
|
+
case line
|
57
|
+
when END_OF_BLOCK
|
58
|
+
end_block(line_no, scope, stack, &block)
|
59
|
+
when /(.+)\s+\w+/
|
60
|
+
parse_def(Regexp.last_match(1), line_no, scope, &block)
|
61
|
+
end
|
62
|
+
when :interface
|
63
|
+
case line
|
64
|
+
when END_OF_BLOCK
|
65
|
+
end_block(line_no, scope, stack, &block)
|
66
|
+
when /(\w+)\(.*\)\s+/
|
67
|
+
yield :defs, scope + [Regexp.last_match(1)], line_no: line_no
|
68
|
+
end
|
69
|
+
when :def
|
70
|
+
case line
|
71
|
+
when END_OF_GROUP
|
72
|
+
stack.pop
|
73
|
+
when /(.+)\s*=.*/
|
74
|
+
parse_def(Regexp.last_match(1), line_no, scope, &block)
|
75
|
+
parse_call(line, line_no, scope, &block)
|
76
|
+
else
|
77
|
+
parse_def(line, line_no, scope, &block)
|
78
|
+
end
|
79
|
+
when :import
|
80
|
+
case line
|
81
|
+
when END_OF_GROUP
|
82
|
+
stack.pop
|
83
|
+
when /"(.+)"/
|
84
|
+
name = Regexp.last_match(1).split('/')
|
85
|
+
yield :imports, name, line_no: line_no
|
86
|
+
end
|
87
|
+
when :func
|
88
|
+
case line
|
89
|
+
when /^\}/
|
90
|
+
yield :end, '}', line_no: line_no, type: :func
|
91
|
+
stack.pop
|
92
|
+
else
|
93
|
+
parse_new_line(line, line_no, scope, stack, &block)
|
94
|
+
end
|
91
95
|
else
|
92
96
|
parse_new_line(line, line_no, scope, stack, &block)
|
93
97
|
end
|
94
|
-
else
|
95
|
-
parse_new_line(line, line_no, scope, stack, &block)
|
96
|
-
end
|
97
98
|
|
98
|
-
|
99
|
-
|
100
|
-
|
99
|
+
# if the line looks like "foo /* foo" then we enter the comment state
|
100
|
+
# after parsing the usable part of the line
|
101
|
+
stack.push(:comment) if ends_with_comment
|
102
|
+
end
|
101
103
|
end
|
102
|
-
end
|
103
104
|
|
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
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
105
|
+
# handles new lines (when not in the middle of an existing definition)
|
106
|
+
def self.parse_new_line(line, line_no, scope, stack, &block)
|
107
|
+
case line
|
108
|
+
when /^func\s+(\w+)\(/
|
109
|
+
yield :defs, scope + [Regexp.last_match(1)], line_no: line_no, type: :func
|
110
|
+
stack.push(:func)
|
111
|
+
when /^func\s+\(\w+\s+\*?(\w+)\)\s*(\w+)\(/
|
112
|
+
yield :defs, scope + [Regexp.last_match(1), Regexp.last_match(2)], line_no: line_no, type: :func
|
113
|
+
stack.push(:func)
|
114
|
+
when /^package\s+(\w+)/
|
115
|
+
scope.push(Regexp.last_match(1))
|
116
|
+
yield :defs, scope, line_no: line_no, type: :package
|
117
|
+
when /^type\s+(\w+)\s+struct\s*\{/
|
118
|
+
scope.push(Regexp.last_match(1))
|
119
|
+
stack.push(:struct)
|
120
|
+
yield :defs, scope, line_no: line_no, type: :class
|
121
|
+
when /^type\s+(\w+)\s+interface\s*\{/
|
122
|
+
scope.push(Regexp.last_match(1))
|
123
|
+
stack.push(:interface)
|
124
|
+
yield :defs, scope, line_no: line_no, type: :class
|
125
|
+
when /^type\s+(\w+)/
|
126
|
+
yield :defs, scope + [Regexp.last_match(1)], line_no: line_no, type: :type
|
127
|
+
when /^import\s+"(.+)"/
|
128
|
+
name = Regexp.last_match(1).split('/')
|
129
|
+
yield :imports, name, line_no: line_no
|
130
|
+
when /^import\s+\(/
|
131
|
+
stack.push(:import)
|
132
|
+
when /^var\s+\(/
|
133
|
+
stack.push(:def)
|
134
|
+
when /^var\s+(\w+)\s/
|
135
|
+
yield :defs, scope + [Regexp.last_match(1)], line_no: line_no
|
136
|
+
parse_call(line, line_no, scope, &block)
|
137
|
+
when /^const\s+\(/
|
138
|
+
stack.push(:def)
|
139
|
+
when /^const\s+(\w+)\s/
|
140
|
+
yield :defs, scope + [Regexp.last_match(1)], line_no: line_no
|
141
|
+
parse_call(line, line_no, scope, &block)
|
142
|
+
when /^\s+(.*?) :?=[^=]/
|
143
|
+
Regexp.last_match(1).split(' ').each do |var|
|
144
|
+
next if CONTROL_KEYS.include?(var)
|
145
|
+
name = var.delete(',').split('.')
|
146
|
+
next if name[0] == '_' # assigning to _ is a discard in golang
|
147
|
+
if name.length == 1
|
148
|
+
yield :assigns, scope + [name[0]], line_no: line_no
|
149
|
+
else
|
150
|
+
yield :assigns, name, line_no: line_no
|
151
|
+
end
|
150
152
|
end
|
153
|
+
parse_call(line, line_no, scope, &block)
|
154
|
+
else
|
155
|
+
parse_call(line, line_no, scope, &block)
|
151
156
|
end
|
152
|
-
parse_call(line, line_no, scope, &block)
|
153
|
-
else
|
154
|
-
parse_call(line, line_no, scope, &block)
|
155
157
|
end
|
156
|
-
end
|
157
158
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
159
|
+
def self.parse_call(line, line_no, scope)
|
160
|
+
line.scan(FUNC_CALL) do |match|
|
161
|
+
name = match[0].split('.').select { |chunk| !chunk.empty? }
|
162
|
+
if name.length == 1
|
163
|
+
next if name[0] == 'func'
|
164
|
+
if BUILTIN_FUNCS.include?(name[0])
|
165
|
+
yield :calls, name[0], line_no: line_no
|
166
|
+
else
|
167
|
+
yield :calls, scope + [name[0]], line_no: line_no
|
168
|
+
end
|
165
169
|
else
|
166
|
-
yield :calls,
|
170
|
+
yield :calls, name, line_no: line_no
|
167
171
|
end
|
168
|
-
else
|
169
|
-
yield :calls, name, line_no: line_no
|
170
172
|
end
|
171
173
|
end
|
172
|
-
end
|
173
174
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
175
|
+
def self.parse_def(line, line_no, scope)
|
176
|
+
# if it doesn't start with a valid identifier character, it's probably
|
177
|
+
# part of a multi-line literal and we should skip it
|
178
|
+
return unless line =~ /^\s*[[:alpha:]_]/
|
178
179
|
|
179
|
-
|
180
|
-
|
181
|
-
|
180
|
+
line.split.each do |var|
|
181
|
+
yield :defs, scope + [var.delete(',')], line_no: line_no
|
182
|
+
break unless var.end_with?(',')
|
183
|
+
end
|
182
184
|
end
|
183
|
-
end
|
184
185
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
186
|
+
def self.end_block(line_no, scope, stack)
|
187
|
+
yield :end, scope + ['}'], line_no: line_no, type: :class
|
188
|
+
stack.pop
|
189
|
+
scope.pop
|
190
|
+
end
|
190
191
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
192
|
+
def self.find_end_of_string(line, start)
|
193
|
+
escape = false
|
194
|
+
(start + 1...line.length).each do |i|
|
195
|
+
if escape
|
196
|
+
escape = false
|
197
|
+
elsif line[i] == '\\'
|
198
|
+
escape = true
|
199
|
+
elsif line[i] == '"'
|
200
|
+
return i
|
201
|
+
end
|
200
202
|
end
|
201
|
-
end
|
202
203
|
|
203
|
-
|
204
|
+
line.length
|
205
|
+
end
|
204
206
|
end
|
205
207
|
end
|
206
208
|
end
|