solargraph 0.56.0 → 0.57.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 (132) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/linting.yml +125 -0
  3. data/.github/workflows/plugins.yml +148 -6
  4. data/.github/workflows/rspec.yml +39 -4
  5. data/.github/workflows/typecheck.yml +5 -2
  6. data/.gitignore +5 -0
  7. data/.overcommit.yml +72 -0
  8. data/.rspec +1 -0
  9. data/.rubocop.yml +66 -0
  10. data/.rubocop_todo.yml +2627 -0
  11. data/.yardopts +1 -0
  12. data/CHANGELOG.md +56 -1
  13. data/README.md +8 -4
  14. data/Rakefile +125 -13
  15. data/lib/solargraph/api_map/cache.rb +3 -2
  16. data/lib/solargraph/api_map/constants.rb +218 -0
  17. data/lib/solargraph/api_map/index.rb +20 -26
  18. data/lib/solargraph/api_map/source_to_yard.rb +10 -4
  19. data/lib/solargraph/api_map/store.rb +126 -18
  20. data/lib/solargraph/api_map.rb +212 -234
  21. data/lib/solargraph/bench.rb +1 -0
  22. data/lib/solargraph/complex_type/type_methods.rb +1 -0
  23. data/lib/solargraph/complex_type/unique_type.rb +7 -7
  24. data/lib/solargraph/complex_type.rb +5 -1
  25. data/lib/solargraph/convention/active_support_concern.rb +111 -0
  26. data/lib/solargraph/convention/base.rb +17 -0
  27. data/lib/solargraph/convention/data_definition/data_assignment_node.rb +61 -0
  28. data/lib/solargraph/convention/data_definition/data_definition_node.rb +91 -0
  29. data/lib/solargraph/convention/data_definition.rb +105 -0
  30. data/lib/solargraph/convention/gemspec.rb +3 -2
  31. data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +2 -1
  32. data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +4 -2
  33. data/lib/solargraph/convention/struct_definition.rb +87 -24
  34. data/lib/solargraph/convention.rb +32 -2
  35. data/lib/solargraph/diagnostics/rubocop.rb +6 -1
  36. data/lib/solargraph/diagnostics/rubocop_helpers.rb +1 -1
  37. data/lib/solargraph/doc_map.rb +48 -17
  38. data/lib/solargraph/environ.rb +9 -2
  39. data/lib/solargraph/gem_pins.rb +17 -11
  40. data/lib/solargraph/language_server/host/dispatch.rb +2 -0
  41. data/lib/solargraph/language_server/host/message_worker.rb +3 -0
  42. data/lib/solargraph/language_server/host.rb +2 -1
  43. data/lib/solargraph/language_server/message/base.rb +2 -1
  44. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +1 -1
  45. data/lib/solargraph/language_server/message/text_document/definition.rb +2 -0
  46. data/lib/solargraph/language_server/message/text_document/formatting.rb +16 -2
  47. data/lib/solargraph/language_server/message/text_document/type_definition.rb +1 -0
  48. data/lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb +2 -0
  49. data/lib/solargraph/language_server/progress.rb +8 -0
  50. data/lib/solargraph/language_server/request.rb +1 -0
  51. data/lib/solargraph/library.rb +47 -30
  52. data/lib/solargraph/location.rb +2 -0
  53. data/lib/solargraph/logging.rb +11 -2
  54. data/lib/solargraph/page.rb +4 -0
  55. data/lib/solargraph/parser/comment_ripper.rb +8 -1
  56. data/lib/solargraph/parser/flow_sensitive_typing.rb +32 -4
  57. data/lib/solargraph/parser/node_methods.rb +2 -2
  58. data/lib/solargraph/parser/node_processor/base.rb +10 -5
  59. data/lib/solargraph/parser/node_processor.rb +24 -8
  60. data/lib/solargraph/parser/parser_gem/class_methods.rb +1 -1
  61. data/lib/solargraph/parser/parser_gem/flawed_builder.rb +1 -0
  62. data/lib/solargraph/parser/parser_gem/node_chainer.rb +3 -1
  63. data/lib/solargraph/parser/parser_gem/node_methods.rb +4 -2
  64. data/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +3 -2
  65. data/lib/solargraph/parser/parser_gem/node_processors/casgn_node.rb +1 -21
  66. data/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +2 -0
  67. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +7 -1
  68. data/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb +0 -22
  69. data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +1 -0
  70. data/lib/solargraph/parser/parser_gem/node_processors/orasgn_node.rb +1 -0
  71. data/lib/solargraph/parser/parser_gem/node_processors/resbody_node.rb +1 -0
  72. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +35 -14
  73. data/lib/solargraph/parser/parser_gem/node_processors/sym_node.rb +1 -0
  74. data/lib/solargraph/parser/parser_gem/node_processors.rb +4 -0
  75. data/lib/solargraph/parser/region.rb +3 -0
  76. data/lib/solargraph/parser/snippet.rb +2 -0
  77. data/lib/solargraph/pin/base.rb +65 -8
  78. data/lib/solargraph/pin/base_variable.rb +1 -2
  79. data/lib/solargraph/pin/callable.rb +9 -0
  80. data/lib/solargraph/pin/closure.rb +2 -0
  81. data/lib/solargraph/pin/common.rb +6 -2
  82. data/lib/solargraph/pin/constant.rb +2 -0
  83. data/lib/solargraph/pin/delegated_method.rb +1 -0
  84. data/lib/solargraph/pin/local_variable.rb +4 -1
  85. data/lib/solargraph/pin/method.rb +12 -7
  86. data/lib/solargraph/pin/method_alias.rb +3 -0
  87. data/lib/solargraph/pin/parameter.rb +18 -8
  88. data/lib/solargraph/pin/proxy_type.rb +1 -0
  89. data/lib/solargraph/pin/reference/override.rb +15 -1
  90. data/lib/solargraph/pin/reference/superclass.rb +5 -0
  91. data/lib/solargraph/pin/reference.rb +26 -0
  92. data/lib/solargraph/pin/search.rb +3 -1
  93. data/lib/solargraph/pin/signature.rb +2 -0
  94. data/lib/solargraph/pin/symbol.rb +5 -0
  95. data/lib/solargraph/pin_cache.rb +64 -4
  96. data/lib/solargraph/position.rb +2 -0
  97. data/lib/solargraph/range.rb +1 -0
  98. data/lib/solargraph/rbs_map/conversions.rb +47 -18
  99. data/lib/solargraph/rbs_map/core_map.rb +3 -0
  100. data/lib/solargraph/rbs_map.rb +15 -2
  101. data/lib/solargraph/shell.rb +3 -0
  102. data/lib/solargraph/source/chain/link.rb +10 -1
  103. data/lib/solargraph/source/chain.rb +9 -2
  104. data/lib/solargraph/source/change.rb +2 -2
  105. data/lib/solargraph/source/cursor.rb +2 -3
  106. data/lib/solargraph/source/source_chainer.rb +1 -1
  107. data/lib/solargraph/source.rb +5 -2
  108. data/lib/solargraph/source_map/clip.rb +1 -1
  109. data/lib/solargraph/source_map/data.rb +4 -0
  110. data/lib/solargraph/source_map/mapper.rb +4 -2
  111. data/lib/solargraph/source_map.rb +21 -14
  112. data/lib/solargraph/type_checker/param_def.rb +2 -0
  113. data/lib/solargraph/type_checker/rules.rb +8 -0
  114. data/lib/solargraph/type_checker.rb +173 -120
  115. data/lib/solargraph/version.rb +1 -1
  116. data/lib/solargraph/workspace/config.rb +1 -3
  117. data/lib/solargraph/workspace/require_paths.rb +98 -0
  118. data/lib/solargraph/workspace.rb +24 -48
  119. data/lib/solargraph/yard_map/helpers.rb +29 -1
  120. data/lib/solargraph/yard_map/mapper/to_constant.rb +5 -5
  121. data/lib/solargraph/yard_map/mapper/to_method.rb +3 -8
  122. data/lib/solargraph/yard_map/mapper/to_namespace.rb +7 -7
  123. data/lib/solargraph/yardoc.rb +18 -3
  124. data/lib/solargraph.rb +15 -0
  125. data/rbs/fills/tuple.rbs +2 -3
  126. data/sig/shims/parser/3.2.0.1/builders/default.rbs +195 -0
  127. data/sig/shims/thor/1.2.0.1/.rbs_meta.yaml +9 -0
  128. data/sig/shims/thor/1.2.0.1/manifest.yaml +7 -0
  129. data/sig/shims/thor/1.2.0.1/thor.rbs +17 -0
  130. data/solargraph.gemspec +14 -4
  131. metadata +128 -11
  132. data/lib/.rubocop.yml +0 -22
