solargraph-arc 0.2.0
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 +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
|