starscope 1.5.3 → 1.5.4
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/.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
|