@@ -19,8 +19,14 @@ module Solargraph
19
19
  require_rubocop(config['version'])
20
20
  options, paths = ::RuboCop::Options.new.parse(args)
21
21
  options[:stdin] = original
22
- corrections = redirect_stdout do
23
- ::RuboCop::Runner.new(options, ::RuboCop::ConfigStore.new).run(paths)
22
+
23
+ # Ensure only one instance of RuboCop::Runner is running at
24
+ # a time - it uses 'chdir' to read config files with ERB,
25
+ # which can conflict with other chdirs.
26
+ corrections = Solargraph::CHDIR_MUTEX.synchronize do
27
+ redirect_stdout do
28
+ ::RuboCop::Runner.new(options, ::RuboCop::ConfigStore.new).run(paths)
29
+ end
24
30
  end
25
31
  result = options[:stdin]
26
32
 
@@ -34,6 +40,7 @@ module Solargraph
34
40
  private
35
41
 
36
42
  # @param corrections [String]
43
+ # @return [void]
37
44
  def log_corrections(corrections)
38
45
  corrections = corrections&.strip
39
46
  return if corrections&.empty?
@@ -45,6 +52,8 @@ module Solargraph
45
52
  end
46
53
  end
47
54
 
55
+ # @param file_uri [String]
56
+ # @return [Hash{String => undefined}]
48
57
  def config_for(file_uri)
