steep 1.5.3 → 1.6.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
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 +35 -0
  5. data/Gemfile +3 -1
  6. data/Gemfile.lock +36 -9
  7. data/gemfile_steep/Gemfile.lock +3 -3
  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 +59 -17
  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: c6fac293e42dfa305b98aa0f8dbf49acdad88f0794a6081f7c6e548ccab49f4b
4
- data.tar.gz: 2824c6289d92fadc1d062571d9652d759fc7774531d532d2bcf846ce20240bec
3
+ metadata.gz: d8a5efa97a65add83ffc658ddfbdf9a436accc8dd4472969610b2224140543ea
4
+ data.tar.gz: 537d28e28ae6b3bc47392170948db46d3d7fa50d61f6f274182b922a253394ea
5
5
  SHA512:
6
- metadata.gz: 800d77371db9fcc77688a30c442355d84f31cbbf21ccc7fda46155e394b011887b98b1c47f3f9a9a117087c4790a3fecfee9ac4bdbfb4dea096deafa959e2d96
7
- data.tar.gz: 2d1eed9a3397eeeb1c5675a7038acbf5cdab173aec3285d8c88df925f9fe7d07bf4ef0f248ae7df5895bef21509bc0e2156883c84a7bdb4a53598b1608041864
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,41 @@
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
+
5
40
  ## 1.5.3 (2023-08-10)
6
41
 
7
42
  ### Type checker core
data/Gemfile CHANGED
@@ -4,9 +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
+
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.3)
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,53 +20,80 @@ PATH
20
20
  GEM
21
21
  remote: https://rubygems.org/
22
22
  specs:
23
- activesupport (7.0.7)
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)
58
+ minitest (5.20.0)
42
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
67
+ psych (5.1.0)
68
+ stringio
49
69
  racc (1.7.1)
50
70
  rainbow (3.1.1)
51
71
  rake (13.0.6)
52
72
  rb-fsevent (0.11.2)
53
73
  rb-inotify (0.10.1)
54
74
  ffi (~> 1.0)
55
- rbs (3.1.3)
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)
56
81
  securerandom (0.2.2)
57
82
  stackprof (0.2.25)
58
- strscan (3.0.6)
83
+ stringio (3.0.8)
84
+ strscan (3.0.7)
59
85
  terminal-table (3.0.2)
60
86
  unicode-display_width (>= 1.1.1, < 3)
61
87
  tzinfo (2.0.6)
62
88
  concurrent-ruby (~> 1.0)
63
- unicode-display_width (2.4.2)
89
+ unicode-display_width (2.5.0)
64
90
 
65
91
  PLATFORMS
66
92
  ruby
67
93
 
68
94
  DEPENDENCIES
69
- minitest (~> 5.19)
95
+ debug
96
+ minitest (~> 5.20)
70
97
  minitest-hooks
71
98
  minitest-slow_test
72
99
  rake
@@ -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)
@@ -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.2)
31
+ rbs (3.1.3)
32
32
  securerandom (0.2.2)
33
- steep (1.5.2)
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"