solargraph 0.61.0 → 0.62.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.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/linting.yml +24 -2
  3. data/.github/workflows/plugins.yml +18 -0
  4. data/.github/workflows/rspec.yml +14 -1
  5. data/.github/workflows/typecheck.yml +6 -0
  6. data/CHANGELOG.md +38 -0
  7. data/lib/solargraph/api_map/index.rb +14 -3
  8. data/lib/solargraph/api_map/store.rb +6 -0
  9. data/lib/solargraph/api_map.rb +34 -2
  10. data/lib/solargraph/collection/gem.rb +12 -10
  11. data/lib/solargraph/collection.rb +0 -1
  12. data/lib/solargraph/complex_type/unique_type.rb +13 -0
  13. data/lib/solargraph/complex_type.rb +23 -3
  14. data/lib/solargraph/convention.rb +0 -4
  15. data/lib/solargraph/diagnostics/type_check.rb +0 -1
  16. data/lib/solargraph/external.rb +56 -28
  17. data/lib/solargraph/language_server/host/diagnoser.rb +1 -1
  18. data/lib/solargraph/language_server/host/dispatch.rb +4 -2
  19. data/lib/solargraph/language_server/host.rb +10 -2
  20. data/lib/solargraph/language_server/message/completion_item/resolve.rb +10 -3
  21. data/lib/solargraph/language_server/message/text_document/definition.rb +1 -1
  22. data/lib/solargraph/library.rb +19 -33
  23. data/lib/solargraph/parser/node_processor/base.rb +6 -0
  24. data/lib/solargraph/parser/parser_gem/node_methods.rb +29 -6
  25. data/lib/solargraph/parser/parser_gem/node_processors/class_definition/class_assignment_node.rb +98 -0
  26. data/lib/solargraph/parser/parser_gem/node_processors/class_definition.rb +54 -0
  27. data/lib/solargraph/parser/parser_gem/node_processors/cvasgn_node.rb +1 -0
  28. data/lib/solargraph/parser/parser_gem/node_processors/data_definition/data_assignment_node.rb +65 -0
  29. data/lib/solargraph/parser/parser_gem/node_processors/data_definition/data_definition_node.rb +97 -0
  30. data/lib/solargraph/parser/parser_gem/node_processors/data_definition.rb +110 -0
  31. data/lib/solargraph/parser/parser_gem/node_processors/gvasgn_node.rb +1 -0
  32. data/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb +2 -0
  33. data/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb +1 -0
  34. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +185 -0
  35. data/lib/solargraph/parser/parser_gem/node_processors/struct_definition/struct_assignment_node.rb +66 -0
  36. data/lib/solargraph/parser/parser_gem/node_processors/struct_definition/struct_definition_node.rb +108 -0
  37. data/lib/solargraph/parser/parser_gem/node_processors/struct_definition.rb +170 -0
  38. data/lib/solargraph/parser/parser_gem/node_processors.rb +33 -29
  39. data/lib/solargraph/pin/base.rb +25 -1
  40. data/lib/solargraph/pin/base_variable.rb +31 -1
  41. data/lib/solargraph/pin/callable.rb +11 -2
  42. data/lib/solargraph/pin/delegated_method.rb +5 -0
  43. data/lib/solargraph/pin/duck_method.rb +36 -16
  44. data/lib/solargraph/pin/instance_variable.rb +45 -0
  45. data/lib/solargraph/pin/method.rb +10 -0
  46. data/lib/solargraph/pin/method_alias.rb +4 -0
  47. data/lib/solargraph/pin/parameter.rb +1 -1
  48. data/lib/solargraph/pin/reference/included_mixin.rb +22 -0
  49. data/lib/solargraph/pin/reference.rb +1 -0
  50. data/lib/solargraph/plugin/active_support.rb +100 -0
  51. data/lib/solargraph/plugin/addon.rb +16 -0
  52. data/lib/solargraph/plugin/base.rb +25 -0
  53. data/lib/solargraph/plugin/panel.rb +37 -0
  54. data/lib/solargraph/plugin.rb +26 -0
  55. data/lib/solargraph/rbs_collection.rb +119 -0
  56. data/lib/solargraph/rbs_map/conversions.rb +106 -47
  57. data/lib/solargraph/rbs_map/gem.rb +4 -1
  58. data/lib/solargraph/rbs_map/helpers.rb +7 -6
  59. data/lib/solargraph/rbs_translator.rb +105 -70
  60. data/lib/solargraph/repo.rb +1 -1
  61. data/lib/solargraph/source/chain/call.rb +32 -13
  62. data/lib/solargraph/source/node_map.rb +27 -0
  63. data/lib/solargraph/source.rb +17 -0
  64. data/lib/solargraph/source_map/call.rb +18 -0
  65. data/lib/solargraph/source_map/mapper.rb +1 -1
  66. data/lib/solargraph/source_map.rb +18 -4
  67. data/lib/solargraph/type_checker/rules.rb +2 -2
  68. data/lib/solargraph/type_checker.rb +4 -1
  69. data/lib/solargraph/version.rb +1 -1
  70. data/lib/solargraph/yard_map/macro.rb +51 -1
  71. data/lib/solargraph.rb +2 -0
  72. data/rbs/fills/boolean/0/boolean.rbs +12 -0
  73. data/solargraph.gemspec +1 -1
  74. metadata +21 -11
  75. data/lib/solargraph/collection/rbs.rb +0 -49
  76. data/lib/solargraph/convention/active_support_concern.rb +0 -111
  77. data/lib/solargraph/convention/data_definition/data_assignment_node.rb +0 -61
  78. data/lib/solargraph/convention/data_definition/data_definition_node.rb +0 -93
  79. data/lib/solargraph/convention/data_definition.rb +0 -108
  80. data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +0 -62
  81. data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +0 -103
  82. data/lib/solargraph/convention/struct_definition.rb +0 -168
