steep 1.5.2 → 1.6.0.pre.1

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby-windows.yml +1 -2
  3. data/.github/workflows/ruby.yml +1 -2
  4. data/CHANGELOG.md +41 -0
  5. data/Gemfile +2 -4
  6. data/Gemfile.lock +37 -19
  7. data/gemfile_steep/Gemfile.lock +4 -4
  8. data/lib/steep/ast/types/logic.rb +6 -0
  9. data/lib/steep/cli.rb +39 -19
  10. data/lib/steep/interface/builder.rb +9 -0
  11. data/lib/steep/path_helper.rb +2 -0
  12. data/lib/steep/server/change_buffer.rb +9 -0
  13. data/lib/steep/server/delay_queue.rb +41 -0
  14. data/lib/steep/server/interaction_worker.rb +4 -2
  15. data/lib/steep/server/master.rb +106 -10
  16. data/lib/steep/server/type_check_worker.rb +10 -3
  17. data/lib/steep/services/completion_provider.rb +1 -1
  18. data/lib/steep/services/stats_calculator.rb +2 -2
  19. data/lib/steep/services/type_name_completion.rb +86 -15
  20. data/lib/steep/signature/validator.rb +9 -2
  21. data/lib/steep/subtyping/check.rb +24 -18
  22. data/lib/steep/type_construction.rb +60 -18
  23. data/lib/steep/type_inference/logic_type_interpreter.rb +26 -0
  24. data/lib/steep/type_inference/method_params.rb +1 -1
  25. data/lib/steep/version.rb +1 -1
  26. data/lib/steep.rb +1 -3
  27. data/sig/shims/language-server_protocol.rbs +12 -0
  28. data/sig/steep/ast/types/logic.rbs +5 -0
  29. data/sig/steep/ast/types.rbs +1 -1
  30. data/sig/steep/cli.rbs +2 -0
  31. data/sig/steep/diagnostic/ruby.rbs +7 -7
  32. data/sig/steep/server/change_buffer.rbs +4 -0
  33. data/sig/steep/server/delay_queue.rbs +37 -0
  34. data/sig/steep/server/master.rbs +4 -0
  35. data/sig/steep/services/stats_calculator.rbs +30 -6
  36. data/sig/steep/services/type_name_completion.rbs +13 -0
  37. data/sig/steep/signature/validator.rbs +5 -0
  38. data/sig/steep/subtyping/check.rbs +1 -1
  39. data/sig/steep/subtyping/relation.rbs +11 -1
  40. data/sig/steep/subtyping/result.rbs +1 -1
  41. data/sig/steep/type_construction.rbs +1 -1
  42. data/smoke/block/test_expectations.yml +10 -14
  43. data/smoke/enumerator/test_expectations.yml +10 -10
  44. data/smoke/integer/test_expectations.yml +5 -16
  45. data/smoke/regression/hello world.rb +1 -0
  46. data/smoke/regression/test_expectations.yml +12 -0
  47. data/steep.gemspec +1 -1
  48. metadata +8 -7
  49. data/lib/steep/shims/filter_map.rb +0 -30
  50. data/lib/steep/shims/symbol_start_with.rb +0 -18
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d2971f6bf7e70b227ac38de9682f640c8b274d8c27db2e02ec6920163d9e26df
4
- data.tar.gz: 6f756247c85e018f5b2feaac36238480438495d242994f9dd23ccacd52e195d2
3
+ metadata.gz: d8a5efa97a65add83ffc658ddfbdf9a436accc8dd4472969610b2224140543ea
4
+ data.tar.gz: 537d28e28ae6b3bc47392170948db46d3d7fa50d61f6f274182b922a253394ea
5
5
  SHA512:
