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
@@ -2,138 +2,140 @@ require 'rkelly'
|
|
2
2
|
require 'babel/transpiler'
|
3
3
|
require 'sourcemap'
|
4
4
|
|
5
|
-
module Starscope
|
6
|
-
module
|
7
|
-
|
5
|
+
module Starscope
|
6
|
+
module Lang
|
7
|
+
module Javascript
|
8
|
+
VERSION = 1
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
def self.match_file(name)
|
11
|
+
name.end_with?('.js')
|
12
|
+
end
|
12
13
|
|
13
|
-
|
14
|
-
|
14
|
+
def self.extract(path, contents, &block)
|
15
|
+
return if path.end_with?('.min.js')
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
17
|
+
transform = Babel::Transpiler.transform(contents,
|
18
|
+
'stage' => 0,
|
19
|
+
'blacklist' => ['validation.react'],
|
20
|
+
'externalHelpers' => true,
|
21
|
+
'compact' => false,
|
22
|
+
'sourceMaps' => true)
|
23
|
+
map = SourceMap::Map.from_hash(transform['map'])
|
24
|
+
ast = RKelly::Parser.new.parse(transform['code'])
|
25
|
+
lines = contents.lines.to_a
|
25
26
|
|
26
|
-
|
27
|
+
return unless ast
|
27
28
|
|
28
|
-
|
29
|
+
found = extract_methods(ast, map, lines, &block)
|
29
30
|
|
30
|
-
|
31
|
+
found = extract_var_decls(ast, map, lines, found, &block)
|
31
32
|
|
32
|
-
|
33
|
-
|
33
|
+
extract_var_reads(ast, map, lines, found, &block)
|
34
|
+
end
|
34
35
|
|
35
|
-
|
36
|
-
|
36
|
+
def self.extract_methods(ast, map, lines)
|
37
|
+
found = {}
|
37
38
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
39
|
+
ast.each do |node|
|
40
|
+
case node
|
41
|
+
when RKelly::Nodes::FunctionExprNode, RKelly::Nodes::FunctionDeclNode
|
42
|
+
line = find_line(node.range.from, map, lines, node.value)
|
43
|
+
next unless line
|
43
44
|
|
44
|
-
|
45
|
-
|
45
|
+
type = :func
|
46
|
+
type = :class if lines[line - 1].include?("class #{node.value}")
|
46
47
|
|
47
|
-
|
48
|
-
|
49
|
-
|
48
|
+
yield :defs, node.value, line_no: line, type: type
|
49
|
+
found[node.value] ||= Set.new
|
50
|
+
found[node.value].add(line)
|
50
51
|
|
51
|
-
|
52
|
+
next if type == :class
|
52
53
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
54
|
+
mapping = map.bsearch(SourceMap::Offset.new(node.range.to.line, node.range.to.char))
|
55
|
+
if lines[mapping.original.line - 1].include? '}'
|
56
|
+
yield :end, '}', line_no: mapping.original.line, type: type
|
57
|
+
else
|
58
|
+
yield :end, '', line_no: mapping.original.line, type: type, col: mapping.original.column
|
59
|
+
end
|
60
|
+
when RKelly::Nodes::FunctionCallNode
|
61
|
+
name = node_name(node.value)
|
62
|
+
next unless name
|
62
63
|
|
63
|
-
|
64
|
+
node = node.arguments.value[0] if name == 'require' && !node.value.is_a?(RKelly::Nodes::DotAccessorNode)
|
64
65
|
|
65
|
-
|
66
|
-
|
66
|
+
line = find_line(node.range.from, map, lines, name)
|
67
|
+
next unless line
|
67
68
|
|
68
|
-
|
69
|
-
|
69
|
+
found[name] ||= Set.new
|
70
|
+
found[name].add(line)
|
70
71
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
72
|
+
if name == 'require' && node.is_a?(RKelly::Nodes::StringNode)
|
73
|
+
yield :requires, node.value[1...-1], line_no: line
|
74
|
+
else
|
75
|
+
yield :calls, name, line_no: line
|
76
|
+
end
|
75
77
|
end
|
76
78
|
end
|
79
|
+
|
80
|
+
found
|
77
81
|
end
|
78
82
|
|
79
|
-
found
|
80
|
-
|
83
|
+
def self.extract_var_decls(ast, map, lines, found)
|
84
|
+
ast.each do |node|
|
85
|
+
next unless node.is_a? RKelly::Nodes::VarDeclNode
|
81
86
|
|
82
|
-
|
83
|
-
|
84
|
-
next unless node.is_a? RKelly::Nodes::VarDeclNode
|
87
|
+
line = find_line(node.range.from, map, lines, node.name)
|
88
|
+
next unless line
|
85
89
|
|
86
|
-
|
87
|
-
|
90
|
+
if node.value.is_a?(RKelly::Nodes::AssignExprNode) &&
|
91
|
+
node.value.value.is_a?(RKelly::Nodes::FunctionCallNode) &&
|
92
|
+
node.value.value.value.is_a?(RKelly::Nodes::ResolveNode) &&
|
93
|
+
node.value.value.value.value == 'require'
|
94
|
+
found[node.name] ||= Set.new
|
95
|
+
found[node.name].add(line)
|
96
|
+
next
|
97
|
+
end
|
88
98
|
|
89
|
-
|
90
|
-
|
91
|
-
node.value.value.value.is_a?(RKelly::Nodes::ResolveNode) &&
|
92
|
-
node.value.value.value.value == 'require'
|
99
|
+
next if found[node.name] && found[node.name].include?(line)
|
100
|
+
yield :defs, node.name, line_no: line
|
93
101
|
found[node.name] ||= Set.new
|
94
102
|
found[node.name].add(line)
|
95
|
-
next
|
96
103
|
end
|
97
104
|
|
98
|
-
|
99
|
-
yield :defs, node.name, line_no: line
|
100
|
-
found[node.name] ||= Set.new
|
101
|
-
found[node.name].add(line)
|
105
|
+
found
|
102
106
|
end
|
103
107
|
|
104
|
-
found
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
ast.each do |node|
|
109
|
-
name = node_name(node)
|
110
|
-
next unless name
|
108
|
+
def self.extract_var_reads(ast, map, lines, found)
|
109
|
+
ast.each do |node|
|
110
|
+
name = node_name(node)
|
111
|
+
next unless name
|
111
112
|
|
112
|
-
|
113
|
-
|
113
|
+
line = find_line(node.range.from, map, lines, name)
|
114
|
+
next unless line
|
114
115
|
|
115
|
-
|
116
|
-
|
116
|
+
next if found[name] && found[name].include?(line)
|
117
|
+
yield :reads, name, line_no: line
|
118
|
+
end
|
117
119
|
end
|
118
|
-
end
|
119
120
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
121
|
+
def self.node_name(node)
|
122
|
+
case node
|
123
|
+
when RKelly::Nodes::DotAccessorNode
|
124
|
+
node.accessor
|
125
|
+
when RKelly::Nodes::ResolveNode
|
126
|
+
node.value
|
127
|
+
end
|
126
128
|
end
|
127
|
-
end
|
128
129
|
|
129
|
-
|
130
|
-
|
131
|
-
|
130
|
+
def self.find_line(from, map, lines, name)
|
131
|
+
mapping = map.bsearch(SourceMap::Offset.new(from.line, from.char))
|
132
|
+
return unless mapping
|
132
133
|
|
133
|
-
|
134
|
-
|
134
|
+
line = lines[mapping.original.line - 1]
|
135
|
+
return unless line.include?(name) || (name == 'require' && line.include?('import'))
|
135
136
|
|
136
|
-
|
137
|
+
mapping.original.line
|
138
|
+
end
|
137
139
|
end
|
138
140
|
end
|
139
141
|
end
|
data/lib/starscope/langs/ruby.rb
CHANGED
@@ -1,111 +1,129 @@
|
|
1
1
|
require 'parser/current'
|
2
2
|
|
3
|
-
module Starscope
|
4
|
-
module
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
3
|
+
module Starscope
|
4
|
+
module Lang
|
5
|
+
module Ruby
|
6
|
+
# This class exists solely to workaround/suppress issues from upstream's handling of invalid unicode.
|
7
|
+
# See https://github.com/whitequark/parser/issues/283; workaround borrowed from
|
8
|
+
# https://github.com/bbatsov/rubocop/commit/5e820eb5cfddf5e0f7efd2c0fa99e6b8a4c7b7e0
|
9
|
+
class Builder < Parser::Builders::Default
|
10
|
+
def string_value(token)
|
11
|
+
value(token)
|
12
|
+
end
|
13
13
|
end
|
14
|
-
end
|
15
|
-
|
16
|
-
def self.extract(path, contents, &block)
|
17
|
-
ast = Parser::CurrentRuby.parse(contents)
|
18
|
-
extract_tree(ast, [], &block) unless ast.nil?
|
19
|
-
end
|
20
14
|
|
21
|
-
|
22
|
-
extract_node(tree, scope, &block)
|
15
|
+
VERSION = 2
|
23
16
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
17
|
+
def self.match_file(name)
|
18
|
+
return true if name.end_with?('.rb', '.rake')
|
19
|
+
File.open(name) do |f|
|
20
|
+
head = f.read(2)
|
21
|
+
return false if head.nil? || !head.start_with?('#!')
|
22
|
+
return f.readline.include?('ruby')
|
23
|
+
end
|
28
24
|
end
|
29
25
|
|
30
|
-
|
26
|
+
def self.extract(path, contents, &block)
|
27
|
+
buffer = Parser::Source::Buffer.new(path, 1)
|
28
|
+
buffer.source = contents.force_encoding(Encoding::UTF_8)
|
31
29
|
|
32
|
-
|
33
|
-
|
30
|
+
parser = Parser::CurrentRuby.new(Builder.new)
|
31
|
+
parser.diagnostics.ignore_warnings = true
|
32
|
+
parser.diagnostics.all_errors_are_fatal = false
|
34
33
|
|
35
|
-
|
36
|
-
|
34
|
+
ast = parser.parse(buffer)
|
35
|
+
extract_tree(ast, [], &block) unless ast.nil?
|
36
|
+
end
|
37
37
|
|
38
|
-
|
39
|
-
|
40
|
-
name = scoped_name(node, scope)
|
41
|
-
yield :calls, name, line_no: loc.line, col: loc.column
|
38
|
+
def self.extract_tree(tree, scope, &block)
|
39
|
+
extract_node(tree, scope, &block)
|
42
40
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
yield :requires, node.children[2].children[0].split('/'),
|
48
|
-
line_no: loc.line, col: loc.column
|
41
|
+
new_scope = []
|
42
|
+
if [:class, :module].include? tree.type
|
43
|
+
new_scope = scoped_name(tree.children[0], scope)
|
44
|
+
scope += new_scope
|
49
45
|
end
|
50
46
|
|
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
|
-
|
47
|
+
tree.children.each { |node| extract_tree(node, scope, &block) if node.is_a? AST::Node }
|
48
|
+
|
49
|
+
scope.pop(new_scope.count)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.extract_node(node, scope)
|
53
|
+
loc = node.location
|
54
|
+
|
55
|
+
case node.type
|
56
|
+
when :send
|
57
|
+
name = scoped_name(node, scope)
|
58
|
+
yield :calls, name, line_no: loc.line, col: loc.column
|
59
|
+
|
60
|
+
if name.last =~ /\w+=$/
|
61
|
+
name[-1] = name.last.to_s.chop.to_sym
|
62
|
+
yield :assigns, name, line_no: loc.line, col: loc.column
|
63
|
+
elsif node.children[0].nil? && node.children[1] == :require && node.children[2].type == :str
|
64
|
+
yield :requires, node.children[2].children[0].split('/'),
|
65
|
+
line_no: loc.line, col: loc.column
|
66
|
+
end
|
67
|
+
|
68
|
+
when :def
|
69
|
+
yield :defs, scope + [node.children[0]],
|
70
|
+
line_no: loc.line, type: :func, col: loc.name.column
|
71
|
+
yield :end, :end, line_no: loc.end.line, type: :func, col: loc.end.column
|
72
|
+
|
73
|
+
when :defs
|
74
|
+
yield :defs, scope + [node.children[1]],
|
75
|
+
line_no: loc.line, type: :func, col: loc.name.column
|
76
|
+
yield :end, :end, line_no: loc.end.line, type: :func, col: loc.end.column
|
77
|
+
|
78
|
+
when :module, :class
|
79
|
+
yield :defs, scope + scoped_name(node.children[0], scope),
|
80
|
+
line_no: loc.line, type: node.type, col: loc.name.column
|
81
|
+
yield :end, :end, line_no: loc.end.line, type: node.type, col: loc.end.column
|
82
|
+
|
83
|
+
when :casgn
|
84
|
+
name = scoped_name(node, scope)
|
85
|
+
yield :assigns, name, line_no: loc.line, col: loc.name.column
|
86
|
+
yield :defs, name, line_no: loc.line, col: loc.name.column
|
87
|
+
|
88
|
+
when :lvasgn, :ivasgn, :cvasgn, :gvasgn
|
89
|
+
yield :assigns, scope + [node.children[0]], line_no: loc.line, col: loc.name.column
|
90
|
+
|
91
|
+
when :const
|
92
|
+
name = scoped_name(node, scope)
|
93
|
+
yield :reads, name, line_no: loc.line, col: loc.name.column
|
94
|
+
|
95
|
+
when :lvar, :ivar, :cvar, :gvar
|
96
|
+
yield :reads, scope + [node.children[0]], line_no: loc.line, col: loc.name.column
|
97
|
+
|
98
|
+
when :sym
|
99
|
+
# handle `:foo` vs `foo: 1`
|
100
|
+
col = if loc.begin
|
101
|
+
loc.begin.column + 1
|
102
|
+
else
|
103
|
+
loc.expression.column
|
104
|
+
end
|
105
|
+
yield :sym, [node.children[0]], line_no: loc.line, col: col
|
106
|
+
end
|
89
107
|
end
|
90
|
-
end
|
91
108
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
109
|
+
def self.scoped_name(node, scope)
|
110
|
+
if node.type == :block
|
111
|
+
scoped_name(node.children[0], scope)
|
112
|
+
elsif [:lvar, :ivar, :cvar, :gvar, :const, :send, :casgn].include? node.type
|
113
|
+
if node.children[0].is_a? Symbol
|
114
|
+
[node.children[0]]
|
115
|
+
elsif node.children[0].is_a? AST::Node
|
116
|
+
scoped_name(node.children[0], scope) << node.children[1]
|
117
|
+
elsif node.children[0].nil?
|
118
|
+
if node.type == :const
|
119
|
+
[node.children[1]]
|
120
|
+
else
|
121
|
+
scope + [node.children[1]]
|
122
|
+
end
|
105
123
|
end
|
124
|
+
else
|
125
|
+
[node.type]
|
106
126
|
end
|
107
|
-
else
|
108
|
-
[node.type]
|
109
127
|
end
|
110
128
|
end
|
111
129
|
end
|