typeprof 0.15.3 → 0.20.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (366) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +1 -1
  3. data/Gemfile.lock +5 -5
  4. data/doc/ide.md +81 -0
  5. data/doc/typeprof-for-ide-log.png +0 -0
  6. data/doc/typeprof-for-ide.png +0 -0
  7. data/exe/typeprof +5 -1
  8. data/lib/typeprof/analyzer.rb +235 -56
  9. data/lib/typeprof/arguments.rb +1 -0
  10. data/lib/typeprof/builtin.rb +23 -23
  11. data/lib/typeprof/cli.rb +23 -5
  12. data/lib/typeprof/code-range.rb +177 -0
  13. data/lib/typeprof/config.rb +41 -20
  14. data/lib/typeprof/container-type.rb +3 -0
  15. data/lib/typeprof/export.rb +191 -15
  16. data/lib/typeprof/import.rb +33 -9
  17. data/lib/typeprof/insns-def.rb +1 -0
  18. data/lib/typeprof/iseq.rb +224 -17
  19. data/lib/typeprof/lsp.rb +884 -0
  20. data/lib/typeprof/method.rb +15 -11
  21. data/lib/typeprof/type.rb +50 -42
  22. data/lib/typeprof/utils.rb +18 -1
  23. data/lib/typeprof/version.rb +1 -1
  24. data/lib/typeprof.rb +3 -5
  25. data/typeprof-lsp +3 -0
  26. data/typeprof.gemspec +2 -2
  27. data/vscode/.gitignore +5 -0
  28. data/vscode/.vscode/launch.json +16 -0
  29. data/vscode/.vscodeignore +7 -0
  30. data/vscode/README.md +22 -0
  31. data/vscode/development.md +31 -0
  32. data/vscode/package-lock.json +3249 -0
  33. data/vscode/package.json +71 -0
  34. data/vscode/sandbox/test.rb +24 -0
  35. data/vscode/src/extension.ts +299 -0
  36. data/vscode/tsconfig.json +15 -0
  37. metadata +21 -334
  38. data/smoke/alias.rb +0 -31
  39. data/smoke/alias2.rb +0 -21
  40. data/smoke/any-cbase.rb +0 -5
  41. data/smoke/any1.rb +0 -16
  42. data/smoke/any2.rb +0 -18
  43. data/smoke/arguments.rb +0 -17
  44. data/smoke/arguments2.rb +0 -56
  45. data/smoke/array-each.rb +0 -15
  46. data/smoke/array-each2.rb +0 -16
  47. data/smoke/array-each3.rb +0 -13
  48. data/smoke/array-ltlt.rb +0 -14
  49. data/smoke/array-ltlt2.rb +0 -17
  50. data/smoke/array-map.rb +0 -12
  51. data/smoke/array-map2.rb +0 -11
  52. data/smoke/array-map3.rb +0 -23
  53. data/smoke/array-mul.rb +0 -18
  54. data/smoke/array-plus1.rb +0 -11
  55. data/smoke/array-plus2.rb +0 -16
  56. data/smoke/array-pop.rb +0 -12
  57. data/smoke/array-range-aref.rb +0 -71
  58. data/smoke/array-replace.rb +0 -13
  59. data/smoke/array-s-aref.rb +0 -12
  60. data/smoke/array1.rb +0 -27
  61. data/smoke/array10.rb +0 -15
  62. data/smoke/array11.rb +0 -14
  63. data/smoke/array12.rb +0 -25
  64. data/smoke/array13.rb +0 -31
  65. data/smoke/array14.rb +0 -14
  66. data/smoke/array15.rb +0 -16
  67. data/smoke/array2.rb +0 -28
  68. data/smoke/array3.rb +0 -26
  69. data/smoke/array4.rb +0 -15
  70. data/smoke/array5.rb +0 -14
  71. data/smoke/array6.rb +0 -17
  72. data/smoke/array7.rb +0 -14
  73. data/smoke/array8.rb +0 -13
  74. data/smoke/array9.rb +0 -13
  75. data/smoke/attr-module.rb +0 -24
  76. data/smoke/attr-vis.rb +0 -43
  77. data/smoke/attr-vis.rbs +0 -4
  78. data/smoke/attr.rb +0 -28
  79. data/smoke/autoload.rb +0 -14
  80. data/smoke/backtrace.rb +0 -33
  81. data/smoke/block-ambiguous.rb +0 -37
  82. data/smoke/block-args1-rest.rb +0 -64
  83. data/smoke/block-args1.rb +0 -60
  84. data/smoke/block-args2-rest.rb +0 -64
  85. data/smoke/block-args2.rb +0 -60
  86. data/smoke/block-args3-rest.rb +0 -75
  87. data/smoke/block-args3.rb +0 -71
  88. data/smoke/block-blockarg.rb +0 -28
  89. data/smoke/block-kwarg.rb +0 -53
  90. data/smoke/block1.rb +0 -23
  91. data/smoke/block10.rb +0 -15
  92. data/smoke/block11.rb +0 -40
  93. data/smoke/block12.rb +0 -23
  94. data/smoke/block13.rb +0 -9
  95. data/smoke/block13.rbs +0 -3
  96. data/smoke/block14.rb +0 -18
  97. data/smoke/block2.rb +0 -15
  98. data/smoke/block3.rb +0 -38
  99. data/smoke/block4.rb +0 -19
  100. data/smoke/block5.rb +0 -21
  101. data/smoke/block6.rb +0 -21
  102. data/smoke/block7.rb +0 -19
  103. data/smoke/block8.rb +0 -28
  104. data/smoke/block9.rb +0 -13
  105. data/smoke/block_given.rb +0 -37
  106. data/smoke/blown.rb +0 -13
  107. data/smoke/break1.rb +0 -19
  108. data/smoke/break2.rb +0 -16
  109. data/smoke/break3.rb +0 -13
  110. data/smoke/break4.rb +0 -17
  111. data/smoke/case.rb +0 -17
  112. data/smoke/case2.rb +0 -18
  113. data/smoke/case3.rb +0 -17
  114. data/smoke/class-hierarchy.rb +0 -54
  115. data/smoke/class-hierarchy2.rb +0 -27
  116. data/smoke/class-new.rb +0 -15
  117. data/smoke/class.rb +0 -7
  118. data/smoke/class_eval.rb +0 -22
  119. data/smoke/class_instance_var.rb +0 -9
  120. data/smoke/class_method.rb +0 -25
  121. data/smoke/class_method2.rb +0 -21
  122. data/smoke/class_method3.rb +0 -29
  123. data/smoke/constant1.rb +0 -46
  124. data/smoke/constant2.rb +0 -36
  125. data/smoke/constant3.rb +0 -10
  126. data/smoke/constant4.rb +0 -12
  127. data/smoke/context-sensitive1.rb +0 -13
  128. data/smoke/cvar.rb +0 -31
  129. data/smoke/cvar2.rb +0 -17
  130. data/smoke/define_method.rb +0 -16
  131. data/smoke/define_method2.rb +0 -18
  132. data/smoke/define_method3.rb +0 -14
  133. data/smoke/define_method3.rbs +0 -3
  134. data/smoke/define_method4.rb +0 -15
  135. data/smoke/define_method4.rbs +0 -3
  136. data/smoke/define_method5.rb +0 -12
  137. data/smoke/define_method6.rb +0 -19
  138. data/smoke/define_method7.rb +0 -18
  139. data/smoke/demo.rb +0 -81
  140. data/smoke/demo1.rb +0 -17
  141. data/smoke/demo10.rb +0 -21
  142. data/smoke/demo11.rb +0 -12
  143. data/smoke/demo2.rb +0 -15
  144. data/smoke/demo3.rb +0 -17
  145. data/smoke/demo4.rb +0 -27
  146. data/smoke/demo5.rb +0 -16
  147. data/smoke/demo6.rb +0 -22
  148. data/smoke/demo7.rb +0 -15
  149. data/smoke/demo8.rb +0 -19
  150. data/smoke/demo9.rb +0 -18
  151. data/smoke/dummy-execution1.rb +0 -15
  152. data/smoke/dummy-execution2.rb +0 -16
  153. data/smoke/dummy_element.rb +0 -14
  154. data/smoke/ensure1.rb +0 -21
  155. data/smoke/enum_for.rb +0 -15
  156. data/smoke/enum_for2.rb +0 -17
  157. data/smoke/enumerator.rb +0 -16
  158. data/smoke/expandarray1.rb +0 -23
  159. data/smoke/expandarray2.rb +0 -24
  160. data/smoke/extended.rb +0 -38
  161. data/smoke/fib.rb +0 -28
  162. data/smoke/flip-flop.rb +0 -28
  163. data/smoke/flow1.rb +0 -17
  164. data/smoke/flow10.rb +0 -17
  165. data/smoke/flow11.rb +0 -17
  166. data/smoke/flow2.rb +0 -15
  167. data/smoke/flow3.rb +0 -15
  168. data/smoke/flow4.rb +0 -5
  169. data/smoke/flow5.rb +0 -20
  170. data/smoke/flow6.rb +0 -20
  171. data/smoke/flow7.rb +0 -21
  172. data/smoke/flow8.rb +0 -14
  173. data/smoke/flow9.rb +0 -12
  174. data/smoke/for.rb +0 -9
  175. data/smoke/freeze.rb +0 -12
  176. data/smoke/function.rb +0 -17
  177. data/smoke/gvar.rb +0 -14
  178. data/smoke/gvar2.rb +0 -15
  179. data/smoke/gvar2.rbs +0 -1
  180. data/smoke/hash-bot.rb +0 -12
  181. data/smoke/hash-fetch.rb +0 -28
  182. data/smoke/hash-merge-bang.rb +0 -12
  183. data/smoke/hash1.rb +0 -20
  184. data/smoke/hash2.rb +0 -13
  185. data/smoke/hash3.rb +0 -14
  186. data/smoke/hash4.rb +0 -11
  187. data/smoke/hash5.rb +0 -14
  188. data/smoke/huge_union.rb +0 -86
  189. data/smoke/identifier_keywords.rb +0 -17
  190. data/smoke/included.rb +0 -38
  191. data/smoke/inheritance.rb +0 -34
  192. data/smoke/inheritance2.rb +0 -35
  193. data/smoke/inherited.rb +0 -26
  194. data/smoke/initialize.rb +0 -28
  195. data/smoke/instance_eval.rb +0 -18
  196. data/smoke/instance_eval2.rb +0 -10
  197. data/smoke/instance_eval3.rb +0 -25
  198. data/smoke/instance_eval4.rb +0 -12
  199. data/smoke/int_times.rb +0 -15
  200. data/smoke/integer.rb +0 -11
  201. data/smoke/ivar.rb +0 -31
  202. data/smoke/ivar2.rb +0 -30
  203. data/smoke/ivar3.rb +0 -17
  204. data/smoke/ivar3.rbs +0 -3
  205. data/smoke/ivar4.rb +0 -21
  206. data/smoke/kernel-class.rb +0 -13
  207. data/smoke/keyword1.rb +0 -12
  208. data/smoke/keyword2.rb +0 -12
  209. data/smoke/keyword3.rb +0 -12
  210. data/smoke/keyword4.rb +0 -12
  211. data/smoke/keyword5.rb +0 -16
  212. data/smoke/kwrest.rb +0 -13
  213. data/smoke/kwrest.rbs +0 -3
  214. data/smoke/kwsplat1.rb +0 -43
  215. data/smoke/kwsplat2.rb +0 -13
  216. data/smoke/lit-complex.rb +0 -10
  217. data/smoke/lit-encoding.rb +0 -10
  218. data/smoke/manual-rbs.rb +0 -29
  219. data/smoke/manual-rbs.rbs +0 -3
  220. data/smoke/manual-rbs2.rb +0 -21
  221. data/smoke/manual-rbs2.rbs +0 -8
  222. data/smoke/manual-rbs3.rb +0 -13
  223. data/smoke/manual-rbs3.rbs +0 -3
  224. data/smoke/masgn1.rb +0 -14
  225. data/smoke/masgn2.rb +0 -18
  226. data/smoke/masgn3.rb +0 -13
  227. data/smoke/method_in_branch.rb +0 -23
  228. data/smoke/method_missing.rb +0 -29
  229. data/smoke/module1.rb +0 -29
  230. data/smoke/module2.rb +0 -28
  231. data/smoke/module3.rb +0 -33
  232. data/smoke/module4.rb +0 -35
  233. data/smoke/module5.rb +0 -17
  234. data/smoke/module6.rb +0 -40
  235. data/smoke/module_function1.rb +0 -29
  236. data/smoke/module_function2.rb +0 -29
  237. data/smoke/multiple-include.rb +0 -15
  238. data/smoke/multiple-superclass.rb +0 -28
  239. data/smoke/next1.rb +0 -21
  240. data/smoke/next2.rb +0 -17
  241. data/smoke/noname.rb +0 -9
  242. data/smoke/object-send1.rb +0 -23
  243. data/smoke/object-send2.rb +0 -10
  244. data/smoke/object-send3.rb +0 -18
  245. data/smoke/once.rb +0 -13
  246. data/smoke/optional1.rb +0 -14
  247. data/smoke/optional2.rb +0 -16
  248. data/smoke/optional3.rb +0 -11
  249. data/smoke/or_raise.rb +0 -18
  250. data/smoke/parameterizedd-self.rb +0 -20
  251. data/smoke/parameterizedd-self2.rb +0 -15
  252. data/smoke/pathname1.rb +0 -14
  253. data/smoke/pathname2.rb +0 -14
  254. data/smoke/pattern-match1.rb +0 -19
  255. data/smoke/pattern-match2.rb +0 -16
  256. data/smoke/prepend1.rb +0 -33
  257. data/smoke/prepend2.rb +0 -10
  258. data/smoke/prepend2.rbs +0 -9
  259. data/smoke/primitive_method.rb +0 -19
  260. data/smoke/printf.rb +0 -20
  261. data/smoke/proc.rb +0 -20
  262. data/smoke/proc2.rb +0 -17
  263. data/smoke/proc3.rb +0 -15
  264. data/smoke/proc4.rb +0 -12
  265. data/smoke/proc5.rb +0 -19
  266. data/smoke/proc6.rb +0 -13
  267. data/smoke/proc7.rb +0 -32
  268. data/smoke/public.rb +0 -38
  269. data/smoke/range.rb +0 -14
  270. data/smoke/rbs-alias.rb +0 -10
  271. data/smoke/rbs-alias.rbs +0 -4
  272. data/smoke/rbs-attr.rb +0 -27
  273. data/smoke/rbs-attr.rbs +0 -5
  274. data/smoke/rbs-attr2.rb +0 -11
  275. data/smoke/rbs-attr2.rbs +0 -3
  276. data/smoke/rbs-extend.rb +0 -10
  277. data/smoke/rbs-extend.rbs +0 -7
  278. data/smoke/rbs-interface.rb +0 -25
  279. data/smoke/rbs-interface.rbs +0 -12
  280. data/smoke/rbs-module.rb +0 -26
  281. data/smoke/rbs-module.rbs +0 -4
  282. data/smoke/rbs-opt-and-rest.rb +0 -10
  283. data/smoke/rbs-opt-and-rest.rbs +0 -3
  284. data/smoke/rbs-proc1.rb +0 -10
  285. data/smoke/rbs-proc1.rbs +0 -3
  286. data/smoke/rbs-proc2.rb +0 -21
  287. data/smoke/rbs-proc2.rbs +0 -3
  288. data/smoke/rbs-proc3.rb +0 -14
  289. data/smoke/rbs-proc3.rbs +0 -4
  290. data/smoke/rbs-record.rb +0 -18
  291. data/smoke/rbs-record.rbs +0 -4
  292. data/smoke/rbs-tyvar.rb +0 -19
  293. data/smoke/rbs-tyvar.rbs +0 -5
  294. data/smoke/rbs-tyvar2.rb +0 -21
  295. data/smoke/rbs-tyvar2.rbs +0 -9
  296. data/smoke/rbs-tyvar3.rb +0 -18
  297. data/smoke/rbs-tyvar3.rbs +0 -5
  298. data/smoke/rbs-tyvar4.rb +0 -37
  299. data/smoke/rbs-tyvar5.rb +0 -13
  300. data/smoke/rbs-tyvar5.rbs +0 -8
  301. data/smoke/rbs-tyvar6.rb +0 -18
  302. data/smoke/rbs-tyvar6.rbs +0 -12
  303. data/smoke/rbs-tyvar7.rb +0 -12
  304. data/smoke/rbs-tyvar7.rbs +0 -7
  305. data/smoke/rbs-vars.rb +0 -35
  306. data/smoke/rbs-vars.rbs +0 -7
  307. data/smoke/redo1.rb +0 -22
  308. data/smoke/redo2.rb +0 -23
  309. data/smoke/req-keyword.rb +0 -13
  310. data/smoke/require1.rb +0 -13
  311. data/smoke/require2.rb +0 -13
  312. data/smoke/rescue1.rb +0 -21
  313. data/smoke/rescue2.rb +0 -23
  314. data/smoke/rescue3.rb +0 -20
  315. data/smoke/rescue4.rb +0 -17
  316. data/smoke/respond_to.rb +0 -23
  317. data/smoke/rest-farg.rb +0 -11
  318. data/smoke/rest1.rb +0 -26
  319. data/smoke/rest2.rb +0 -31
  320. data/smoke/rest3.rb +0 -37
  321. data/smoke/rest4.rb +0 -19
  322. data/smoke/rest5.rb +0 -11
  323. data/smoke/rest6.rb +0 -12
  324. data/smoke/retry1.rb +0 -21
  325. data/smoke/return.rb +0 -14
  326. data/smoke/reveal.rb +0 -13
  327. data/smoke/simple.rb +0 -12
  328. data/smoke/singleton_class.rb +0 -8
  329. data/smoke/singleton_method.rb +0 -12
  330. data/smoke/step.rb +0 -18
  331. data/smoke/string-split.rb +0 -12
  332. data/smoke/struct-keyword_init.rb +0 -10
  333. data/smoke/struct.rb +0 -13
  334. data/smoke/struct2.rb +0 -25
  335. data/smoke/struct3.rb +0 -14
  336. data/smoke/struct4.rb +0 -7
  337. data/smoke/struct5.rb +0 -16
  338. data/smoke/struct6.rb +0 -15
  339. data/smoke/struct7.rb +0 -17
  340. data/smoke/stub-keyword.rb +0 -10
  341. data/smoke/super1.rb +0 -69
  342. data/smoke/super2.rb +0 -16
  343. data/smoke/super3.rb +0 -20
  344. data/smoke/super4.rb +0 -45
  345. data/smoke/super5.rb +0 -38
  346. data/smoke/svar1.rb +0 -13
  347. data/smoke/symbol-proc-attr.rb +0 -22
  348. data/smoke/symbol-proc-attr2.rb +0 -15
  349. data/smoke/symbol-proc-bot.rb +0 -13
  350. data/smoke/symbol-proc.rb +0 -25
  351. data/smoke/tap1.rb +0 -18
  352. data/smoke/toplevel.rb +0 -13
  353. data/smoke/two-map.rb +0 -18
  354. data/smoke/type_var.rb +0 -11
  355. data/smoke/typed_method.rb +0 -16
  356. data/smoke/uninitialize-var.rb +0 -13
  357. data/smoke/union-recv.rb +0 -35
  358. data/smoke/user-demo.rb +0 -15
  359. data/smoke/wrong-extend.rb +0 -27
  360. data/smoke/wrong-include.rb +0 -27
  361. data/smoke/wrong-include2.rb +0 -17
  362. data/smoke/wrong-rbs.rb +0 -15
  363. data/smoke/wrong-rbs.rbs +0 -7
  364. data/testbed/ao.rb +0 -297
  365. data/testbed/diff-lcs-entrypoint.rb +0 -4
  366. data/testbed/goodcheck-Gemfile.lock +0 -51