@@ -118,11 +118,13 @@ module Solargraph
118
118
  .tap { |lib| lib.add_observer self }
119
119
  end
120
120
 
121
- # @param progress [Solargraph::LanguageServer::Progress, nil]
121
+ # The Library observer callback that #generic_library registers.
122
122
  #
123
+ # @abstract
124
+ # @param progress [Solargraph::LanguageServer::Progress, nil]
123
125
  # @return [void]
124
126
  def update progress
125
- progress&.send(self)
127
+ raise NotImplementedError, 'Host::Dispatch requires an update method'
126
128
  end
127
129
  end
128
130
  end
@@ -495,7 +495,7 @@ module Solargraph
495
495
  )
496
496
  result.concat(library.locate_pins(location).select { |pin| pin.name == params['label'] })
497
497
  end
498
- if params['data']['path']
498
+ if params['data']['path'] && !params['data']['path'].empty?
499
499
  result.concat library.path_pins(params['data']['path'])
500
500
  # @todo This exception is necessary because `Library#path_pins` does
501
501
  # not perform a namespace method query, so the implicit `.new` pin
@@ -706,6 +706,15 @@ module Solargraph
706
706
  client_capabilities['window'] && client_capabilities['window']['workDoneProgress']
707
707
  end
708
708
 
709
+ # The Library observer callback that sends a library's progress to
710
+ # the client.
711
+ #
712
+ # @param progress [Solargraph::LanguageServer::Progress, nil]
713
+ # @return [void]
714
+ def update progress
715
+ progress&.send(self)
716
+ end
717
+
709
718
  private
710
719
 
711
720
  # @return [Array<Integer>]
@@ -736,7 +745,6 @@ module Solargraph
736
745
  # @return [String]
737
746
  def normalize_separators path
738
747
  return path if File::ALT_SEPARATOR.nil?
739
- # @sg-ignore flow sensitive typing needs to handle constants
740
748
  path.gsub(File::ALT_SEPARATOR, File::SEPARATOR)
741
749
  end
742
750
 
@@ -7,6 +7,12 @@ module Solargraph
7
7
  # completionItem/resolve message handler
8
8
  #
9
9
  class Resolve < Base
10
+ # Properties that must not be changed during completionItem/resolve
11
+ # according to the LSP specification.
12
+ #
13
+ # @see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_completion
14
+ IMMUTABLE_FIELDS = %i[label kind sortText filterText insertText textEdit data].freeze
15
+
10
16
  def process
11
17
  pins = host.locate_pins(params)
12
18
  set_result merge(pins)
@@ -17,14 +23,15 @@ module Solargraph
17
23
  # @param pins [Array<Pin::Base>]
