solargraph-arc 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
- data/.github/workflows/ruby.yml +41 -0
- data/.gitignore +7 -0
- data/.projections.json +5 -0
- data/.rspec +1 -0
- data/.solargraph.yml +19 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +107 -0
- data/README.md +76 -0
- data/coverage/rails5.json +114 -0
- data/coverage/rails6.json +114 -0
- data/lib/solargraph/arc/annotations.rb +60 -0
- data/lib/solargraph/arc/autoload.rb +48 -0
- data/lib/solargraph/arc/debug.rb +30 -0
- data/lib/solargraph/arc/delegate.rb +34 -0
- data/lib/solargraph/arc/devise.rb +94 -0
- data/lib/solargraph/arc/patches.rb +50 -0
- data/lib/solargraph/arc/rails_api.rb +90 -0
- data/lib/solargraph/arc/relation.rb +60 -0
- data/lib/solargraph/arc/schema.rb +87 -0
- data/lib/solargraph/arc/storage.rb +42 -0
- data/lib/solargraph/arc/types.yml +22 -0
- data/lib/solargraph/arc/util.rb +61 -0
- data/lib/solargraph/arc/version.rb +5 -0
- data/lib/solargraph/arc/walker.rb +89 -0
- data/lib/solargraph-arc.rb +63 -0
- data/script/find_sources.rb +33 -0
- data/script/generate_definitions.rb +133 -0
- data/solargraph-arc.gemspec +43 -0
- data/tmp/.gitkeep +0 -0
- metadata +161 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
module Solargraph
|
2
|
+
module Arc
|
3
|
+
class Delegate
|
4
|
+
def self.instance
|
5
|
+
@instance ||= self.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def process(source_map, ns)
|
9
|
+
return [] unless source_map.code.include?("delegate")
|
10
|
+
|
11
|
+
walker = Walker.from_source(source_map.source)
|
12
|
+
pins = []
|
13
|
+
|
14
|
+
walker.on :send, [nil, :delegate] do |ast|
|
15
|
+
methods = ast.children[2..-1]
|
16
|
+
.map {|c| c.children.first }
|
17
|
+
.select {|s| s.is_a?(Symbol) }
|
18
|
+
|
19
|
+
methods.each do |meth|
|
20
|
+
pins << Util.build_public_method(
|
21
|
+
ns,
|
22
|
+
meth.to_s,
|
23
|
+
location: Util.build_location(ast, ns.filename)
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
walker.walk
|
29
|
+
Solargraph.logger.debug("[ARC][Delegate] added #{pins.map(&:name)} to #{ns.path}")
|
30
|
+
pins
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Solargraph
|
2
|
+
module Arc
|
3
|
+
class Devise
|
4
|
+
def self.instance
|
5
|
+
@instance ||= self.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@seen_devise_closures = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def process(source_map, ns)
|
13
|
+
if source_map.filename.include?("app/models")
|
14
|
+
process_model(source_map, ns)
|
15
|
+
elsif source_map.filename.end_with?("app/controllers/application_controller.rb")
|
16
|
+
process_controller(source_map, ns)
|
17
|
+
else
|
18
|
+
[]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def process_model(source_map, ns)
|
25
|
+
walker = Walker.from_source(source_map.source)
|
26
|
+
pins = []
|
27
|
+
|
28
|
+
walker.on :send, [nil, :devise] do |ast|
|
29
|
+
@seen_devise_closures << ns
|
30
|
+
|
31
|
+
modules = ast.children[2..-1]
|
32
|
+
.map {|c| c.children.first }
|
33
|
+
.select {|s| s.is_a?(Symbol) }
|
34
|
+
|
35
|
+
modules.each do |mod|
|
36
|
+
pins << Util.build_module_include(
|
37
|
+
ns,
|
38
|
+
"Devise::Models::#{mod.to_s.capitalize}",
|
39
|
+
Util.build_location(ast, ns.filename)
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
walker.walk
|
45
|
+
Solargraph.logger.debug("[ARC][Devise] added #{pins.map(&:name)} to #{ns.path}") if pins.any?
|
46
|
+
pins
|
47
|
+
end
|
48
|
+
|
49
|
+
def process_controller(source_map, ns)
|
50
|
+
pins = [
|
51
|
+
Util.build_module_include(
|
52
|
+
ns,
|
53
|
+
"Devise::Controllers::Helpers",
|
54
|
+
Util.dummy_location(ns.filename)
|
55
|
+
)
|
56
|
+
]
|
57
|
+
|
58
|
+
mapping_pins = @seen_devise_closures.map do |model_ns|
|
59
|
+
ast = Walker.normalize_ast(source_map.source)
|
60
|
+
mapping = model_ns.name.underscore
|
61
|
+
|
62
|
+
[
|
63
|
+
Util.build_public_method(
|
64
|
+
ns,
|
65
|
+
"authenticate_#{mapping}!",
|
66
|
+
location: Util.build_location(ast, ns.filename)
|
67
|
+
),
|
68
|
+
Util.build_public_method(
|
69
|
+
ns,
|
70
|
+
"#{mapping}_signed_in?",
|
71
|
+
types: ["true", "false"],
|
72
|
+
location: Util.build_location(ast, ns.filename)
|
73
|
+
),
|
74
|
+
Util.build_public_method(
|
75
|
+
ns,
|
76
|
+
"current_#{mapping}",
|
77
|
+
types: [model_ns.name, "nil"],
|
78
|
+
location: Util.build_location(ast, ns.filename)
|
79
|
+
),
|
80
|
+
Util.build_public_method(
|
81
|
+
ns,
|
82
|
+
"#{mapping}_session",
|
83
|
+
location: Util.build_location(ast, ns.filename)
|
84
|
+
)
|
85
|
+
]
|
86
|
+
end.flatten
|
87
|
+
|
88
|
+
pins += mapping_pins
|
89
|
+
Solargraph.logger.debug("[ARC][Devise] added #{pins.map(&:name)} to #{ns.path}") if pins.any?
|
90
|
+
pins
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Solargraph
|
2
|
+
class ApiMap
|
3
|
+
# TODO: https://github.com/castwide/solargraph/pull/512
|
4
|
+
def get_complex_type_methods complex_type, context = '', internal = false
|
5
|
+
# This method does not qualify the complex type's namespace because
|
6
|
+
# it can cause conflicts between similar names, e.g., `Foo` vs.
|
7
|
+
# `Other::Foo`. It still takes a context argument to determine whether
|
8
|
+
# protected and private methods are visible.
|
9
|
+
return [] if complex_type.undefined? || complex_type.void?
|
10
|
+
result = Set.new
|
11
|
+
complex_type.each do |type|
|
12
|
+
if type.duck_type?
|
13
|
+
result.add Pin::DuckMethod.new(name: type.to_s[1..-1])
|
14
|
+
result.merge get_methods('Object')
|
15
|
+
else
|
16
|
+
unless type.nil? || type.name == 'void'
|
17
|
+
visibility = [:public]
|
18
|
+
if type.namespace == context || super_and_sub?(type.namespace, context)
|
19
|
+
visibility.push :protected
|
20
|
+
visibility.push :private if internal
|
21
|
+
end
|
22
|
+
result.merge get_methods(type.namespace, scope: type.scope, visibility: visibility)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
result.to_a
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class YardMap
|
31
|
+
# TODO: remove after https://github.com/castwide/solargraph/pull/509 is merged
|
32
|
+
def spec_for_require path
|
33
|
+
name = path.split('/').first
|
34
|
+
spec = Gem::Specification.find_by_name(name, @gemset[name])
|
35
|
+
|
36
|
+
# Avoid loading the spec again if it's going to be skipped anyway
|
37
|
+
#
|
38
|
+
return spec if @source_gems.include?(spec.name)
|
39
|
+
# Avoid loading the spec again if it's already the correct version
|
40
|
+
if @gemset[spec.name] && @gemset[spec.name] != spec.version
|
41
|
+
begin
|
42
|
+
return Gem::Specification.find_by_name(spec.name, "= #{@gemset[spec.name]}")
|
43
|
+
rescue Gem::LoadError
|
44
|
+
Solargraph.logger.warn "Unable to load #{spec.name} #{@gemset[spec.name]} specified by workspace, using #{spec.version} instead"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
spec
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Solargraph
|
2
|
+
module Arc
|
3
|
+
class RailsApi
|
4
|
+
def self.instance
|
5
|
+
@instance ||= self.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def global yard_map
|
9
|
+
return [] if yard_map.required.empty?
|
10
|
+
|
11
|
+
ann = File.read(File.dirname(__FILE__) + "/annotations.rb")
|
12
|
+
source = Solargraph::Source.load_string(ann, "annotations.rb")
|
13
|
+
map = Solargraph::SourceMap.map(source)
|
14
|
+
|
15
|
+
Solargraph.logger.debug("[Arc][Rails] found #{map.pins.size} pins in annotations")
|
16
|
+
|
17
|
+
overrides = YAML.load_file(File.dirname(__FILE__) + "/types.yml").map do |meth, data|
|
18
|
+
if data["return"]
|
19
|
+
Util.method_return(meth, data["return"])
|
20
|
+
elsif data["yieldself"]
|
21
|
+
Solargraph::Pin::Reference::Override.from_comment(
|
22
|
+
meth,
|
23
|
+
"@yieldself [#{data['yieldself'].join(',')}]"
|
24
|
+
)
|
25
|
+
elsif data["yieldparam"]
|
26
|
+
Solargraph::Pin::Reference::Override.from_comment(
|
27
|
+
meth,
|
28
|
+
"@yieldparam [#{data['yieldparam'].join(',')}]"
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
ns = Solargraph::Pin::Namespace.new(
|
34
|
+
name: "ActionController::Base",
|
35
|
+
gates: ["ActionController::Base"]
|
36
|
+
)
|
37
|
+
|
38
|
+
definitions = [
|
39
|
+
Util.build_public_method(
|
40
|
+
ns,
|
41
|
+
"response",
|
42
|
+
types: ["ActionDispatch::Response"],
|
43
|
+
location: Util.dummy_location("whatever.rb")
|
44
|
+
),
|
45
|
+
Util.build_public_method(
|
46
|
+
ns,
|
47
|
+
"request",
|
48
|
+
types: ["ActionDispatch::Request"],
|
49
|
+
location: Util.dummy_location("whatever.rb")
|
50
|
+
),
|
51
|
+
Util.build_public_method(
|
52
|
+
ns,
|
53
|
+
"session",
|
54
|
+
types: ["ActionDispatch::Request::Session"],
|
55
|
+
location: Util.dummy_location("whatever.rb")
|
56
|
+
),
|
57
|
+
Util.build_public_method(
|
58
|
+
ns,
|
59
|
+
"flash",
|
60
|
+
types: ["ActionDispatch::Flash::FlashHash"],
|
61
|
+
location: Util.dummy_location("whatever.rb")
|
62
|
+
)
|
63
|
+
]
|
64
|
+
|
65
|
+
map.pins + definitions + overrides
|
66
|
+
end
|
67
|
+
|
68
|
+
def local(source_map, ns)
|
69
|
+
return [] unless source_map.filename.include?("db/migrate")
|
70
|
+
node = Walker.normalize_ast(source_map.source)
|
71
|
+
|
72
|
+
pins = [
|
73
|
+
Util.build_module_include(
|
74
|
+
ns,
|
75
|
+
"ActiveRecord::ConnectionAdapters::SchemaStatements",
|
76
|
+
Util.build_location(node, ns.filename)
|
77
|
+
),
|
78
|
+
Util.build_module_extend(
|
79
|
+
ns,
|
80
|
+
"ActiveRecord::ConnectionAdapters::SchemaStatements",
|
81
|
+
Util.build_location(node, ns.filename)
|
82
|
+
)
|
83
|
+
]
|
84
|
+
|
85
|
+
Solargraph.logger.debug("[ARC][RailsApi] added #{pins.map(&:name)} to #{ns.path}")
|
86
|
+
pins
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Solargraph
|
2
|
+
module Arc
|
3
|
+
class Relation
|
4
|
+
def self.instance
|
5
|
+
@instance ||= self.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def process(source_map, ns)
|
9
|
+
return [] unless source_map.filename.include?("app/models")
|
10
|
+
|
11
|
+
walker = Walker.from_source(source_map.source)
|
12
|
+
pins = []
|
13
|
+
|
14
|
+
walker.on :send, [nil, :belongs_to] do |ast|
|
15
|
+
pins << singular_association(ns, ast)
|
16
|
+
end
|
17
|
+
|
18
|
+
walker.on :send, [nil, :has_one] do |ast|
|
19
|
+
pins << singular_association(ns, ast)
|
20
|
+
end
|
21
|
+
|
22
|
+
walker.on :send, [nil, :has_many] do |ast|
|
23
|
+
pins << plural_association(ns, ast)
|
24
|
+
end
|
25
|
+
|
26
|
+
walker.on :send, [nil, :has_and_belongs_to_many] do |ast|
|
27
|
+
pins << plural_association(ns, ast)
|
28
|
+
end
|
29
|
+
|
30
|
+
walker.walk
|
31
|
+
Solargraph.logger.debug("[ARC][Relation] added #{pins.map(&:name)} to #{ns.path}") if pins.any?
|
32
|
+
pins
|
33
|
+
end
|
34
|
+
|
35
|
+
# TODO: handle custom class for relation
|
36
|
+
def plural_association(ns, ast)
|
37
|
+
relation_name = ast.children[2].children.first
|
38
|
+
|
39
|
+
Util.build_public_method(
|
40
|
+
ns,
|
41
|
+
relation_name.to_s,
|
42
|
+
types: ["ActiveRecord::Associations::CollectionProxy<#{relation_name.to_s.singularize.camelize}>"],
|
43
|
+
location: Util.build_location(ast, ns.filename)
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
# TODO: handle custom class for relation
|
48
|
+
def singular_association(ns, ast)
|
49
|
+
relation_name = ast.children[2].children.first
|
50
|
+
|
51
|
+
Util.build_public_method(
|
52
|
+
ns,
|
53
|
+
relation_name.to_s,
|
54
|
+
types: [relation_name.to_s.camelize],
|
55
|
+
location: Util.build_location(ast, ns.filename)
|
56
|
+
)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Solargraph
|
2
|
+
module Arc
|
3
|
+
class Schema
|
4
|
+
ColumnData = Struct.new(:type, :ast)
|
5
|
+
|
6
|
+
RUBY_TYPES = {
|
7
|
+
decimal: 'BigDecimal',
|
8
|
+
float: 'BigDecimal',
|
9
|
+
integer: 'Integer',
|
10
|
+
date: 'Date',
|
11
|
+
datetime: 'ActiveSupport::TimeWithZone',
|
12
|
+
string: 'String',
|
13
|
+
boolean: 'Boolean',
|
14
|
+
text: 'String',
|
15
|
+
jsonb: 'Hash',
|
16
|
+
bigint: 'Integer',
|
17
|
+
inet: 'IPAddr'
|
18
|
+
}
|
19
|
+
|
20
|
+
def self.instance
|
21
|
+
@instance ||= self.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@schema_present = File.exist?("db/schema.rb")
|
26
|
+
end
|
27
|
+
|
28
|
+
def process(source_map, ns)
|
29
|
+
return [] unless @schema_present
|
30
|
+
return [] unless source_map.filename.include?("app/models")
|
31
|
+
|
32
|
+
table_name = infer_table_name(ns)
|
33
|
+
table = schema[table_name]
|
34
|
+
|
35
|
+
return [] unless table
|
36
|
+
|
37
|
+
pins = table.map do |column, data|
|
38
|
+
Util.build_public_method(
|
39
|
+
ns,
|
40
|
+
column,
|
41
|
+
types: [RUBY_TYPES.fetch(data.type.to_sym)],
|
42
|
+
location: Util.build_location(data.ast, "db/schema.rb")
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
Solargraph.logger.debug("[ARC][Schema] added #{pins.map(&:name)} to #{ns.path}") if pins.any?
|
47
|
+
pins
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def schema
|
53
|
+
@extracted_schema ||= begin
|
54
|
+
ast = NodeParser.parse(File.read("db/schema.rb"), "db/schema.rb")
|
55
|
+
extract_schema(ast)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# TODO: support custom table names, by parsing `self.table_name = ` invokations
|
60
|
+
# inside model
|
61
|
+
def infer_table_name(ns)
|
62
|
+
ns.name.underscore.pluralize
|
63
|
+
end
|
64
|
+
|
65
|
+
def extract_schema(ast)
|
66
|
+
schema = {}
|
67
|
+
|
68
|
+
walker = Walker.new(ast)
|
69
|
+
walker.on :block, [:send, nil, :create_table] do |ast, query|
|
70
|
+
table_name = ast.children.first.children[2].children.last
|
71
|
+
schema[table_name] = {}
|
72
|
+
|
73
|
+
query.on :send, [:lvar, :t] do |column_ast|
|
74
|
+
name = column_ast.children[2].children.last
|
75
|
+
type = column_ast.children[1]
|
76
|
+
|
77
|
+
next if type == :index
|
78
|
+
schema[table_name][name] = ColumnData.new(type, column_ast)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
walker.walk
|
83
|
+
schema
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Solargraph
|
2
|
+
module Arc
|
3
|
+
class Storage
|
4
|
+
def self.instance
|
5
|
+
@instance ||= self.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def process(source_map, ns)
|
9
|
+
return [] unless source_map.filename.include?("app/models")
|
10
|
+
|
11
|
+
walker = Walker.from_source(source_map.source)
|
12
|
+
pins = []
|
13
|
+
|
14
|
+
walker.on :send, [nil, :has_one_attached] do |ast|
|
15
|
+
name = ast.children[2].children.first
|
16
|
+
|
17
|
+
pins << Util.build_public_method(
|
18
|
+
ns,
|
19
|
+
name.to_s,
|
20
|
+
types: ["ActiveStorage::Attached::One"],
|
21
|
+
location: Util.build_location(ast, ns.filename)
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
walker.on :send, [nil, :has_many_attached] do |ast|
|
26
|
+
name = ast.children[2].children.first
|
27
|
+
|
28
|
+
pins << Util.build_public_method(
|
29
|
+
ns,
|
30
|
+
name.to_s,
|
31
|
+
types: ["ActiveStorage::Attached::Many"],
|
32
|
+
location: Util.build_location(ast, ns.filename)
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
walker.walk
|
37
|
+
Solargraph.logger.debug("[ARC][Storage] added #{pins.map(&:name)} to #{ns.path}") if pins.any?
|
38
|
+
pins
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
ActionController::Metal#params:
|
2
|
+
return: ["ActionController::Parameters"]
|
3
|
+
ActionController::Cookies#cookies:
|
4
|
+
return: ["ActionDispatch::Cookies::CookieJar"]
|
5
|
+
ActionDispatch::Flash::FlashHash#now:
|
6
|
+
return: ["ActionDispatch::Flash::FlashNow"]
|
7
|
+
ActiveRecord::QueryMethods#where:
|
8
|
+
return: ["self", "ActiveRecord::Relation", "ActiveRecord::QueryMethods::WhereChain"]
|
9
|
+
ActiveRecord::QueryMethods#not:
|
10
|
+
return: ["ActiveRecord::QueryMethods::WhereChain"]
|
11
|
+
ActiveRecord::FinderMethods#find_by:
|
12
|
+
return: ["self", "nil"]
|
13
|
+
Rails.application:
|
14
|
+
return: ["Rails::Application"]
|
15
|
+
ActionDispatch::Routing::RouteSet#draw:
|
16
|
+
yieldself: ["ActionDispatch::Routing::Mapper"]
|
17
|
+
ActiveRecord::ConnectionAdapters::SchemaStatements#create_table:
|
18
|
+
yieldparam: ["ActiveRecord::ConnectionAdapters::TableDefinition"]
|
19
|
+
ActiveRecord::ConnectionAdapters::SchemaStatements#create_join_table:
|
20
|
+
yieldparam: ["ActiveRecord::ConnectionAdapters::TableDefinition"]
|
21
|
+
ActiveRecord::ConnectionAdapters::SchemaStatements#change_table:
|
22
|
+
yieldparam: ["ActiveRecord::ConnectionAdapters::Table"]
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Solargraph
|
2
|
+
module Arc
|
3
|
+
module Util
|
4
|
+
def self.build_public_method(ns, name, types: nil, location: nil, attribute: false, scope: :instance)
|
5
|
+
opts = {
|
6
|
+
name: name,
|
7
|
+
location: location,
|
8
|
+
closure: ns,
|
9
|
+
scope: scope,
|
10
|
+
attribute: attribute
|
11
|
+
}
|
12
|
+
|
13
|
+
comments = []
|
14
|
+
comments << "@return [#{types.join(',')}]" if types
|
15
|
+
|
16
|
+
opts[:comments] = comments.join("\n")
|
17
|
+
|
18
|
+
Solargraph::Pin::Method.new(**opts)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.build_module_include(ns, module_name, location)
|
22
|
+
Solargraph::Pin::Reference::Include.new(
|
23
|
+
closure: ns,
|
24
|
+
name: module_name,
|
25
|
+
location: location
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.build_module_extend(ns, module_name, location)
|
30
|
+
Solargraph::Pin::Reference::Extend.new(
|
31
|
+
closure: ns,
|
32
|
+
name: module_name,
|
33
|
+
location: location
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.dummy_location(path)
|
38
|
+
Solargraph::Location.new(
|
39
|
+
path,
|
40
|
+
Solargraph::Range.from_to(0, 0, 0, 0)
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.build_location(ast, path)
|
45
|
+
Solargraph::Location.new(
|
46
|
+
path,
|
47
|
+
Solargraph::Range.from_to(
|
48
|
+
ast.location.first_line,
|
49
|
+
0,
|
50
|
+
ast.location.last_line,
|
51
|
+
ast.location.column
|
52
|
+
)
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.method_return(path, type)
|
57
|
+
Solargraph::Pin::Reference::Override.method_return(path, type)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Solargraph
|
2
|
+
module Arc
|
3
|
+
class Walker
|
4
|
+
class Hook
|
5
|
+
attr_reader :args, :proc, :node_type
|
6
|
+
def initialize(node_type, args, &block)
|
7
|
+
@node_type = node_type
|
8
|
+
@args = args
|
9
|
+
@proc = Proc.new(&block)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# https://github.com/castwide/solargraph/issues/522
|
14
|
+
def self.normalize_ast(source)
|
15
|
+
ast = source.node
|
16
|
+
|
17
|
+
if ast.is_a?(::Parser::AST::Node)
|
18
|
+
ast
|
19
|
+
else
|
20
|
+
NodeParser.parse(source.code, source.filename)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.from_source(source)
|
25
|
+
self.new(self.normalize_ast(source))
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(ast)
|
29
|
+
@ast = ast
|
30
|
+
@hooks = Hash.new([])
|
31
|
+
end
|
32
|
+
|
33
|
+
def on(node_type, args=[], &block)
|
34
|
+
@hooks[node_type] << Hook.new(node_type, args, &block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def walk
|
38
|
+
if @ast.is_a?(Array)
|
39
|
+
@ast.each { |node| traverse(node) }
|
40
|
+
else
|
41
|
+
traverse(@ast)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def traverse(node)
|
48
|
+
return unless node.is_a?(::Parser::AST::Node)
|
49
|
+
|
50
|
+
@hooks[node.type].each do |hook|
|
51
|
+
try_match(node, hook)
|
52
|
+
end
|
53
|
+
|
54
|
+
node.children.each {|child| traverse(child) }
|
55
|
+
end
|
56
|
+
|
57
|
+
def try_match(node, hook)
|
58
|
+
return unless node.type == hook.node_type
|
59
|
+
return unless node.children
|
60
|
+
|
61
|
+
matched = hook.args.empty? || if node.children.first.is_a?(::Parser::AST::Node)
|
62
|
+
node.children.any? { |child| child.is_a?(::Parser::AST::Node) && match_children(hook.args[1..-1], child.children) }
|
63
|
+
else
|
64
|
+
match_children(hook.args, node.children)
|
65
|
+
end
|
66
|
+
|
67
|
+
if matched
|
68
|
+
if hook.proc.arity == 1
|
69
|
+
hook.proc.call(node)
|
70
|
+
elsif hook.proc.arity == 2
|
71
|
+
walker = Walker.new(node)
|
72
|
+
hook.proc.call(node, walker)
|
73
|
+
walker.walk
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def match_children(args, children)
|
79
|
+
args.each_with_index.all? do |arg, i|
|
80
|
+
if children[i].is_a?(::Parser::AST::Node)
|
81
|
+
children[i].type == arg
|
82
|
+
else
|
83
|
+
children[i] == arg
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|