49
58
  conf = host.formatter_config(file_uri)
50
59
  return {} unless conf.is_a?(Hash)
@@ -52,7 +61,9 @@ module Solargraph
52
61
  conf['rubocop'] || {}
53
62
  end
54
63
 
64
+ # @param file_uri [String]
55
65
  # @param config [Hash{String => String}]
66
+ # @return [Array<String>]
56
67
  def cli_args file_uri, config
57
68
  file = UriHelpers.uri_to_file(file_uri)
58
69
  args = [
@@ -71,6 +82,8 @@ module Solargraph
71
82
  end
72
83
 
73
84
  # @param config [Hash{String => String}]
85
+ # @sg-ignore
86
+ # @return [Class<RuboCop::Formatter::BaseFormatter>]
74
87
  def formatter_class(config)
75
88
  if self.class.const_defined?('BlankRubocopFormatter')
76
89
  # @sg-ignore
@@ -83,6 +96,7 @@ module Solargraph
83
96
  end
84
97
 
85
98
  # @param value [Array, String]
99
+ # @return [String]
86
100
  def cop_list(value)
87
101
  value = value.join(',') if value.respond_to?(:join)
88
102
  return nil if value == '' || !value.is_a?(String)
@@ -10,6 +10,7 @@ module Solargraph::LanguageServer::Message::TextDocument
10
10
 
11
11
  private
12
12
 
13
+ # @return [Array<Hash>]
13
14
  def code_location
14
15
  suggestions = host.type_definitions_at(params['textDocument']['uri'], @line, @column)
15
16
  return nil if suggestions.empty?
@@ -9,11 +9,13 @@ module Solargraph::LanguageServer::Message::Workspace
9
9
 
10
10
  private
11
11
 
12
+ # @return [void]
12
13
  def add_folders
13
14
  return unless params['event'] && params['event']['added']
14
15
  host.prepare_folders params['event']['added']
15
16
  end
16
17
 
18
+ # @return [void]
17
19
  def remove_folders
18
20
  return unless params['event'] && params['event']['removed']
19
21
  params['event']['removed'].each do |folder|
@@ -39,6 +39,7 @@ module Solargraph
39
39
 
40
40
  # @param message [String]
41
41
  # @param percentage [Integer]
42
+ # @return [void]
42
43
  def begin message, percentage
43
44
  @kind = 'begin'
44
45
  @message = message
@@ -47,6 +48,7 @@ module Solargraph
47
48
 
48
49
  # @param message [String]
49
50
  # @param percentage [Integer]
51
+ # @return [void]
50
52
  def report message, percentage
51
53
  @kind = 'report'
52
54
  @message = message
@@ -54,6 +56,7 @@ module Solargraph
54
56
  end
55
57
 
56
58
  # @param message [String]
59
+ # @return [void]
57
60
  def finish message
58
61
  @kind = 'end'
59
62
  @message = message
@@ -62,6 +65,7 @@ module Solargraph
62
65
  end
63
66
 
64
67
  # @param host [Solargraph::LanguageServer::Host]
68
+ # @return [void]
65
69
  def send host
66
70
  return unless host.client_supports_progress? && !finished?
67
71
 
@@ -91,6 +95,7 @@ module Solargraph
91
95
  @status = CREATED
92
96
  end
93
97
 
98
+ # @return [Hash]
94
99
  def build
95
100
  {
96
101
  token: uuid,
@@ -101,6 +106,7 @@ module Solargraph
101
106
  }
102
107
  end
103
108
 
109
+ # @return [Hash]
104
110
  def build_value
105
111
  case kind
106
112
  when 'begin'
@@ -115,6 +121,7 @@ module Solargraph
115
121
  end
116
122
 
117
123
  # @param host [Host]
124
+ # @return [void]
118
125
  def keep_alive host
119
126
  mutex.synchronize { @last = Time.now }
120
127
  @keep_alive ||= Thread.new do
@@ -127,6 +134,7 @@ module Solargraph
127
134
  end
128
135
  end
129
136
 
137
+ # @return [Mutex]
130
138
  def mutex
131
139
  @mutex ||= Mutex.new
132
140
  end
@@ -16,6 +16,7 @@ module Solargraph
16
16
  @block.call(result) unless @block.nil?
17
17
  end
18
18
 
19
+ # @return [void]
19
20
  def send_response
20
21
  # noop
21
22
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'pathname'
4
4
  require 'observer'
5
+ require 'open3'
5
6
 
6
7
  module Solargraph
7
8
  # A Library handles coordination between a Workspace and an ApiMap.
@@ -401,8 +402,8 @@ module Solargraph
401
402
  repargs = {}
402
403
  workspace.config.reporters.each do |line|
403
404
  if line == 'all!'
404
- Diagnostics.reporters.each do |reporter|
405
- repargs[reporter] ||= []
405
+ Diagnostics.reporters.each do |reporter_name|
406
+ repargs[Diagnostics.reporter(reporter_name)] ||= []
406
407
  end
407
408
  else
408
409
  args = line.split(':').map(&:strip)
@@ -436,17 +437,6 @@ module Solargraph
436
437
  )