6
- metadata.gz: f4ae7293b7ba2f8a33c96e470346f853f83afff373f5724a094c788841602cff54ad911ea74d73cc63471383f8fe043b217fef6528d4d6a6d91cb3351dc358e7
7
- data.tar.gz: 720040f41d6a7c0fa11facd1bc0b8e0860cd54d3572fd698807894ff6c4fe33beea6c9fff8a4a1bd115e3b494021590ef33bfcd7035556a46cbcc00455943292
6
+ metadata.gz: 386ddbecc785a37748b8a6d9dcf514af55cec3bcafca945e363d5e76e56fd266cd91238183b3c99635595168b0049e93b065f3154569d10909c2ecd6ccdd9519
7
+ data.tar.gz: 15a3baf8009e78767015a4529f71b20300edc221aaf0b218efdee5a894f73d619d2c3078c68f4e3511bec4dbb67aece479e9a95c6b1ae1fb9066da86094fc810
@@ -11,7 +11,6 @@ jobs:
11
11
  strategy:
12
12
  matrix:
13
13
  ruby_version:
14
- - "2.7"
15
14
  - "3.0"
16
15
  - "3.1"
17
16
  - "3.2"
@@ -21,7 +20,7 @@ jobs:
21
20
  - build
22
21
  runs-on: windows-latest
23
22
  steps:
24
- - uses: actions/checkout@v3
23
+ - uses: actions/checkout@v4
25
24
  - uses: ruby/setup-ruby@v1
26
25
  with:
27
26
  ruby-version: ${{ matrix.ruby_version }}
@@ -11,7 +11,6 @@ jobs:
11
11
  strategy:
12
12
  matrix:
13
13
  container_tag:
14
- - "2.7"
15
14
  - "3.0"
16
15
  - "3.1"
17
16
  - "3.2"
@@ -24,7 +23,7 @@ jobs:
24
23
  container:
25
24
  image: rubylang/ruby:${{ matrix.container_tag }}
26
25
  steps:
27
- - uses: actions/checkout@v3
26
+ - uses: actions/checkout@v4
28
27
  - name: Run test
29
28
  run: |
30
29
  git config --global --add safe.directory /__w/steep/steep