18
24
  # @return [Hash]
19
25
  def merge pins
20
- return params if pins.empty?
26
+ original = params.transform_keys(&:to_sym)
27
+ return original if pins.empty?
21
28
  docs = pins
22
29
  .reject { |pin| pin.documentation.empty? && pin.return_type.undefined? }
23
- result = params
24
- .transform_keys(&:to_sym)
30
+ result = original
25
31
  .merge(pins.first.resolve_completion_item)
26
32
  .merge(documentation: markup_content(join_docs(docs)))
27
33
  result[:detail] = pins.first.detail
34
+ IMMUTABLE_FIELDS.each { |field| result[field] = original[field] }
28
35
  result
29
36
  end
30
37
 
@@ -15,7 +15,7 @@ module Solargraph
15
15
 
16
16
  # @return [Array<Hash>, nil]
17
17
  def code_location
18
- suggestions = host.definitions_at(params['textDocument']['uri'], @line, @column)
18
+ suggestions = host.definitions_at(params['textDocument']['uri'], @line, @column) || []
19
19
  # @sg-ignore Need to add nil check here
20
20
  return nil if suggestions.empty?
21
21
  # @sg-ignore Need to add nil check here
@@ -589,49 +589,35 @@ module Solargraph
589
589
  def cache_next_gemspec
590
590
  return if @cache_progress
591
591
 
592
- spec = api_map.unloaded_gems.first
592
+ spec = (api_map.unloaded_gems - cache_errors).first
593
593
  return end_cache_progress unless spec
594
594
 
595
595
  pending = api_map.unloaded_gems.length - cache_errors.length - 1
596
596
 
597
- if Yardoc.processing?(spec)
598
- logger.info "Enqueuing cache of #{spec.name} #{spec.version} (already being processed)"
599
- queued_gemspec_cache.push(spec)
600
- return if pending - queued_gemspec_cache.length < 1
597
+ logger.info "Caching #{spec.name} #{spec.version}"
598
+ Thread.new do
599
+ report_cache_progress spec.name, pending
601
600
 
602
- # Try the next cacheable gemspec. This method is always called
603
- # from inside sync_catalog's mutex (either directly or via this
604
- # same recursive call), so recursing through sync_catalog here
605
- # would try to re-lock a mutex this thread already holds and
606
- # deadlock (https://github.com/castwide/solargraph/issues/1111).
607
- cache_next_gemspec
608
- else
609
- logger.info "Caching #{spec.name} #{spec.version}"
610
- Thread.new do
611
- report_cache_progress spec.name, pending
612
-
613
- cmd = [workspace.command_path, 'cache', spec.name]
614
- cmd.push('--directory', workspace.directory) if workspace.directory
601
+ cmd = [workspace.command_path, 'cache', spec.name]
602
+ cmd.push('--directory', workspace.directory) if workspace.directory
603
+ begin
615
604
  _o, e, s = Open3.capture3(*cmd)
616
- if s.success?
617
- logger.info "Cached #{spec.name} #{spec.version}"
618
- else
619
- cache_errors.add spec
620
- logger.warn "Error caching gemspec #{spec.name} #{spec.version}"
621
- logger.warn e
622
- end
623
- end_cache_progress
624
- catalog
625
- sync_catalog
605
+ rescue SystemCallError => e
606
+ # The command could not start, e.g. Errno::ENOENT - handled below
626
607
  end
608
+ if s&.success?
609
+ logger.info "Cached #{spec.name} #{spec.version}"
610
+ else
611
+ cache_errors.add spec
612
+ logger.warn "Error caching gemspec #{spec.name} #{spec.version}"
613
+ logger.warn e
614
+ end
615
+ end_cache_progress
616
+ catalog
617
+ sync_catalog
627
618
  end
628
619
  end
629
620
 
630
- # @return [Array<Gem::Specification>]
631
- def queued_gemspec_cache
632
- @queued_gemspec_cache ||= []
633
- end
634
-
635
621
  # @param gem_name [String]
636
622
  # @param pending [Integer]
637
623
  # @return [void]
@@ -91,6 +91,12 @@ module Solargraph
91
91
  region.source.comments_for(node)
92
92
  end
93
93
 