437
438
  end
438
439
 
439
- # Get an array of foldable ranges for the specified file.
440
- #
441
- # @deprecated The library should not need to handle folding ranges. The
442
- # source itself has all the information it needs.
443
- #
444
- # @param filename [String]
445
- # @return [Array<Range>]
446
- def folding_ranges filename
447
- read(filename).folding_ranges
448
- end
449
-
450
440
  # Create a library from a directory.
451
441
  #
452
442
  # @param directory [String] The path to be used for the workspace
@@ -510,7 +500,7 @@ module Solargraph
510
500
 
511
501
  private
512
502
 
513
- # @return [Hash{String => Set<String>}]
503
+ # @return [Hash{String => Array<String>}]
514
504
  def source_map_external_require_hash
515
505
  @source_map_external_require_hash ||= {}
516
506
  end
@@ -518,6 +508,7 @@ module Solargraph
518
508
  # @param source_map [SourceMap]
519
509
  # @return [void]
520
510
  def find_external_requires source_map
511
+ # @type [Set<String>]
521
512
  new_set = source_map.requires.map(&:name).to_set
522
513
  # return if new_set == source_map_external_require_hash[source_map.filename]
523
514
  _filenames = nil
@@ -588,29 +579,54 @@ module Solargraph
588
579
  # @return [void]
589
580
  def cache_next_gemspec
590
581
  return if @cache_progress
591
- spec = (api_map.uncached_yard_gemspecs + api_map.uncached_rbs_collection_gemspecs).
592
- find { |spec| !cache_errors.include?(spec) }
582
+
583
+ spec = cacheable_specs.first
593
584
  return end_cache_progress unless spec
594
585
 
595
586
  pending = api_map.uncached_gemspecs.length - cache_errors.length - 1
596
- logger.info "Caching #{spec.name} #{spec.version}"
597
- Thread.new do
598
- cache_pid = Process.spawn(workspace.command_path, 'cache', spec.name, spec.version.to_s)
599
- report_cache_progress spec.name, pending
600
- Process.wait(cache_pid)
601
- logger.info "Cached #{spec.name} #{spec.version}"
602
- rescue Errno::EINVAL => _e
603
- logger.info "Cached #{spec.name} #{spec.version} with EINVAL"
604
- rescue StandardError => e
605
- cache_errors.add spec
606
- Solargraph.logger.warn "Error caching gemspec #{spec.name} #{spec.version}: [#{e.class}] #{e.message}"
607
- ensure
608
- end_cache_progress
587
+
588
+ if Yardoc.processing?(spec)
589
+ logger.info "Enqueuing cache of #{spec.name} #{spec.version} (already being processed)"
590
+ queued_gemspec_cache.push(spec)
591
+ return if pending - queued_gemspec_cache.length < 1
592
+
609
593
  catalog
