typeprof 0.15.3 → 0.20.3

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 (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