@@ -0,0 +1,884 @@
1
+ require "socket"
2
+ require "json"
3
+ require "uri"
4
+
5
+ module TypeProf
6
+ def self.start_lsp_server(config)
7
+ if config.lsp_options[:stdio]
8
+ reader = LSP::Reader.new($stdin)
9
+ writer = LSP::Writer.new($stdout)
10
+ # pipe all builtin print output to stderr to avoid conflicting with lsp
11
+ $stdout = $stderr
12
+ TypeProf::LSP::Server.new(config, reader, writer).run
13
+ else
14
+ Socket.tcp_server_sockets("localhost", config.lsp_options[:port]) do |servs|
15
+ serv = servs[0].local_address
16
+ $stdout << JSON.generate({
17
+ host: serv.ip_address,
18
+ port: serv.ip_port,
19
+ pid: $$,
20
+ })
21
+ $stdout.flush
22
+
23
+ $stdout = $stderr
24
+
25
+ Socket.accept_loop(servs) do |sock|
26
+ sock.set_encoding("UTF-8")
27
+ begin
28
+ reader = LSP::Reader.new(sock)
29
+ writer = LSP::Writer.new(sock)
30
+ TypeProf::LSP::Server.new(config, reader, writer).run
31
+ ensure
32
+ sock.close
33
+ end
34
+ exit
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ module LSP
41
+ CompletionSession = Struct.new(:results, :row, :start_col_offset)
42
+ class CompletionSession
43
+ def reusable?(other_row, other_start_col_offset)
44
+ other_row == self.row && other_start_col_offset == self.start_col_offset
45
+ end
46
+ end
47
+
48
+ class Text
49
+ class AnalysisToken < Utils::CancelToken
50
+ def initialize
51
+ @timer = Utils::TimerCancelToken.new(1)
52
+ @cancelled = false
53
+ end
54
+
55
+ def cancel
56
+ @cancelled = true
57
+ end
58
+
59
+ def cancelled?
60
+ @timer.cancelled? || @cancelled
61
+ end
62
+ end
63
+
64
+ def initialize(server, uri, text, version)
65
+ @server = server
66
+ @uri = uri
67
+ @text = text
68
+ @version = version
69
+ @sigs = nil
70
+
71
+ @last_analysis_cancel_token = nil
72
+ @analysis_queue = Queue.new
73
+ @analysis_thread = Thread.new do
74
+ loop do
75
+ work = @analysis_queue.pop
76
+ begin
77
+ work.call
78
+ rescue Exception
79
+ puts "Rescued exception:"
80
+ puts $!.full_message
81
+ puts
82
+ end
83
+ end
84
+ end
85
+
86
+ # analyze synchronously to respond the first codeLens request
87
+ res, def_table, caller_table = self.analyze(uri, text)
88
+ on_text_changed_analysis(res, def_table, caller_table)
89
+ end
90
+
91
+ attr_reader :text, :version, :sigs, :caller_table
92
+ attr_accessor :definition_table
93
+
94
+ def lines
95
+ @text.lines
96
+ end
97
+
98
+ def apply_changes(changes, version)
99
+ @definition_table = nil
100
+ text = @text.empty? ? [] : @text.lines
101
+ changes.each do |change|
102
+ case change
103
+ in {
104
+ range: {
105
+ start: { line: start_row, character: start_col },
106
+ end: { line: end_row , character: end_col }
107
+ },
108
+ text: change_text,
109
+ }
110
+ else
111
+ raise
112
+ end
113
+ text << "" if start_row == text.size
114
+ text << "" if end_row == text.size
115
+ if start_row == end_row
116
+ text[start_row][start_col...end_col] = change_text
117
+ else
118
+ text[start_row][start_col..] = ""
119
+ text[end_row][...end_col] = ""
120
+ change_text = change_text.lines
121
+ case change_text.size
122
+ when 0
123
+ text[start_row] += text[end_row]
124
+ text[start_row + 1 .. end_row] = []
125
+ when 1
126
+ text[start_row] += change_text.first + text[end_row]
127
+ text[start_row + 1 .. end_row] = []
128
+ else
129
+ text[start_row] += change_text.shift
130
+ text[end_row].prepend(change_text.pop)
131
+ text[start_row + 1 ... end_row - 1] = change_text
132
+ end
133
+ end
134
+ end
135
+ @text = text.join
136
+ @version = version
137
+
138
+ on_text_changed
139
+ end
140
+
141
+ def new_code_completion_session(row, start_offset, end_offset)
142
+ lines = @text.lines
143
+ lines[row][start_offset, end_offset] = ".__typeprof_lsp_completion"
144
+ tmp_text = lines.join
145
+ res, = analyze(@uri, tmp_text)
146
+ if res && res[:completion]
147
+ results = res[:completion].keys.map do |name|
148
+ {
149
+ label: name,
150
+ kind: 2, # Method
151
+ }
152
+ end
153
+ return CompletionSession.new(results, row, start_offset)
154
+ else
155
+ nil
156
+ end
157
+ end
158
+
159
+ def code_complete(loc, trigger_kind)
160
+ case loc
161
+ in { line: row, character: col }
162
+ end
163
+ unless row < @text.lines.length && col >= 1 && @text.lines[row][0, col] =~ /\.\w*$/
164
+ return nil
165
+ end
166
+ start_offset = $~.begin(0)
167
+ end_offset = $&.size
168
+
169
+ case trigger_kind
170
+ when LSP::CompletionTriggerKind::TRIGGER_FOR_INCOMPLETE_COMPLETIONS
171
+ unless @current_completion_session&.reusable?(row, start_offset)
172
+ puts "no reusable completion session but got TRIGGER_FOR_INCOMPLETE_COMPLETIONS"
173
+ @current_completion_session = new_code_completion_session(row, start_offset, end_offset)
174
+ end
175
+ return @current_completion_session.results
176
+ else
177
+ @current_completion_session = new_code_completion_session(row, start_offset, end_offset)
178
+ return @current_completion_session&.results
179
+ end
180
+ end
181
+
182
+ private def locate_arg_index_in_signature_help(node, loc, sig_help)
183
+ case node.type
184
+ when :FCALL
185
+ _mid, args_node = node.children
186
+ when :CALL
187
+ _recv, _mid, args_node = node.children
188
+ end
189
+
190
+ idx = 0
191
+
192
+ if args_node
193
+ arg_nodes = args_node.children.compact
194
+
195
+ arg_indexes = {}
196
+ hash = arg_nodes.pop if arg_nodes.last&.type == :HASH
197
+
198
+ arg_nodes.each_with_index do |node, i|
199
+ # Ingore arguments after rest argument
200
+ break if node.type == :LIST || node.type == :ARGSCAT
201
+
202
+ arg_indexes[i] = ISeq.code_range_from_node(node)
203
+ end
204
+
205
+ # Handle keyword arguments
206
+ if hash
207
+ hash.children.last.children.compact.each_slice(2) do |node1, node2|
208
+ # key: expression
209
+ # ^^^^ ^^^^^^^^^^
210
+ # node1 node2
211
+ key = node1.children.first
212
+ arg_indexes[key] =
213
+ CodeRange.new(
214
+ CodeLocation.new(node1.first_lineno, node1.first_lineno),
215
+ CodeLocation.new(node2.last_lineno, node2.last_lineno),
216
+ )
217
+ end
218
+ end
219
+
220
+ if arg_indexes.size >= 1 && arg_indexes.values.last.last < loc
221
+ # There is the cursor after the last argument: "foo(111, 222,|)"
222
+ idx = arg_indexes.size - 1
223
+ prev_cr = arg_indexes.values.last
224
+ if prev_cr.last.lineno == loc.lineno
225
+ line = @text.lines[prev_cr.last.lineno - 1]
226
+ idx += 1 if line[prev_cr.last.column..loc.column].include?(",")
227
+ end
228
+ else
229
+ # There is the cursor within any argument: "foo(111,|222)" or foo(111, 22|2)"
230
+ prev_cr = nil
231
+ arg_indexes.each do |i, cr|
232
+ idx = sig_help.keys.index(i)
233
+ if loc < cr.first
234
+ break if !prev_cr || prev_cr.last.lineno != loc.lineno
235
+ line = @text.lines[prev_cr.last.lineno - 1]
236
+ idx -= 1 unless line[prev_cr.last.column..loc.column].include?(",")
237
+ break
238
+ end
239
+ break if loc <= cr.last
240
+ prev_cr = cr
241
+ end
242
+ end
243
+ end
244
+
245
+ idx
246
+ end
247
+
248
+ def signature_help(loc, trigger_kind)
249
+ loc = CodeLocation.from_lsp(loc)
250
+
251
+ res, = analyze(@uri, @text, signature_help_loc: loc)
252
+
253
+ if res
254
+ res[:signature_help].filter_map do |sig_str, sig_help, node_id|
255
+ node = ISeq.find_node_by_id(@text, node_id)
256
+ if node && ISeq.code_range_from_node(node).contain_loc?(loc)
257
+ idx = locate_arg_index_in_signature_help(node, loc, sig_help)
258
+
259
+ {
260
+ label: sig_str,
261
+ parameters: sig_help.values.map do |r|
262
+ {
263
+ label: [r.begin, r.end],
264
+ }
265
+ end,
266
+ activeParameter: idx,
267
+ }
268
+ end
269
+ end
270
+ else
271
+ nil
272
+ end
273
+ end
274
+
275
+ def analyze(uri, text, cancel_token: nil, signature_help_loc: nil)
276
+ config = @server.typeprof_config.dup
277
+ path = URI(uri).path
278
+ config.rb_files = [[path, text]]
279
+ config.rbs_files = ["typeprof.rbs"] # XXX
280
+ config.verbose = 0
281
+ config.max_sec = 1
282
+ config.options[:show_errors] = true
283
+ config.options[:show_indicator] = false
284
+ config.options[:lsp] = true
285
+ config.options[:signature_help_loc] = [path, signature_help_loc] if signature_help_loc
286
+
287
+ TypeProf.analyze(config, cancel_token)
288
+ rescue SyntaxError
289
+ end
290
+
291
+ def push_analysis_queue(&work)
292
+ @analysis_queue.push(work)
293
+ end
294
+
295
+ def on_text_changed
296
+ cancel_token = AnalysisToken.new
297
+ @last_analysis_cancel_token&.cancel
298
+ @last_analysis_cancel_token = cancel_token
299
+
300
+ uri = @uri
301
+ text = @text
302
+ self.push_analysis_queue do
303
+ if cancel_token.cancelled?
304
+ next
305
+ end
306
+ res, def_table, caller_table = self.analyze(uri, text, cancel_token: cancel_token)
307
+ unless cancel_token.cancelled?
308
+ on_text_changed_analysis(res, def_table, caller_table)
309
+ end
310
+ end
311
+ end
312
+
313
+ def on_text_changed_analysis(res, definition_table, caller_table)
314
+ @definition_table = definition_table
315
+ @caller_table = caller_table
316
+ return unless res
317
+
318
+ @sigs = []
319
+ res[:sigs].each do |file, lineno, sig_str, rbs_code_range, class_kind, class_name|
320
+ uri0 = "file://" + file
321
+ if @uri == uri0
322
+ command = { title: sig_str }
323
+ if rbs_code_range
324
+ command[:command] = "typeprof.jumpToRBS"
325
+ command[:arguments] = [uri0, { line: lineno - 1, character: 0 }, @server.root_uri + "/" + rbs_code_range[0], rbs_code_range[1].to_lsp]
326
+ else
327
+ command[:command] = "typeprof.createPrototypeRBS"
328
+ command[:arguments] = [class_kind, class_name, sig_str]
329
+ end
330
+ @sigs << {
331
+ range: {
332
+ start: { line: lineno - 1, character: 0 },
333
+ end: { line: lineno - 1, character: 1 },
334
+ },
335
+ command: command,
336
+ }
337
+ end
338
+ end
339
+
340
+ diagnostics = {}
341
+ res[:errors]&.each do |(file, code_range), msg|
342
+ next unless file and code_range
343
+ uri0 = "file://" + file
344
+ diagnostics[uri0] ||= []
345
+ diagnostics[uri0] << {
346
+ range: code_range.to_lsp,
347
+ severity: 1,
348
+ source: "TypeProf",
349
+ message: msg,
350
+ }
351
+ end
352
+
353
+ @server.send_request("workspace/codeLens/refresh")
354
+
355
+ @server.send_notification(
356
+ "textDocument/publishDiagnostics",
357
+ {
358
+ uri: @uri,
359
+ version: version,
360
+ diagnostics: diagnostics[@uri] || [],
361
+ }
362
+ )
363
+ end
364
+ end
365
+
366
+ class Message
367
+ def initialize(server, json)
368
+ @server = server
369
+ @id = json[:id]
370
+ @method = json[:method]
371
+ @params = json[:params]
372
+ end
373
+
374
+ def run
375
+ p [:ignored, @method]
376
+ end
377
+
378
+ def respond(result)
379
+ raise "do not respond to notification" if @id == nil
380
+ @server.send_response(id: @id, result: result)
381
+ end
382
+
383
+ def respond_error(error)
384
+ raise "do not respond to notification" if @id == nil
385
+ @server.send_response(id: @id, error: error)
386
+ end
387
+
388
+ Classes = []
389
+ def self.inherited(klass)
390
+ Classes << klass
391
+ end
392
+
393
+ Table = Hash.new(Message)
394
+ def self.build_table
395
+ Classes.each {|klass| Table[klass::METHOD] = klass }
396
+ end
397
+
398
+ def self.find(method)
399
+ Table[method]
400
+ end
401
+ end
402
+
403
+ module ErrorCodes
404
+ ParseError = -32700
405
+ InvalidRequest = -32600
406
+ MethodNotFound = -32601
407
+ InvalidParams = -32602
408
+ InternalError = -32603
409
+ end
410
+
411
+ class Message::Initialize < Message
412
+ METHOD = "initialize"
413
+ def run
414
+ @server.root_uri = @params[:rootUri]
415
+ pwd = Dir.pwd
416
+ @params[:workspaceFolders]&.each do |folder|
417
+ folder => { uri:, }
418
+ if pwd == URI(uri).path
419
+ @server.root_uri = uri
420
+ end
421
+ end
422
+
423
+ respond(
424
+ capabilities: {
425
+ textDocumentSync: {
426
+ openClose: true,
427
+ change: 2, # Incremental
428
+ },
429
+ completionProvider: {
430
+ triggerCharacters: ["."],
431
+ },
432
+ signatureHelpProvider: {
433
+ triggerCharacters: ["(", ","],
434
+ },
435
+ #codeActionProvider: {
436
+ # codeActionKinds: ["quickfix", "refactor"],
437
+ # resolveProvider: false,
438
+ #},
439
+ codeLensProvider: {
440
+ resolveProvider: true,
441
+ },
442
+ executeCommandProvider: {
443
+ commands: [
444
+ "typeprof.createPrototypeRBS",
445
+ "typeprof.enableSignature",
446
+ "typeprof.disableSignature",
447
+ ],
448
+ },
449
+ definitionProvider: true,
450
+ typeDefinitionProvider: true,
451
+ referencesProvider: true,
452
+ },
453
+ serverInfo: {
454
+ name: "typeprof",
455
+ version: "0.0.0",
456
+ },
457
+ )
458
+
459
+ puts "TypeProf for IDE is started successfully"
460
+ end
461
+ end
462
+
463
+ class Message::Initialized < Message
464
+ METHOD = "initialized"
465
+ def run
466
+ end
467
+ end
468
+
469
+ class Message::Shutdown < Message
470
+ METHOD = "shutdown"
471
+ def run
472
+ respond(nil)
473
+ end
474
+ end
475
+
476
+ class Message::Exit < Message
477
+ METHOD = "exit"
478
+ def run
479
+ exit
480
+ end
481
+ end
482
+
483
+ module Message::Workspace
484
+ end
485
+
486
+ class Message::Workspace::DidChangeWatchedFiles < Message
487
+ METHOD = "workspace/didChangeWatchedFiles"
488
+ def run
489
+ #p "workspace/didChangeWatchedFiles"
490
+ #pp @params
491
+ end
492
+ end
493
+
494
+ class Message::Workspace::ExecuteCommand < Message
495
+ METHOD = "workspace/executeCommand"
496
+ def run
497
+ case @params[:command]
498
+ when "typeprof.enableSignature"
499
+ @server.signature_enabled = true
500
+ @server.send_request("workspace/codeLens/refresh")
501
+ when "typeprof.disableSignature"
502
+ @server.signature_enabled = false
503
+ @server.send_request("workspace/codeLens/refresh")
504
+ when "typeprof.createPrototypeRBS"
505
+ class_kind, class_name, sig_str = @params[:arguments]
506
+ code_range =
507
+ CodeRange.new(
508
+ CodeLocation.new(1, 0),
509
+ CodeLocation.new(1, 0),
510
+ )
511
+ text = []
512
+ text << "#{ class_kind } #{ class_name.join("::") }\n"
513
+ text << " #{ sig_str }\n"
514
+ text << "end\n\n"
515
+ text = text.join
516
+ @server.send_request(
517
+ "workspace/applyEdit",
518
+ edit: {
519
+ changes: {
520
+ @server.root_uri + "/typeprof.rbs" => [
521
+ {
522
+ range: code_range.to_lsp,
523
+ newText: text,
524
+ }
525
+ ],
526
+ },
527
+ },
528
+ ) do |res|
529
+ code_range =
530
+ CodeRange.new(
531
+ CodeLocation.new(1, 0),
532
+ CodeLocation.new(3, 3), # 3 = "end".size
533
+ )
534
+ @server.send_request(
535
+ "window/showDocument",
536
+ uri: @server.root_uri + "/typeprof.rbs",
537
+ takeFocus: true,
538
+ selection: code_range.to_lsp,
539
+ )
540
+ end
541
+ respond(nil)
542
+ else
543
+ respond_error(
544
+ code: ErrorCodes::InvalidRequest,
545
+ message: "Unknown command: #{ @params[:command] }",
546
+ )
547
+ end
548
+ end
549
+ end
550
+
551
+ module Message::TextDocument
552
+ end
553
+
554
+ class Message::TextDocument::DidOpen < Message
555
+ METHOD = "textDocument/didOpen"
556
+ def run
557
+ case @params
558
+ in { textDocument: { uri:, version:, text: } }
559
+ else
560
+ raise
561
+ end
562
+ if uri.start_with?(@server.root_uri)
563
+ @server.open_texts[uri] = Text.new(@server, uri, text, version)
564
+ end
565
+ end
566
+ end
567
+
568
+ class Message::TextDocument::DidChange < Message
569
+ METHOD = "textDocument/didChange"
570
+ def run
571
+ case @params
572
+ in { textDocument: { uri:, version: }, contentChanges: changes }
573
+ else
574
+ raise
575
+ end
576
+ @server.open_texts[uri]&.apply_changes(changes, version)
577
+ end
578
+
579
+ def cancel
580
+ puts "cancel"
581
+ end
582
+ end
583
+
584
+ class Message::TextDocument::DidClose < Message
585
+ METHOD = "textDocument/didClose"
586
+ def run
587
+ case @params
588
+ in { textDocument: { uri: } }
589
+ else
590
+ raise
591
+ end
592
+ @server.open_texts.delete(uri)
593
+ end
594
+ end
595
+
596
+ class Message::TextDocument::Definition < Message
597
+ METHOD = "textDocument/definition"
598
+ def run
599
+ case @params
600
+ in {
601
+ textDocument: { uri:, },
602
+ position: loc,
603
+ }
604
+ else
605
+ raise
606
+ end
607
+
608
+ definition_table = @server.open_texts[uri]&.definition_table
609
+ code_locations = definition_table[CodeLocation.from_lsp(loc)] if definition_table
610
+ if code_locations
611
+ respond(
612
+ code_locations.map do |path, code_range|
613
+ {
614
+ uri: "file://" + path,
615
+ range: code_range.to_lsp,
616
+ }
617
+ end
618
+ )
619
+ else
620
+ respond(nil)
621
+ end
622
+ end
623
+ end
624
+
625
+ class Message::TextDocument::TypeDefinition < Message
626
+ METHOD = "textDocument/typeDefinition"
627
+ def run
628
+ respond(nil)
629
+ # jump example
630
+ #respond(
631
+ # uri: "file:///path/to/typeprof/vscode/sandbox/test.rbs",
632
+ # range: {
633
+ # start: { line: 1, character: 4 },
634
+ # end: { line: 1, character: 7 },
635
+ # },
636
+ #)
637
+ end
638
+ end
639
+
640
+ class Message::TextDocument::References < Message
641
+ METHOD = "textDocument/references"
642
+ def run
643
+ case @params
644
+ in {
645
+ textDocument: { uri:, },
646
+ position: loc,
647
+ }
648
+ else
649
+ raise
650
+ end
651
+
652
+ caller_table = @server.open_texts[uri]&.caller_table
653
+ code_locations = caller_table[CodeLocation.from_lsp(loc)] if caller_table
654
+ if code_locations
655
+ respond(
656
+ code_locations.map do |path, code_range|
657
+ {
658
+ uri: "file://" + path,
659
+ range: code_range.to_lsp,
660
+ }
661
+ end
662
+ )
663
+ else
664
+ respond(nil)
665
+ end
666
+ end
667
+ end
668
+
669
+ module CompletionTriggerKind
670
+ INVOKED = 1
671
+ TRIGGER_CHARACTER = 2
672
+ TRIGGER_FOR_INCOMPLETE_COMPLETIONS = 3
673
+ end
674
+
675
+ class Message::TextDocument::Completion < Message
676
+ METHOD = "textDocument/completion"
677
+ def run
678
+ case @params
679
+ in {
680
+ textDocument: { uri:, },
681
+ position: loc,
682
+ context: {
683
+ triggerKind: trigger_kind
684
+ },
685
+ }
686
+ in {
687
+ textDocument: { uri:, },
688
+ position: loc,
689
+ }
690
+ trigger_kind = 1
691
+ else
692
+ raise
693
+ end
694
+
695
+ items = @server.open_texts[uri]&.code_complete(loc, trigger_kind)
696
+
697
+ if items
698
+ respond(
699
+ {
700
+ isIncomplete: true,
701
+ items: items
702
+ }
703
+ )
704
+ else
705
+ respond(nil)
706
+ end
707
+ end
708
+ end
709
+
710
+ class Message::TextDocument::SignatureHelp < Message
711
+ METHOD = "textDocument/signatureHelp"
712
+ def run
713
+ case @params
714
+ in {
715
+ textDocument: { uri:, },
716
+ position: loc,
717
+ context: {
718
+ triggerKind: trigger_kind
719
+ },
720
+ }
721
+ in {
722
+ textDocument: { uri:, },
723
+ position: loc,
724
+ }
725
+ trigger_kind = 1
726
+ else
727
+ raise
728
+ end
729
+
730
+ items = @server.open_texts[uri]&.signature_help(loc, trigger_kind)
731
+
732
+ if items
733
+ respond({
734
+ signatures: items
735
+ })
736
+ else
737
+ respond(nil)
738
+ end
739
+ end
740
+ end
741
+
742
+ class Message::TextDocument::CodeLens < Message
743
+ METHOD = "textDocument/codeLens"
744
+ def run
745
+ case @params
746
+ in { textDocument: { uri: } }
747
+ else
748
+ raise
749
+ end
750
+
751
+ text = @server.open_texts[uri]
752
+ if text && @server.signature_enabled
753
+ # enqueue in the analysis queue because codeLens is order sensitive
754
+ text.push_analysis_queue do
755
+ respond(text.sigs)
756
+ end
757
+ else
758
+ respond(nil)
759
+ end
760
+ end
761
+ end
762
+
763
+ class Message::CancelRequest < Message
764
+ METHOD = "$/cancelRequest"
765
+ def run
766
+ req = @server.running_requests_from_client[@params[:id]]
767
+ #p [:cancel, @params[:id]]
768
+ req.cancel if req.respond_to?(:cancel)
769
+ end
770
+ end
771
+
772
+ Message.build_table
773
+
774
+ class Reader
775
+ class ProtocolError < StandardError
776
+ end
777
+
778
+ def initialize(io)
779
+ @io = io
780
+ end
781
+
782
+ def read
783
+ while line = @io.gets
784
+ line2 = @io.gets
785
+ if line =~ /\AContent-length: (\d+)\r\n\z/i && line2 == "\r\n"
786
+ len = $1.to_i
787
+ json = JSON.parse(@io.read(len), symbolize_names: true)
788
+ yield json
789
+ else
790
+ raise ProtocolError, "LSP broken header"
791
+ end
792
+ end
793
+ end
794
+ end
795
+
796
+ class Writer
797
+ def initialize(io)
798
+ @io = io
799
+ end
800
+
801
+ def write(**json)
802
+ json = JSON.generate(json.merge(jsonrpc: "2.0"))
803
+ @io << "Content-Length: #{ json.bytesize }\r\n\r\n" << json
804
+ @io.flush
805
+ end
806
+
807
+ module ErrorCodes
808
+ ParseError = -32700
809
+ InvalidRequest = -32600
810
+ MethodNotFound = -32601
811
+ InvalidParams = -32602
812
+ InternalError = -32603
813
+ end
814
+ end
815
+
816
+ module Helpers
817
+ def pos(line, character)
818
+ { line: line, character: character }
819
+ end
820
+
821
+ def range(s, e)
822
+ { start: s, end: e }
823
+ end
824
+ end
825
+
826
+ class Server
827
+ class Exit < StandardError; end
828
+
829
+ include Helpers
830
+
831
+ def initialize(config, reader, writer)
832
+ @typeprof_config = config
833
+ @reader = reader
834
+ @writer = writer
835
+ @tx_mutex = Mutex.new
836
+ @request_id = 0
837
+ @running_requests_from_client = {}
838
+ @running_requests_from_server = {}
839
+ @open_texts = {}
840
+ @sigs = {} # tmp
841
+ @signature_enabled = true
842
+ end
843
+
844
+ attr_reader :typeprof_config, :open_texts, :sigs, :running_requests_from_client
845
+ attr_accessor :root_uri, :signature_enabled
846
+
847
+ def run
848
+ @reader.read do |json|
849
+ if json[:method]
850
+ # request or notification
851
+ msg = Message.find(json[:method]).new(self, json)
852
+ @running_requests_from_client[json[:id]] = msg if json[:id]
853
+ msg.run
854
+ else
855
+ callback = @running_requests_from_server.delete(json[:id])
856
+ callback&.call(json[:params])
857
+ end
858
+ end
859
+ rescue Exit
860
+ end
861
+
862
+ def send_response(**msg)
863
+ @running_requests_from_client.delete(msg[:id])
864
+ exclusive_write(**msg)
865
+ end
866
+
867
+ def send_notification(method, params = nil)
868
+ exclusive_write(method: method, params: params)
869
+ end
870
+
871
+ def send_request(method, **params, &blk)
872
+ id = @request_id += 1
873
+ @running_requests_from_server[id] = blk
874
+ exclusive_write(id: id, method: method, params: params)
875
+ end
876
+
877
+ def exclusive_write(**json)
878
+ @tx_mutex.synchronize do
879
+ @writer.write(**json)
880
+ end
881
+ end
882
+ end
883
+ end
884
+ end