610
594
  sync_catalog
595
+ else
596
+ logger.info "Caching #{spec.name} #{spec.version}"
597
+ Thread.new do
598
+ report_cache_progress spec.name, pending
599
+ _o, e, s = Open3.capture3(workspace.command_path, 'cache', spec.name, spec.version.to_s)
600
+ if s.success?
601
+ logger.info "Cached #{spec.name} #{spec.version}"
602
+ else
603
+ cache_errors.add spec
604
+ logger.warn "Error caching gemspec #{spec.name} #{spec.version}"
605
+ logger.warn e
606
+ end
607
+ end_cache_progress
608
+ catalog
609
+ sync_catalog
610
+ end
611
611
  end
612
612
  end
613
613
 
614
+ # @return [Array<Gem::Specification>]
615
+ def cacheable_specs
616
+ cacheable = api_map.uncached_yard_gemspecs +
617
+ api_map.uncached_rbs_collection_gemspecs -
618
+ queued_gemspec_cache -
619
+ cache_errors.to_a
620
+ return cacheable unless cacheable.empty?
621
+
622
+ queued_gemspec_cache
623
+ end
624
+
625
+ # @return [Array<Gem::Specification>]
626
+ def queued_gemspec_cache
627
+ @queued_gemspec_cache ||= []
628
+ end
629
+
614
630
  # @param gem_name [String]
615
631
  # @param pending [Integer]
616
632
  # @return [void]
@@ -648,13 +664,14 @@ module Solargraph
648
664
  @total = nil
649
665
  end
650
666
 
667
+ # @return [void]
651
668
  def sync_catalog
652
669
  return if @sync_count == 0
653
670
 
654
671
  mutex.synchronize do
655
672
  logger.info "Cataloging #{workspace.directory.empty? ? 'generic workspace' : workspace.directory}"
656
- api_map.catalog bench
657
673
  source_map_hash.values.each { |map| find_external_requires(map) }
674
+ api_map.catalog bench
658
675
  logger.info "Catalog complete (#{api_map.source_maps.length} files, #{api_map.pins.length} pins)"
659
676
  logger.info "#{api_map.uncached_yard_gemspecs.length} uncached YARD gemspecs"
660
677
  logger.info "#{api_map.uncached_rbs_collection_gemspecs.length} uncached RBS collection gemspecs"
@@ -25,6 +25,7 @@ module Solargraph
25
25
  [filename, range]
26
26
  end
27
27
 
28
+ # @param other [self]
28
29
  def <=>(other)
29
30
  return nil unless other.is_a?(Location)
30
31
  if filename == other.filename
@@ -60,6 +61,7 @@ module Solargraph
60
61
  end
61
62
 
62
63
  # @param node [Parser::AST::Node, nil]
64
+ # @return [Location, nil]
63
65
  def self.from_node(node)
64
66
  return nil if node.nil? || node.loc.nil?
65
67
  range = Range.from_node(node)
@@ -11,8 +11,17 @@ module Solargraph
11
11
  'info' => Logger::INFO,
12
12
  'debug' => Logger::DEBUG
13
13
  }
14
-
15
- @@logger = Logger.new(STDERR, level: DEFAULT_LOG_LEVEL)
14
+ configured_level = ENV.fetch('SOLARGRAPH_LOG', nil)
15
+ level = if LOG_LEVELS.keys.include?(configured_level)
16
+ LOG_LEVELS.fetch(configured_level)
17
+ else
18
+ if configured_level
19
+ warn "Invalid value for SOLARGRAPH_LOG: #{configured_level.inspect} - " \
20
+ "valid values are #{LOG_LEVELS.keys}"
21
+ end
22
+ DEFAULT_LOG_LEVEL
23
+ end
24
+ @@logger = Logger.new(STDERR, level: level)
16
25
  # @sg-ignore Fix cvar issue
17
26
  @@logger.formatter = proc do |severity, datetime, progname, msg|
18
27
  "[#{severity}] #{msg}\n"
@@ -27,8 +27,10 @@ module Solargraph
27
27
  end
28
28
 
29
29
  # @param text [String]
30
+ # @sg-ignore https://github.com/lsegal/yard/pull/1615
30
31
  # @return [String]
31
32
  def htmlify text
33
+ # @type [String]
32
34
  YARD::Templates::Helpers::Markup::RDocMarkup.new(text).to_html
33
35
  end
34
36
 