94
+ # @param node [Parser::AST::Node]
95
+ # @return [String, nil]
96
+ def type_assertion_for node
97
+ region.source.type_assertion_for(node)
98
+ end
99
+
94
100
  # @param position [Solargraph::Position]
95
101
  # @return [Pin::Closure, nil]
96
102
  def named_path_pin position
@@ -197,6 +197,18 @@ module Solargraph
197
197
  nodes.any? { |n| splatted_call?(n) }
198
198
  end
199
199
 
200
+ # Anonymous `*`, `**` and `...` arguments pass on whatever the
201
+ # enclosing method received, so neither count nor types are known.
202
+ #
203
+ # @param nodes [Enumerable<Parser::AST::Node>]
204
+ def any_forwarded_args? nodes
205
+ nodes.any? do |n|
206
+ next false unless Parser.is_ast_node?(n)
207
+ next true if %i[forwarded_args forwarded_restarg].include?(n.type)
208
+ n.type == :hash && n.children.any? { |c| Parser.is_ast_node?(c) && c.type == :forwarded_kwrestarg }
209
+ end
210
+ end
211
+
200
212
  # @todo Temporarily here for testing. Move to Solargraph::Parser.
201
213
  # @param node [Parser::AST::Node]
202
214
  # @return [Array<Parser::AST::Node>]
@@ -442,6 +454,11 @@ module Solargraph
442
454
  CONDITIONAL_ALL_BUT_FIRST = %i[if unless].freeze
443
455
  ONLY_ONE_CHILD = [:return].freeze
444
456
  FIRST_TWO_CHILDREN = [:rescue].freeze
457
+ # :ensure's value is always its body's value (first
458
+ # child); the ensure clause itself (second child) never
459
+ # contributes to the method's return value unless it
460
+ # explicitly returns.
461
+ ENSURE = [:ensure].freeze
445
462
  COMPOUND_STATEMENTS = %i[begin kwbegin].freeze
446
463
  SKIPPABLE = %i[def defs class sclass module].freeze
447
464
  FUNCTION_VALUE = [:block].freeze
@@ -488,6 +505,13 @@ module Solargraph
488
505
  result.concat reduce_to_value_nodes([node.children[0]])
489
506
  elsif FIRST_TWO_CHILDREN.include?(node.type)
490
507
  result.concat reduce_to_value_nodes([node.children[0], node.children[1]])
508
+ elsif ENSURE.include?(node.type)
509
+ result.concat reduce_to_value_nodes([node.children[0]])
510
+ ensure_body = node.children[1]
511
+ if include_explicit_returns && ensure_body.is_a?(::Parser::AST::Node)
512
+ # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check
513
+ result.concat explicit_return_values_from_compound_statement(ensure_body)
514
+ end
491
515
  elsif FUNCTION_VALUE.include?(node.type)
492
516
  # the block itself is a first class value that could be returned
493
517
  result.push node
@@ -605,14 +629,13 @@ module Solargraph
605
629
  elsif CONDITIONAL_ALL_BUT_FIRST.include?(node.type)
606
630
  # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check
607
631
  result.concat reduce_to_value_nodes(node.children[1..])
608
- # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check
609
- elsif node.type == :return
632
+ elsif ONLY_ONE_CHILD.include?(node.type) || ENSURE.include?(node.type)
610
633
  # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check
611
634
  result.concat reduce_to_value_nodes([node.children[0]])
612
- # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check
613
- elsif node.type == :or
614
- # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check
615
- result.concat reduce_to_value_nodes(node.children)
635
+ elsif FIRST_TWO_CHILDREN.include?(node.type)
636
+ # a :rescue node's value is its body's value or the
637
+ # value of whichever resbody handled the exception
638
+ result.concat reduce_to_value_nodes([node.children[0], node.children[1]])
616
639
  # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check
617
640
  elsif node.type == :block
618
641
  # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ module Parser