data/CHANGELOG.md CHANGED
@@ -2,6 +2,47 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 1.6.0.pre.1 (2023-10-27)
6
+
7
+ ### Type checker core
8
+
9
+ * Test if a parameter is `_` ([#946](https://github.com/soutaro/steep/pull/946))
10
+ * Let `[]=` call have correct type ([#945](https://github.com/soutaro/steep/pull/945))
11
+ * Support type narrowing by `Module#<` ([#877](https://github.com/soutaro/steep/pull/877))
12
+ * Fewer `UnresolvedOverloading` ([#941](https://github.com/soutaro/steep/pull/941))
13
+ * Fix ArgumentTypeMismatch for PublishDiagnosticsParams ([#895](https://github.com/soutaro/steep/pull/895))
14
+ * Add types for LSP::Constant::MessageType ([#894](https://github.com/soutaro/steep/pull/894))
15
+ * `nil` is not a `NilClass` ([#920](https://github.com/soutaro/steep/pull/920))
16
+ * Fix unexpected error when DifferentMethodParameterKind ([#917](https://github.com/soutaro/steep/pull/917))
17
+
18
+ ### Commandline tool
19
+
20
+ * Fix space in file path crash ([#944](https://github.com/soutaro/steep/pull/944))
21
+ * refactor: Rename driver objects to command ([#893](https://github.com/soutaro/steep/pull/893))
22
+ * Run with `--jobs=2` automatically on CI ([#924](https://github.com/soutaro/steep/pull/924))
23
+ * Fix type alias validation ([#922](https://github.com/soutaro/steep/pull/922))
24
+
25
+ ### Language server
26
+
27
+ * Let goto definition work from `UnresolvedOverloading` error calls ([#943](https://github.com/soutaro/steep/pull/943))
28
+ * Let label be whole method type in SignatureHelp ([#942](https://github.com/soutaro/steep/pull/942))
29
+ * Set up file watcher ([#936](https://github.com/soutaro/steep/pull/936))
30
+ * Reset file content on `didOpen` notification ([#935](https://github.com/soutaro/steep/pull/935))
31
+ * Start type check on change ([#934](https://github.com/soutaro/steep/pull/934))
32
+ * Better completion with module alias and `use` directives ([#923](https://github.com/soutaro/steep/pull/923))
33
+
34
+ ### Miscellaneous
35
+
36
+ * Drop 2.7 support ([#928](https://github.com/soutaro/steep/pull/928))
37
+ * Type check `subtyping/check.rb` ([#921](https://github.com/soutaro/steep/pull/921))
38
+ * Type check constant under `self` ([#908](https://github.com/soutaro/steep/pull/908))
39
+
40
+ ## 1.5.3 (2023-08-10)
41
+
42
+ ### Type checker core
43
+
44
+ * Fix type checking parenthesized conditional nodes ([#896](https://github.com/soutaro/steep/pull/896))
45
+
5
46
  ## 1.5.2 (2023-07-27)
6
47
 
7
48
  ### Type checker core
data/Gemfile CHANGED
@@ -4,13 +4,11 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  gem "rake"
7
- gem "minitest", "~> 5.19"
7
+ gem "minitest", "~> 5.20"
8
8
  gem "minitest-hooks"
9
9
  group :stackprof, optional: true do
10
10
  gem "stackprof"
11
11
  end
12
12
  gem 'minitest-slow_test'
13
13
 
14
- group :development do
15
- gem "ruby-lsp", require: false
16
- end
14
+ gem "debug", require: false, platform: :mri
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- steep (1.5.2)
4
+ steep (1.6.0.pre.1)
5
5
  activesupport (>= 5.1)
6
6
  concurrent-ruby (>= 1.1.10)
7
7
  csv (>= 3.0.9)
@@ -20,65 +20,83 @@ PATH
20
20
  GEM
21
21
  remote: https://rubygems.org/
22
22
  specs:
23
- activesupport (7.0.6)
23
+ activesupport (7.1.1)
24
+ base64
25
+ bigdecimal
24
26
  concurrent-ruby (~> 1.0, >= 1.0.2)
27
+ connection_pool (>= 2.2.5)
28
+ drb
25
29
  i18n (>= 1.6, < 2)
26
30
  minitest (>= 5.1)
31
+ mutex_m
27
32
  tzinfo (~> 2.0)
28
33
  ast (2.4.2)
34
+ base64 (0.1.1)
35
+ bigdecimal (3.1.4)
29
36
  concurrent-ruby (1.2.2)
37
+ connection_pool (2.4.1)
30
38
  csv (3.2.7)
31
- ffi (1.15.5)
39
+ debug (1.8.0)
40
+ irb (>= 1.5.0)
41
+ reline (>= 0.3.1)
42
+ drb (2.1.1)
43
+ ruby2_keywords
44
+ ffi (1.16.3)
32
45
  fileutils (1.7.1)
33
46
  i18n (1.14.1)
34
47
  concurrent-ruby (~> 1.0)
48
+ io-console (0.6.0)
49
+ irb (1.8.1)
50
+ rdoc
51
+ reline (>= 0.3.8)
35
52
  json (2.6.3)
36
53
  language_server-protocol (3.17.0.3)
37
54
  listen (3.8.0)
38
55
  rb-fsevent (~> 0.10, >= 0.10.3)
39
56
  rb-inotify (~> 0.9, >= 0.9.10)
40
57
  logger (1.5.3)
41
- minitest (5.19.0)
42
- minitest-hooks (1.5.0)
58
+ minitest (5.20.0)
59
+ minitest-hooks (1.5.1)
43
60
  minitest (> 5.3)
44
61
  minitest-slow_test (0.2.0)
45
62
  minitest (>= 5.0)
46
- parser (3.2.2.3)
63
+ mutex_m (0.1.2)
64
+ parser (3.2.2.4)
47
65
  ast (~> 2.4.1)
48
66
  racc
49
- prettier_print (1.2.1)
67
+ psych (5.1.0)
68
+ stringio
50
69
  racc (1.7.1)
51
70
  rainbow (3.1.1)
52
71
  rake (13.0.6)
53
72
  rb-fsevent (0.11.2)
54
73
  rb-inotify (0.10.1)
55
74
  ffi (~> 1.0)
56
- rbs (3.1.1)
57
- ruby-lsp (0.5.1)
58
- language_server-protocol (~> 3.17.0)
59
- sorbet-runtime
60
- syntax_tree (>= 6.1.1, < 7)
75
+ rbs (3.2.2)
76
+ rdoc (6.5.0)
77
+ psych (>= 4.0.0)
78
+ reline (0.3.8)
79
+ io-console (~> 0.5)
80
+ ruby2_keywords (0.0.5)
61
81
  securerandom (0.2.2)
62
- sorbet-runtime (0.5.10832)
63
82
  stackprof (0.2.25)
64
- strscan (3.0.6)
65
- syntax_tree (6.1.1)
66
- prettier_print (>= 1.2.0)
83
+ stringio (3.0.8)
84
+ strscan (3.0.7)
67
85
  terminal-table (3.0.2)
68
86
  unicode-display_width (>= 1.1.1, < 3)
69
87
  tzinfo (2.0.6)
70
88
  concurrent-ruby (~> 1.0)
71
- unicode-display_width (2.4.2)
89
+ unicode-display_width (2.5.0)
72
90
 
73
91
  PLATFORMS
74
92
  ruby
75
93
 
76
94
  DEPENDENCIES
77
- minitest (~> 5.19)
95
+ debug
96
+ minitest (~> 5.20)
78
97
  minitest-hooks
79
98
  minitest-slow_test
80
99
  rake
81
- ruby-lsp
82
100
  stackprof
83
101
  steep!
84
102
 
@@ -1,7 +1,7 @@
1
1
  GEM
2
2
  remote: https://rubygems.org/
3
3
  specs:
4
- activesupport (7.0.6)
4
+ activesupport (7.0.7.2)
5
5
  concurrent-ruby (~> 1.0, >= 1.0.2)
6
6
  i18n (>= 1.6, < 2)
7
7
  minitest (>= 5.1)
@@ -19,7 +19,7 @@ GEM
19
19
  rb-fsevent (~> 0.10, >= 0.10.3)
20
20
  rb-inotify (~> 0.9, >= 0.9.10)
21
21
  logger (1.5.3)
22
- minitest (5.18.1)
22
+ minitest (5.19.0)
23
23
  parser (3.2.2.3)
24
24
  ast (~> 2.4.1)
25
25
  racc
@@ -28,9 +28,9 @@ GEM
28
28
  rb-fsevent (0.11.2)
29
29
  rb-inotify (0.10.1)
30
30
  ffi (~> 1.0)
31
- rbs (3.1.1)
31
+ rbs (3.1.3)
32
32
  securerandom (0.2.2)
33
- steep (1.5.1)
33
+ steep (1.5.3)
34
34
  activesupport (>= 5.1)
35
35
  concurrent-ruby (>= 1.1.10)
36
36
  csv (>= 3.0.9)
@@ -68,6 +68,12 @@ module Steep
68
68
  end
69
69
  end
70
70
 
71
+ class ArgIsAncestor < Base
72
+ def initialize(location: nil)
73
+ @location = location
74
+ end
75
+ end
76
+
71
77
  class Env < Base
72
78
  attr_reader :truthy, :falsy, :type
73
79
 
data/lib/steep/cli.rb CHANGED
@@ -84,6 +84,18 @@ module Steep
84
84
  end
85
85
  end
86
86
 
87
+ def setup_jobs_for_ci(jobs_option)
88
+ if ENV["CI"]
89
+ unless jobs_option.jobs_count
90
+ stderr.puts Rainbow("CI environment is detected but no `--jobs` option is given.").yellow
91
+ stderr.puts " Using `[2, #{jobs_option.default_jobs_count} (# or processors)].min` to avoid hitting memory limit."
92
+ stderr.puts " Specify `--jobs` option to increase the number of jobs."
93
+
94
+ jobs_option.jobs_count = [2, jobs_option.default_jobs_count].min
95
+ end
96
+ end
97
+ end
98
+
87
99
  def process_init
88
100
  Drivers::Init.new(stdout: stdout, stderr: stderr).tap do |command|
89
101
  OptionParser.new do |opts|
@@ -98,63 +110,69 @@ module Steep
98
110
  end
99
111
 
100
112
  def process_check
101
- Drivers::Check.new(stdout: stdout, stderr: stderr).tap do |check|
113
+ Drivers::Check.new(stdout: stdout, stderr: stderr).tap do |command|
102
114
  OptionParser.new do |opts|
103
115
  opts.banner = "Usage: steep check [options] [sources]"
104
116
 
105
- opts.on("--steepfile=PATH") {|path| check.steepfile = Pathname(path) }
117
+ opts.on("--steepfile=PATH") {|path| command.steepfile = Pathname(path) }
106
118
  opts.on("--with-expectations[=PATH]", "Type check with expectations saved in PATH (or steep_expectations.yml)") do |path|
107
- check.with_expectations_path = Pathname(path || "steep_expectations.yml")
119
+ command.with_expectations_path = Pathname(path || "steep_expectations.yml")
108
120
  end
109
121
  opts.on("--save-expectations[=PATH]", "Save expectations with current type check result to PATH (or steep_expectations.yml)") do |path|
110
- check.save_expectations_path = Pathname(path || "steep_expectations.yml")
122
+ command.save_expectations_path = Pathname(path || "steep_expectations.yml")
111
123
  end
112
124
  opts.on("--severity-level=LEVEL", /^error|warning|information|hint$/, "Specify the minimum diagnostic severity to be recognized as an error (defaults: warning): error, warning, information, or hint") do |level|
113
- check.severity_level = level.to_sym
125
+ command.severity_level = level.to_sym
114
126
  end
115
- handle_jobs_option check.jobs_option, opts
127
+ handle_jobs_option command.jobs_option, opts
116
128
  handle_logging_options opts
117
129
  end.parse!(argv)
118
130
 
119
- check.command_line_patterns.push *argv
131
+ setup_jobs_for_ci(command.jobs_option)
132
+
133
+ command.command_line_patterns.push *argv
120
134
  end.run
121
135
  end
122
136
 
123
137
  def process_checkfile
124
- Drivers::Checkfile.new(stdout: stdout, stderr: stderr).tap do |check|
138
+ Drivers::Checkfile.new(stdout: stdout, stderr: stderr).tap do |command|
125
139
  OptionParser.new do |opts|
126
140
  opts.banner = "Usage: steep checkfile [options] [files]"
127
141
 
128
- opts.on("--steepfile=PATH") {|path| check.steepfile = Pathname(path) }
129
- opts.on("--all-rbs", "Type check all RBS files") { check.all_rbs = true }
130
- opts.on("--all-ruby", "Type check all Ruby files") { check.all_ruby = true }
142
+ opts.on("--steepfile=PATH") {|path| command.steepfile = Pathname(path) }
143
+ opts.on("--all-rbs", "Type check all RBS files") { command.all_rbs = true }
144
+ opts.on("--all-ruby", "Type check all Ruby files") { command.all_ruby = true }
131
145
  opts.on("--stdin", "Read files to type check from stdin") do
132
146
  while line = stdin.gets()
133
147
  object = JSON.parse(line, symbolize_names: true)
134
148
  Steep.logger.info { "Loading content of `#{object[:path]}` from stdin: #{object[:content].lines[0].chomp}" }
135
- check.stdin_input[Pathname(object[:path])] = object[:content]
149
+ command.stdin_input[Pathname(object[:path])] = object[:content]
136
150
  end
137
151
  end
138
- handle_jobs_option check.jobs_option, opts
152
+ handle_jobs_option command.jobs_option, opts
139
153
  handle_logging_options opts
140
154
  end.parse!(argv)
141
155
 
142
- check.command_line_args.push *argv
156
+ setup_jobs_for_ci(command.jobs_option)
157
+
158
+ command.command_line_args.push *argv
143
159
  end.run
144
160
  end
145
161
 
146
162
  def process_stats
147
- Drivers::Stats.new(stdout: stdout, stderr: stderr).tap do |check|
163
+ Drivers::Stats.new(stdout: stdout, stderr: stderr).tap do |command|
148
164
  OptionParser.new do |opts|
149
165
  opts.banner = "Usage: steep stats [options] [sources]"
150
166
 
151
- opts.on("--steepfile=PATH") {|path| check.steepfile = Pathname(path) }
152
- opts.on("--format=FORMAT", "Specify output format: csv, table") {|format| check.format = format }
153
- handle_jobs_option check.jobs_option, opts
167
+ opts.on("--steepfile=PATH") {|path| command.steepfile = Pathname(path) }
168
+ opts.on("--format=FORMAT", "Specify output format: csv, table") {|format| command.format = format }
169
+ handle_jobs_option command.jobs_option, opts
154
170
  handle_logging_options opts
155
171
  end.parse!(argv)
156
172
 
157
- check.command_line_patterns.push *argv
173
+ setup_jobs_for_ci(command.jobs_option)
174
+
175
+ command.command_line_patterns.push *argv
158
176
  end.run
159
177
  end
160
178
 
@@ -199,6 +217,8 @@ module Steep
199
217
  handle_logging_options opts
200
218
  end.parse!(argv)
201
219
 
220
+ setup_jobs_for_ci(command.jobs_option)
221
+
202
222
  dirs = argv.map {|dir| Pathname(dir) }
203
223
  command.dirs.push(*dirs)
204
224
  end.run
@@ -766,6 +766,15 @@ module Steep
766
766
  )
767
767
  )
768
768
  end
769
+ when :<, :<=
770
+ case defined_in
771
+ when RBS::BuiltinNames::Module.name
772
+ return method_type.with(
773
+ type: method_type.type.with(
774
+ return_type: AST::Types::Logic::ArgIsAncestor.new(location: method_type.type.return_type.location)
775
+ )
776
+ )
777
+ end
769
778
  end
770
779
  end
771
780
 
@@ -7,6 +7,7 @@ module Steep
7
7
  if uri.scheme == "file"
8
8
  path = uri.path or raise
9
9
  path.sub!(%r{^/([a-zA-Z])(:|%3A)//?}i, '\1:/') if dosish
10
+ path = URI::DEFAULT_PARSER.unescape(path)
10
11
  Pathname(path)
11
12
  end
12
13
  end
@@ -20,6 +21,7 @@ module Steep
20
21
  if dosish
21
22
  str_path.insert(0, "/") if str_path[0] != "/"
22
23
  end
24
+ str_path = URI::DEFAULT_PARSER.escape(str_path)
23
25
  URI::File.build(path: str_path)
24
26
  end
25
27
  end
@@ -61,6 +61,15 @@ module Steep
61
61
  end
62
62
  end
63
63
  end
64
+
65
+ def reset_change(uri:, text:)
66
+ push_buffer do |changes|
67
+ if path = Steep::PathHelper.to_pathname(uri)
68
+ path = project.relative_path(path)
69
+ changes[path] = [Services::ContentChange.new(text: text)]
70
+ end
71
+ end
72
+ end
64
73
  end
65
74
  end
66
75
  end
@@ -0,0 +1,41 @@
1
+ module Steep
2
+ module Server
3
+ class DelayQueue
4
+ attr_reader :delay, :thread, :queue, :last_task
5
+
6
+ def initialize(delay:)
7
+ @delay = delay
8
+
9
+ @queue = Thread::Queue.new
10
+
11
+ @thread = Thread.new do
12
+ while (scheduled_at, proc = queue.pop)
13
+ # @type var scheduled_at: Time
14
+ # @type var proc: ^() -> void
15
+
16
+ diff = scheduled_at - Time.now
17
+ case
18
+ when diff > 0.1
19
+ sleep diff
20
+ when diff > 0
21
+ while Time.now < scheduled_at
22
+ # nop
23
+ sleep 0
24
+ end
25
+ end
26
+
27
+ if proc.equal?(last_task)
28
+ proc[]
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ def execute(&block)
35
+ @last_task = block
36
+ scheduled_at = Time.now + delay
37
+ queue << [scheduled_at, block]
38
+ end
39
+ end
40
+ end
41
+ end
@@ -227,9 +227,11 @@ module Steep
227
227
  )
228
228
  )
229
229
 
230
+ type_name = sig_service.latest_env.normalize_type_name(type_name)
231
+
230
232
  case type_name.kind
231
233
  when :class
232
- env = sig_service.latest_env #: RBS::Environment
234
+ env = sig_service.latest_env
233
235
  class_entry = env.module_class_entry(type_name) or raise
234
236
 
235
237
  case class_entry
@@ -407,7 +409,7 @@ module Steep
407
409
  if (items, index = provider.run(line: job.line, column: job.column))
408
410
  signatures = items.map do |item|
409
411
  LSP::Interface::SignatureInformation.new(
410
- label: "(#{item.method_type.type.param_to_s})",
412
+ label: item.method_type.to_s,
411
413
  parameters: item.parameters.map { |param| LSP::Interface::ParameterInformation.new(label: param)},
412
414
  active_parameter: item.active_parameter,
413
415
  documentation: item.comment&.yield_self do |comment|
@@ -402,6 +402,7 @@ module Steep
402
402
 
403
403
  attr_reader :initialize_params
404
404
  attr_accessor :typecheck_automatically
405
+ attr_reader :start_type_checking_queue
405
406
 
406
407
  def initialize(project:, reader:, writer:, interaction_worker:, typecheck_workers:, queue: Queue.new)
407
408
  @project = project
@@ -416,6 +417,7 @@ module Steep
416
417
 
417
418
  @controller = TypeCheckController.new(project: project)
418
419
  @result_controller = ResultController.new()
420
+ @start_type_checking_queue = DelayQueue.new(delay: 0.1)
419
421
  end
420
422
 
421
423
  def start
@@ -488,6 +490,8 @@ module Steep
488
490
  Steep.logger.info { "Processing SendMessageJob: dest=#{job.dest.name}, method=#{job.message[:method] || "-"}, id=#{job.message[:id] || "-"}" }
489
491
  job.dest << job.message
490
492
  end
493
+ when Proc
494
+ job.call()
491
495
  end
492
496
  end
493
497
  end
@@ -529,6 +533,10 @@ module Steep
529
533
  initialize_params&.dig(:capabilities, :window, :workDoneProgress) ? true : false
530
534
  end
531
535
 
536
+ def file_system_watcher_supported?
537
+ initialize_params&.dig(:capabilities, :workspace, :didChangeWatchedFiles, :dynamicRegistration) || false
538
+ end
539
+
532
540
  def process_message_from_client(message)
533
541
  Steep.logger.info "Processing message from client: method=#{message[:method]}, id=#{message[:id]}"
534
542
  id = message[:id]
@@ -551,7 +559,6 @@ module Steep
551
559
  capabilities: LSP::Interface::ServerCapabilities.new(
552
560
  text_document_sync: LSP::Interface::TextDocumentSyncOptions.new(
553
561
  change: LSP::Constant::TextDocumentSyncKind::INCREMENTAL,
554
- save: LSP::Interface::SaveOptions.new(include_text: false),
555
562
  open_close: true
556
563
  ),
557
564
  hover_provider: {
@@ -575,6 +582,52 @@ module Steep
575
582
  )
576
583
  }
577
584
  )
585
+
586
+ if file_system_watcher_supported?
587
+ patterns = [] #: Array[String]
588
+ project.targets.each do |target|
589
+ target.source_pattern.patterns.each do |pat|
590
+ path = project.base_dir + pat
591
+ patterns << path.to_s unless path.directory?
592
+ end
593
+ target.source_pattern.prefixes.each do |pat|
594
+ path = project.base_dir + pat
595
+ patterns << (path + "**/*.rb").to_s unless path.file?
596
+ end
597
+ target.signature_pattern.patterns.each do |pat|
598
+ path = project.base_dir + pat
599
+ patterns << path.to_s unless path.directory?
600
+ end
601
+ target.signature_pattern.prefixes.each do |pat|
602
+ path = project.base_dir + pat
603
+ patterns << (path + "**/*.rbs").to_s unless path.file?
604
+ end
605
+ end
606
+ patterns.sort!
607
+ patterns.uniq!
608
+
609
+ Steep.logger.info { "Setting up didChangeWatchedFiles with pattern: #{patterns.inspect}" }
610
+
611
+ job_queue << SendMessageJob.to_client(
612
+ message: {
613
+ id: SecureRandom.uuid,
614
+ method: "client/registerCapability",
615
+ params: {
616
+ registrations: [
617
+ {
618
+ id: SecureRandom.uuid,
619
+ method: "workspace/didChangeWatchedFiles",
620
+ registerOptions: {
621
+ watchers: patterns.map do |pattern|
622
+ { globPattern: pattern }
623
+ end
624
+ }
625
+ }
626
+ ]
627
+ }
628
+ }
629
+ )
630
+ end
578
631
  end
579
632
  end
580
633
 
@@ -585,28 +638,71 @@ module Steep
585
638
  end
586
639
  end
587
640
 
641
+ when "workspace/didChangeWatchedFiles"
642
+ message[:params][:changes].each do |change|
643
+ uri = change[:uri]
644
+ type = change[:type]
645
+
646
+ path = PathHelper.to_pathname(uri) or next
647
+
648
+ controller.push_changes(path)
649
+
650
+ case type
651
+ when 1, 2
652
+ content = path.read
653
+ when 4
654
+ # Deleted
655
+ content = ""
656
+ end
657
+
658
+ broadcast_notification({
659
+ method: "$/file/reset",
660
+ params: { uri: uri, content: content }
661
+ })
662
+ end
663
+
664
+ if typecheck_automatically
665
+ start_type_checking_queue.execute do
666
+ job_queue.push(
667
+ -> do
668
+ last_request = current_type_check_request
669
+ if request = controller.make_request(last_request: last_request)
670
+ start_type_check(request, last_request: last_request, start_progress: request.total > 10)
671
+ end
672
+ end
673
+ )
674
+ end
675
+ end
676
+
588
677
  when "textDocument/didChange"
589
678
  if path = pathname(message[:params][:textDocument][:uri])
590
679
  broadcast_notification(message)
591
680
  controller.push_changes(path)
592
- end
593
681
 
594
- when "textDocument/didSave"
595
- if path = pathname(message[:params][:textDocument][:uri])
596
682
  if typecheck_automatically
597
- if request = controller.make_request(last_request: current_type_check_request)
598
- start_type_check(
599
- request,
600
- last_request: current_type_check_request,
601
- start_progress: request.total > 10
683
+ start_type_checking_queue.execute do
684
+ job_queue.push(
685
+ -> do
686
+ last_request = current_type_check_request
687
+ if request = controller.make_request(last_request: last_request)
688
+ start_type_check(request, last_request: last_request, start_progress: request.total > 10)
689
+ end
690
+ end
602
691
  )
603
692
  end
604
693
  end
605
694
  end
606
695
 
607
696
  when "textDocument/didOpen"
608
- if path = pathname(message[:params][:textDocument][:uri])
697
+ uri = message[:params][:textDocument][:uri]
698
+ text = message[:params][:textDocument][:text]
699
+
700
+ if path = pathname(uri)
609
701
  controller.update_priority(open: path)
702
+ broadcast_notification({
703
+ method: "$/file/reset",
704
+ params: { uri: uri, content: text }
705
+ })
610
706
  end
611
707
 
612
708
  when "textDocument/didClose"