@@ -70,8 +72,10 @@ module Solargraph
70
72
  # @param template [String]
71
73
  # @param layout [Boolean]
72
74
  # @param locals [Hash]
75
+ # @sg-ignore
73
76
  # @return [String]
74
77
  def render template, layout: true, locals: {}
78
+ # @type [String]
75
79
  @render_method.call(template, layout: layout, locals: locals)
76
80
  end
77
81
 
@@ -3,6 +3,13 @@ require 'ripper'
3
3
  module Solargraph
4
4
  module Parser
5
5
  class CommentRipper < Ripper::SexpBuilderPP
6
+ # @!override Ripper::SexpBuilder#on_embdoc_beg
7
+ # @return [Array(Symbol, String, Array)]
8
+ # @!override Ripper::SexpBuilder#on_embdoc
9
+ # @return [Array(Symbol, String, Array)]
10
+ # @!override Ripper::SexpBuilder#on_embdoc_end
11
+ # @return [Array(Symbol, String, Array)]
12
+
6
13
  # @param src [String]
7
14
  # @param filename [String]
8
15
  # @param lineno [Integer]
@@ -51,7 +58,7 @@ module Solargraph
51
58
  result
52
59
  end
53
60
 
54
- # @return [Hash{Integer => String}]
61
+ # @return [Hash{Integer => Solargraph::Parser::Snippet}]
55
62
  def parse
56
63
  @comments = {}
57
64
  super
@@ -4,14 +4,20 @@ module Solargraph
4
4
  include Solargraph::Parser::NodeMethods
5
5
 
6
6
  # @param locals [Array<Solargraph::Pin::LocalVariable, Solargraph::Pin::Parameter>]
7
+ # @param enclosing_breakable_pin [Solargraph::Pin::Breakable, nil]
7
8
  def initialize(locals, enclosing_breakable_pin = nil)
8
9
  @locals = locals
9
10
  @enclosing_breakable_pin = enclosing_breakable_pin
10
11
  end
11
12
 
12
13
  # @param and_node [Parser::AST::Node]
14
+ # @param true_ranges [Array<Range>]
15
+ #
16
+ # @return [void]
13
17
  def process_and(and_node, true_ranges = [])
18
+ # @type [Parser::AST::Node]
14
19
  lhs = and_node.children[0]
20
+ # @type [Parser::AST::Node]
15
21
  rhs = and_node.children[1]
16
22
 
17
23
  before_rhs_loc = rhs.location.expression.adjust(begin_pos: -1)
@@ -23,6 +29,8 @@ module Solargraph
23
29
  end
24
30
 
25
31
  # @param if_node [Parser::AST::Node]
32
+ #
33
+ # @return [void]
26
34
  def process_if(if_node)
27
35
  #
28
36
  # See if we can refine a type based on the result of 'if foo.nil?'
@@ -36,7 +44,9 @@ module Solargraph
36
44
  # s(:send, nil, :bar))
37
45
  # [4] pry(main)>
38
46
  conditional_node = if_node.children[0]
47
+ # @type [Parser::AST::Node]
39
48
  then_clause = if_node.children[1]
49
+ # @type [Parser::AST::Node]
40
50
  else_clause = if_node.children[2]
41
51
 
42
52
  true_ranges = []
@@ -72,8 +82,11 @@ module Solargraph
72
82
  # them based on the Closure and Location.
73
83
  #
74
84
  # @param pins [Array<Pin::LocalVariable>]
85
+ # @param name [String]
75
86
  # @param closure [Pin::Closure]
76
87
  # @param location [Location]
88
+ #
89
+ # @return [Array<Pin::LocalVariable>]
77
90
  def self.visible_pins(pins, name, closure, location)
78
91
  logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location})" }
79
92
  pins_with_name = pins.select { |p| p.name == name }
@@ -107,7 +120,10 @@ module Solargraph
107
120
  private
108
121
 
109
122
  # @param pin [Pin::LocalVariable]
110
- # @param if_node [Parser::AST::Node]
123
+ # @param downcast_type_name [String]
124
+ # @param presence [Range]
125
+ #
126
+ # @return [void]
111
127
  def add_downcast_local(pin, downcast_type_name, presence)
112
128
  # @todo Create pin#update method