5
+ module ParserGem
6
+ module NodeProcessors
7
+ module ClassDefinition
8
+ # A node wrapper for a class definition via const assignment.
9
+ # @example
10
+ # MyError = Class.new(StandardError) do
11
+ # def retry_after; 5; end
12
+ # end
13
+ class ClassAssignmentNode
14
+ class << self
15
+ # @example
16
+ # s(:casgn, nil, :Foo,
17
+ # s(:block,
18
+ # s(:send,
19
+ # s(:const, nil, :Class), :new,
20
+ # s(:const, nil, :StandardError)),
21
+ # s(:args),
22
+ # s(:def, :retry_after, s(:args), s(:int, 5))))
23
+ #
24
+ # @param node [Parser::AST::Node]
25
+ # @return [Boolean]
26
+ def match? node
27
+ return false unless node&.type == :casgn
28
+
29
+ class_new_node?(new_node(node))
30
+ end
31
+
32
+ # The `Class.new(...)` send node, whether or not a block is attached.
33
+ #
34
+ # @param node [Parser::AST::Node]
35
+ # @return [Parser::AST::Node, nil]
36
+ def new_node node
37
+ value = node.children[2]
38
+ return nil if value.nil?
39
+ return value unless value.type == :block
40
+
41
+ value.children[0]
42
+ end
43
+
44
+ private
45
+
46
+ # @param send_node [Parser::AST::Node, nil]
47
+ # @return [Boolean]
48
+ def class_new_node? send_node
49
+ return false if send_node.nil?
50
+ return false unless send_node.type == :send
51
+ return false unless send_node.children[1] == :new
52
+
53
+ receiver = send_node.children[0]
54
+ return false if receiver.nil?
55
+ return false unless receiver.type == :const
56
+
57
+ receiver.children[1] == :Class
58
+ end
59
+ end
60
+
61
+ # @param node [Parser::AST::Node]
62
+ def initialize node
63
+ @node = node
64
+ end
65
+
66
+ # @return [String]
67
+ def class_name
68
+ namespace = node.children[0]
69
+ return node.children[1].to_s if namespace.nil?
70
+
71
+ "#{Parser::NodeMethods.unpack_name(namespace)}::#{node.children[1]}"
72
+ end
73
+
74
+ # The superclass, when it is written as a constant. `Class.new(expr)`
75
+ # with a non-constant argument yields nil (superclass stays Object).
76
+ #
77
+ # @return [String, nil]
78
+ def superclass_name
79
+ send_node = self.class.new_node(node)
80
+ return nil if send_node.nil?
81
+
82
+ arg = send_node.children[2]
83
+ return nil if arg.nil?
84
+ return nil unless arg.type == :const
85
+
86
+ Parser::NodeMethods.unpack_name(arg)
87
+ end
88
+
89
+ private
90
+
91
+ # @return [Parser::AST::Node]
92
+ attr_reader :node
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ module Parser
5
+ module ParserGem
6
+ module NodeProcessors
7
+ module ClassDefinition
8
+ autoload :ClassAssignmentNode, 'solargraph/parser/parser_gem/node_processors/class_definition/class_assignment_node'
9
+
10
+ class ClassNode < Parser::NodeProcessor::Base
11
+ # @return [Boolean] continue processing the next processor of the same node.
12
+ def process
13
+ definition_node = class_definition_node
14
+ return true if definition_node.nil?
15
+
16
+ loc = get_node_location(node)
17
+ nspin = Solargraph::Pin::Namespace.new(
18
+ type: :class,
19
+ location: loc,
20
+ closure: region.closure,
21
+ name: definition_node.class_name,
22
+ comments: comments_for(node),
23
+ visibility: :public,
24
+ gates: region.closure.gates.freeze,
25
+ source: :class_definition
26
+ )
27
+ pins.push nspin
28
+
29
+ superclass_name = definition_node.superclass_name
30
+ if superclass_name
31
+ pins.push Pin::Reference::Superclass.new(
32
+ location: loc,
33
+ closure: nspin,
34
+ name: superclass_name,
35
+ source: :class_definition
36
+ )
37
+ end
38
+
39
+ process_children region.update(closure: nspin, visibility: :public)
40
+ false
41
+ end
42
+
43
+ private
44
+
45
+ # @return [ClassDefinition::ClassAssignmentNode, nil]
46
+ def class_definition_node
47
+ @class_definition_node ||= ClassAssignmentNode.new(node) if ClassAssignmentNode.match?(node)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -12,6 +12,7 @@ module Solargraph
12
12
  closure: region.closure,
13
13
  name: node.children[0].to_s,
14
14
  comments: comments_for(node),
15
+ type_assertion: type_assertion_for(node),
15
16
  assignment: node.children[1],
16
17
  source: :parser
17
18
  )
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ module Parser
5
+ module ParserGem
6
+ module NodeProcessors
7
+ module DataDefinition
8
+ # A node wrapper for a Data definition via const assignment.
9
+ # @example
10
+ # MyData = Data.new(:bar, :baz) do
11
+ # def foo
12
+ # end
13
+ # end
14
+ class DataAssignmentNode < DataDefintionNode
15
+ class << self
16
+ # @example
17
+ # s(:casgn, nil, :Foo,
18
+ # s(:block,
19
+ # s(:send,
20
+ # s(:const, nil, :Data), :define,
21
+ # s(:sym, :bar),
22
+ # s(:sym, :baz)),
23
+ # s(:args),
24
+ # s(:def, :foo,
25
+ # s(:args),
26
+ # s(:send, nil, :bar))))
27
+ # @param node [::Parser::AST::Node]
28
+ def match? node
29
+ return false unless node&.type == :casgn
30
+ return false if node.children[2].nil?
31
+
32
+ data_node = if node.children[2].type == :block
33
+ node.children[2].children[0]
34
+ else
35
+ node.children[2]
36
+ end
37
+
38
+ data_definition_node?(data_node)
39
+ end
40
+ end
41
+
42
+ def class_name
43
+ if node.children[0]
44
+ Parser::NodeMethods.unpack_name(node.children[0]) + "::#{node.children[1]}"
45
+ else
46
+ node.children[1].to_s
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ # @return [Parser::AST::Node]
53
+ def data_node
54
+ if node.children[2].type == :block
55
+ node.children[2].children[0]
56
+ else
57
+ node.children[2]
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ module Parser
5
+ module ParserGem
6
+ module NodeProcessors
7
+ module DataDefinition
8
+ # A node wrapper for a Data definition via inheritance.
9
+ # @example
10
+ # class MyData < Data.new(:bar, :baz)
11
+ # def foo
12
+ # end
13
+ # end
14
+ class DataDefintionNode
15
+ class << self
16
+ # @example
17
+ # s(:class,
18
+ # s(:const, nil, :Foo),
19
+ # s(:send,
20
+ # s(:const, nil, :Data), :define,
21
+ # s(:sym, :bar),
22
+ # s(:sym, :baz)),
23
+ # s(:hash,
24
+ # s(:pair,
25
+ # s(:sym, :keyword_init),
26
+ # s(:true)))),
27
+ # s(:def, :foo,
28
+ # s(:args),
29
+ # s(:send, nil, :bar)))
30
+ #
31
+ # @param node [Parser::AST::Node]
32
+ def match? node
33
+ return false unless node&.type == :class
34
+
35
+ data_definition_node?(node.children[1])
36
+ end
37
+
38
+ private
39
+
40
+ # @param data_node [Parser::AST::Node]
41
+ # @return [Boolean]
42
+ def data_definition_node? data_node
43
+ return false unless data_node.is_a?(::Parser::AST::Node)
44
+ return false unless data_node&.type == :send
45
+ return false unless data_node.children[0]&.type == :const
46
+ return false unless data_node.children[0].children[1] == :Data
47
+ return false unless data_node.children[1] == :define
48
+
49
+ true
50
+ end
51
+ end
52
+
53
+ # @param node [Parser::AST::Node]
54
+ def initialize node
55
+ @node = node
56
+ end
57
+
58
+ # @return [String]
59
+ def class_name
60
+ Parser::NodeMethods.unpack_name(node)
61
+ end
62
+
63
+ # @return [Array<Array(Parser::AST::Node, String)>]
64
+ def attributes
65
+ data_attribute_nodes.map do |data_def_param|
66
+ next unless data_def_param.type == :sym
67
+ [data_def_param, data_def_param.children[0].to_s]
68
+ end.compact
69
+ end
70
+
71
+ # @return [Parser::AST::Node, nil]
72
+ def body_node
73
+ node.children[2]
74
+ end
75
+
76
+ private
77
+
78
+ # @return [Parser::AST::Node]
79
+ attr_reader :node
80
+
81
+ # @return [Parser::AST::Node, nil]
82
+ def data_node
83
+ node.children[1]
84
+ end
85
+
86
+ # @sg-ignore Need to add nil check here
87
+ # @return [Array<Parser::AST::Node>]
88
+ def data_attribute_nodes
89
+ # @sg-ignore Need to add nil check here
90
+ data_node.children[2..]
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ module Parser
5
+ module ParserGem
6
+ module NodeProcessors
7
+ module DataDefinition
8
+ autoload :DataDefintionNode, 'solargraph/parser/parser_gem/node_processors/data_definition/data_definition_node'
9
+ autoload :DataAssignmentNode, 'solargraph/parser/parser_gem/node_processors/data_definition/data_assignment_node'
10
+
11
+ class DataNode < Parser::NodeProcessor::Base
12
+ # @return [Boolean] continue processing the next processor of the same node.
13
+ def process
14
+ return true if data_definition_node.nil?
15
+
16
+ loc = get_node_location(node)
17
+ nspin = Solargraph::Pin::Namespace.new(
18
+ type: :class,
19
+ location: loc,
20
+ closure: region.closure,
21
+ # @sg-ignore flow sensitive typing needs to handle attrs
22
+ name: data_definition_node.class_name,
23
+ comments: comments_for(node),
24
+ visibility: :public,
25
+ gates: region.closure.gates.freeze
26
+ )
27
+ pins.push nspin
28
+
29
+ # define initialize method
30
+ initialize_method_pin = Pin::Method.new(
31
+ name: 'initialize',
32
+ parameters: [],
33
+ scope: :instance,
34
+ location: get_node_location(node),
35
+ closure: nspin,
36
+ visibility: :private,
37
+ comments: comments_for(node)
38
+ )
39
+
40
+ # @todo Support both arg and kwarg initializers for Data.define
41
+ # Solargraph::SourceMap::Clip#complete_keyword_parameters does not seem to currently take into account [Pin::Method#signatures] hence we only one for :kwarg
42
+ pins.push initialize_method_pin
43
+
44
+ # @sg-ignore flow sensitive typing needs to handle attrs
45
+ data_definition_node.attributes.map do |attribute_node, attribute_name|
46
+ initialize_method_pin.parameters.push(
47
+ Pin::Parameter.new(
48
+ name: attribute_name,
49
+ decl: :kwarg,
50
+ location: get_node_location(attribute_node),
51
+ closure: initialize_method_pin
52
+ )
53
+ )
54
+ end
55
+
56
+ # define attribute readers and instance variables
57
+ # @sg-ignore flow sensitive typing needs to handle attrs
58
+ data_definition_node.attributes.each do |attribute_node, attribute_name|
59
+ name = attribute_name.to_s
60
+ method_pin = Pin::Method.new(
61
+ name: name,
62
+ parameters: [],
63
+ scope: :instance,
64
+ location: get_node_location(attribute_node),
65
+ closure: nspin,
66
+ comments: attribute_comments(attribute_node, attribute_name),
67
+ visibility: :public
68
+ )
69
+
70
+ pins.push method_pin
71
+
72
+ pins.push Pin::InstanceVariable.new(name: "@#{attribute_name}",
73
+ closure: method_pin,
74
+ location: get_node_location(attribute_node),
75
+ comments: attribute_comments(attribute_node, attribute_name))
76
+ end
77
+
78
+ process_children region.update(closure: nspin, visibility: :public)
79
+
80
+ false
81
+ end
82
+
83
+ private
84
+
85
+ # @return [DataDefinition::DataDefintionNode, DataDefinition::DataAssignmentNode, nil]
86
+ def data_definition_node
87
+ @data_definition_node ||= if DataDefintionNode.match?(node)
88
+ DataDefintionNode.new(node)
89
+ elsif DataAssignmentNode.match?(node)
90
+ DataAssignmentNode.new(node)
91
+ end
92
+ end
93
+
94
+ # @param attribute_node [Parser::AST::Node]
95
+ # @param attribute_name [String]
96
+ # @return [String, nil]
97
+ def attribute_comments attribute_node, attribute_name
98
+ data_comments = comments_for(attribute_node)
99
+ return if data_comments.nil? || data_comments.empty?
100
+
101
+ data_comments.split("\n").find do |row|
102
+ row.include?(attribute_name)
103
+ end&.gsub('@param', '@return')&.gsub(attribute_name, '')
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end