113
129
  new_pin = Solargraph::Pin::LocalVariable.new(
@@ -126,6 +142,7 @@ module Solargraph
126
142
 
127
143
  # @param facts_by_pin [Hash{Pin::LocalVariable => Array<Hash{Symbol => String}>}]
128
144
  # @param presences [Array<Range>]
145
+ #
129
146
  # @return [void]
130
147
  def process_facts(facts_by_pin, presences)
131
148
  #
@@ -142,6 +159,9 @@ module Solargraph
142
159
  end
143
160
 
144
161
  # @param conditional_node [Parser::AST::Node]
162
+ # @param true_ranges [Array<Range>]
163
+ #
164
+ # @return [void]
145
165
  def process_conditional(conditional_node, true_ranges)
146
166
  if conditional_node.type == :send
147
167
  process_isa(conditional_node, true_ranges)
@@ -176,12 +196,20 @@ module Solargraph
176
196
  [isa_type_name, variable_name]
177
197
  end
178
198
 
199
+ # @param variable_name [String]
200
+ # @param position [Position]
201
+ #
202
+ # @return [Solargraph::Pin::LocalVariable, nil]
179
203
  def find_local(variable_name, position)
180
204
  pins = locals.select { |pin| pin.name == variable_name && pin.presence.include?(position) }
181
205
  return unless pins.length == 1
182
206
  pins.first
183
207
  end
184
208
 
209
+ # @param isa_node [Parser::AST::Node]
210
+ # @param true_presences [Array<Range>]
211
+ #
212
+ # @return [void]
185
213
  def process_isa(isa_node, true_presences)
186
214
  isa_type_name, variable_name = parse_isa(isa_node)
187
215
  return if variable_name.nil? || variable_name.empty?
@@ -197,10 +225,12 @@ module Solargraph
197
225
  end
198
226
 
199
227
  # @param node [Parser::AST::Node]
228
+ #
229
+ # @return [String, nil]
200
230
  def type_name(node)
201
231
  # e.g.,
202
232
  # s(:const, nil, :Baz)
203
- return unless node.type == :const
233
+ return unless node&.type == :const
204
234
  module_node = node.children[0]
205
235
  class_node = node.children[1]
206
236
 
@@ -212,8 +242,6 @@ module Solargraph
212
242
  "#{module_type_name}::#{class_node}"
213
243
  end
214
244
 
215
- # @todo "return type could not be inferred" should not trigger here
216
- # @sg-ignore
217
245
  # @param clause_node [Parser::AST::Node]
218
246
  def always_breaks?(clause_node)
219
247
  clause_node&.type == :break
@@ -1,6 +1,6 @@
1
1
  module Solargraph
2
2
  module Parser
3
- class NodeMethods
3
+ module NodeMethods
4
4
  module_function
5
5
 
6
6
  # @abstract
@@ -74,7 +74,7 @@ module Solargraph
74
74
 
75
75
  # @abstract
76
76
  # @param node [Parser::AST::Node]
77
- # @return [Hash{Parser::AST::Node => Source::Chain}]
77
+ # @return [Hash{Symbol => Source::Chain}]
78
78
  def convert_hash node
79
79
  raise NotImplementedError
80
80
  end
@@ -13,7 +13,7 @@ module Solargraph
13
13
  # @return [Array<Pin::Base>]
14
14
  attr_reader :pins
15
15
 
16
- # @return [Array<Pin::BaseVariable>]
16
+ # @return [Array<Pin::LocalVariable>]
17
17
  attr_reader :locals
18
18
 
19
19
  # @param node [Parser::AST::Node]
@@ -30,9 +30,12 @@ module Solargraph
30
30
 
31
31
  # Subclasses should override this method to generate new pins.
32
32
  #
33
- # @return [void]
33
+ # @return [Boolean] continue processing the next processor of the same node type.
34
+ # @return [void] In case there is only one processor registered for the node type, it can be void.
34
35
  def process
35
36
  process_children
37
+
38
+ true
36
39
  end
37
40
 
38
41
  private
@@ -64,7 +67,9 @@ module Solargraph
64
67
  # @param position [Solargraph::Position]
65
68
  # @return [Pin::Closure, nil]
66
69
  def named_path_pin position
67
- pins.select{|pin| pin.is_a?(Pin::Closure) && pin.path && !pin.path.empty? && pin.location.range.contain?(position)}.last
70
+ pins.select do |pin|
71
+ pin.is_a?(Pin::Closure) && pin.path && !pin.path.empty? && pin.location.range.contain?(position)
72
+ end.last
68
73
  end
69
74
 
70
75
  # @todo Candidate for deprecation
@@ -72,14 +77,14 @@ module Solargraph
72
77
  # @return [Pin::Closure, nil]
73
78
  def block_pin position
74
79
  # @todo determine if this can return a Pin::Block
75
- pins.select{|pin| pin.is_a?(Pin::Closure) && pin.location.range.contain?(position)}.last
80
+ pins.select { |pin| pin.is_a?(Pin::Closure) && pin.location.range.contain?(position) }.last
76
81
  end
77
82
 
78
83
  # @todo Candidate for deprecation
79
84
  # @param position [Solargraph::Position]
80
85
  # @return [Pin::Closure, nil]
81
86
  def closure_pin position
82
- pins.select{|pin| pin.is_a?(Pin::Closure) && pin.location.range.contain?(position)}.last
87
+ pins.select { |pin| pin.is_a?(Pin::Closure) && pin.location.range.contain?(position) }.last
83
88
  end
84
89
  end
85
90
  end
@@ -9,16 +9,26 @@ module Solargraph
9
9
  autoload :Base, 'solargraph/parser/node_processor/base'
10
10
 
11
11
  class << self
12
- # @type [Hash{Symbol => Class<NodeProcessor::Base>}]
12
+ # @type [Hash{Symbol => Array<Class<NodeProcessor::Base>>}]
13
13
  @@processors ||= {}
14
14
 
15
- # Register a processor for a node type.
15
+ # Register a processor for a node type. You can register multiple processors for the same type.
16
+ # If a node processor returns true, it will skip the next processor of the same node type.
16
17
  #
17
18
  # @param type [Symbol]
18
19
  # @param cls [Class<NodeProcessor::Base>]
19
- # @return [Class<NodeProcessor::Base>]
20
+ # @return [Array<Class<NodeProcessor::Base>>]
20
21
  def register type, cls
21
- @@processors[type] = cls
22
+ @@processors[type] ||= []
23
+ @@processors[type] << cls
24
+ end
25
+
26
+ # @param type [Symbol]
27
+ # @param cls [Class<NodeProcessor::Base>]
28
+ #
29
+ # @return [void]
30
+ def deregister type, cls
31
+ @@processors[type].delete(cls)
22
32
  end
23
33
  end
24
34
 
@@ -36,10 +46,16 @@ module Solargraph
36
46
  )
37
47
  end
38
48
  return [pins, locals] unless Parser.is_ast_node?(node)
39
- klass = @@processors[node.type] || NodeProcessor::Base
40
- processor = klass.new(node, region, pins, locals)
41
- processor.process
42
- [processor.pins, processor.locals]
49
+ node_processor_classes = @@processors[node.type] || [NodeProcessor::Base]
50
+
51
+ node_processor_classes.each do |klass|
52
+ processor = klass.new(node, region, pins, locals)
53
+ process_next = processor.process
54
+
55
+ break unless process_next
56
+ end
57
+
58
+ [pins, locals]
43
59
  end
44
60
  end
45
61
  end
@@ -17,7 +17,7 @@ module Solargraph
17
17
  module ClassMethods
18
18
  # @param code [String]
19
19
  # @param filename [String, nil]
20
- # @return [Array(Parser::AST::Node, Hash{Integer => String})]
20
+ # @return [Array(Parser::AST::Node, Hash{Integer => Solargraph::Parser::Snippet})]
21
21
  def parse_with_comments code, filename = nil
22
22
  node = parse(code, filename)
23
23
  comments = CommentRipper.new(code, filename, 0).parse
@@ -9,6 +9,7 @@ module Solargraph
9
9
  class FlawedBuilder < ::Parser::Builders::Default
10
10
  # @param token [::Parser::AST::Node]
11
11
  # @return [String]
12
+ # @sg-ignore
12
13
  def string_value(token)
13
14
  value(token)
14
15
  end
@@ -7,6 +7,7 @@ module Solargraph
7
7
  #
8
8
  class NodeChainer
9
9
  include NodeMethods
10
+
10
11
  Chain = Source::Chain
11
12
 
12
13
  # @param node [Parser::AST::Node]
@@ -98,7 +99,8 @@ module Solargraph
98
99
  elsif [:gvar, :gvasgn].include?(n.type)
99
100
  result.push Chain::GlobalVariable.new(n.children[0].to_s)
100
101
  elsif n.type == :or_asgn
101
- result.concat generate_links n.children[1]
102
+ new_node = n.updated(n.children[0].type, n.children[0].children + [n.children[1]])
103
+ result.concat generate_links new_node
102
104
  elsif [:class, :module, :def, :defs].include?(n.type)
103
105
  # @todo Undefined or what?
104
106
  result.push Chain::UNDEFINED_CALL