kapusta 0.13.2 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +50 -11
- data/bin/check-all +6 -1
- data/bin/compile-examples +7 -2
- data/examples/anagram.kap +3 -3
- data/examples/app/args.kap +9 -0
- data/examples/arrange-coins.kap +3 -3
- data/examples/baseball-game.kap +5 -5
- data/examples/best-time-to-buy-sell-stock.kap +2 -2
- data/examples/binary-search.kap +2 -2
- data/examples/binary-to-decimal.kap +1 -1
- data/examples/blocks-and-kwargs.kap +2 -2
- data/examples/count-effects.kap +1 -1
- data/examples/count-items-matching-rule.kap +18 -0
- data/examples/doto-hygiene.kap +2 -2
- data/examples/doto.kap +2 -2
- data/examples/egg-count.kap +1 -1
- data/examples/exceptions.kap +1 -1
- data/examples/falling-drops.kap +7 -7
- data/examples/fennel-parity-examples.txt +3 -6
- data/examples/good-pairs.kap +18 -0
- data/examples/greet.kap +1 -1
- data/examples/happy-number.kap +2 -2
- data/examples/left-right-difference.kap +60 -0
- data/examples/length-of-last-word.kap +4 -4
- data/examples/manhattan-distance.kap +1 -1
- data/examples/maximum-subarray.kap +23 -7
- data/examples/minimum-start-value.kap +19 -0
- data/examples/move-zeroes.kap +2 -2
- data/examples/mruby-runtime-examples.txt +8 -2
- data/examples/non-constant-local.kap +1 -1
- data/examples/number-of-1-bits.kap +1 -1
- data/examples/number-of-steps.kap +1 -1
- data/examples/palindrome.kap +2 -2
- data/examples/pangram.kap +4 -4
- data/examples/pcall.kap +3 -3
- data/examples/pipeline.kap +4 -4
- data/examples/plus-one.kap +3 -3
- data/examples/raindrops.kap +1 -1
- data/examples/range-width.kap +14 -0
- data/examples/recent-counter.kap +6 -6
- data/examples/require-local-args.kap +9 -0
- data/examples/require-local.kap +7 -0
- data/examples/require-module-local.kap +4 -0
- data/examples/require-module.kap +4 -0
- data/examples/reverse-integer.kap +1 -1
- data/examples/roman-to-integer.kap +3 -3
- data/examples/running-sum.kap +20 -0
- data/examples/safe-lookup.kap +2 -2
- data/examples/single-number.kap +1 -1
- data/examples/stack.kap +14 -14
- data/examples/subtract-product-sum.kap +1 -1
- data/examples/summary-ranges.kap +45 -0
- data/examples/threading.kap +5 -5
- data/examples/tset.kap +1 -1
- data/examples/two-sum-hash.kap +3 -3
- data/examples/two-sum.kap +1 -1
- data/examples/ugly-number.kap +1 -1
- data/examples/underground-system.kap +39 -0
- data/examples/valid-parentheses-1.kap +6 -6
- data/exe/kapusta-ls +49 -2
- data/lib/kapusta/ast.rb +8 -0
- data/lib/kapusta/compiler/emitter/bindings.rb +111 -89
- data/lib/kapusta/compiler/emitter/collections.rb +32 -40
- data/lib/kapusta/compiler/emitter/control_flow.rb +33 -31
- data/lib/kapusta/compiler/emitter/expressions.rb +21 -5
- data/lib/kapusta/compiler/emitter/interop.rb +168 -48
- data/lib/kapusta/compiler/emitter/patterns.rb +12 -14
- data/lib/kapusta/compiler/emitter/support.rb +63 -81
- data/lib/kapusta/compiler/language.rb +522 -0
- data/lib/kapusta/compiler/lua_compat.rb +23 -28
- data/lib/kapusta/compiler/macro_expander.rb +30 -30
- data/lib/kapusta/compiler/macro_lowerer.rb +12 -24
- data/lib/kapusta/compiler/normalizer.rb +25 -17
- data/lib/kapusta/compiler.rb +3 -24
- data/lib/kapusta/env.rb +2 -2
- data/lib/kapusta/errors.rb +2 -1
- data/lib/kapusta/formatter/ast_helpers.rb +78 -0
- data/lib/kapusta/formatter/cli.rb +125 -0
- data/lib/kapusta/formatter/line_helpers.rb +44 -0
- data/lib/kapusta/formatter/validator.rb +32 -0
- data/lib/kapusta/formatter.rb +354 -325
- data/lib/kapusta/lsp/identifier.rb +1 -1
- data/lib/kapusta/lsp/rename.rb +21 -11
- data/lib/kapusta/lsp/scope_walker.rb +122 -212
- data/lib/kapusta/lsp/workspace_index.rb +17 -5
- data/lib/kapusta/reader.rb +4 -2
- data/lib/kapusta/version.rb +1 -1
- data/lib/kapusta.rb +39 -6
- data/spec/cli_spec.rb +13 -0
- data/spec/examples_errors_spec.rb +3 -1
- data/spec/examples_spec.rb +67 -15
- data/spec/formatter_spec.rb +246 -0
- data/spec/lsp_spec.rb +69 -0
- data/spec/require_spec.rb +294 -0
- metadata +20 -2
- data/examples/describe.kap +0 -9
|
@@ -65,7 +65,7 @@ module Kapusta
|
|
|
65
65
|
result = []
|
|
66
66
|
@entries.each do |uri, entry|
|
|
67
67
|
entry.walker.bindings.each do |b|
|
|
68
|
-
next unless
|
|
68
|
+
next unless Compiler::Language.header_scope?(b.kind)
|
|
69
69
|
|
|
70
70
|
segs = b.sym.dotted? ? b.sym.segments : [b.sym.name]
|
|
71
71
|
result << [uri, b] if segs == prefix
|
|
@@ -104,7 +104,7 @@ module Kapusta
|
|
|
104
104
|
def constant_definition_with_prefix?(prefix, except_prefix: nil)
|
|
105
105
|
@entries.any? do |_uri, entry|
|
|
106
106
|
entry.walker.bindings.any? do |b|
|
|
107
|
-
next false unless
|
|
107
|
+
next false unless Compiler::Language.header_scope?(b.kind)
|
|
108
108
|
next false if except_prefix && matches_prefix?(b.sym, except_prefix)
|
|
109
109
|
|
|
110
110
|
matches_prefix?(b.sym, prefix)
|
|
@@ -147,7 +147,7 @@ module Kapusta
|
|
|
147
147
|
@entries.each do |uri, entry|
|
|
148
148
|
occs = []
|
|
149
149
|
entry.walker.bindings.each do |b|
|
|
150
|
-
next unless
|
|
150
|
+
next unless Compiler::Language.header_scope?(b.kind)
|
|
151
151
|
|
|
152
152
|
occs << b if matches_prefix?(b.sym, prefix)
|
|
153
153
|
end
|
|
@@ -171,7 +171,13 @@ module Kapusta
|
|
|
171
171
|
end
|
|
172
172
|
|
|
173
173
|
def matches_prefix?(sym, prefix)
|
|
174
|
-
segs = sym.dotted?
|
|
174
|
+
segs = if sym.dotted?
|
|
175
|
+
sym.segments
|
|
176
|
+
elsif sym.colonized?
|
|
177
|
+
sym.colon_segments
|
|
178
|
+
else
|
|
179
|
+
[sym.name]
|
|
180
|
+
end
|
|
175
181
|
return false if segs.length < prefix.length
|
|
176
182
|
|
|
177
183
|
segs[0...prefix.length] == prefix
|
|
@@ -194,7 +200,13 @@ module Kapusta
|
|
|
194
200
|
end
|
|
195
201
|
|
|
196
202
|
def first_segment_capitalized?(sym)
|
|
197
|
-
first = sym.dotted?
|
|
203
|
+
first = if sym.dotted?
|
|
204
|
+
sym.segments.first
|
|
205
|
+
elsif sym.colonized?
|
|
206
|
+
sym.colon_segments.first
|
|
207
|
+
else
|
|
208
|
+
sym.name
|
|
209
|
+
end
|
|
198
210
|
first.match?(/\A[A-Z]/)
|
|
199
211
|
end
|
|
200
212
|
|
data/lib/kapusta/reader.rb
CHANGED
|
@@ -286,7 +286,7 @@ module Kapusta
|
|
|
286
286
|
break unless token.start_with?('.') && token.length > 1
|
|
287
287
|
|
|
288
288
|
token[1..].split('.').each do |name|
|
|
289
|
-
current = List.new([Sym.new('
|
|
289
|
+
current = List.new([Sym.new('.'), current, Kapusta.kebab_to_snake(name).to_sym])
|
|
290
290
|
end
|
|
291
291
|
end
|
|
292
292
|
|
|
@@ -345,7 +345,9 @@ module Kapusta
|
|
|
345
345
|
raise reader_error(:bad_shorthand, source_position) unless value.is_a?(Sym)
|
|
346
346
|
|
|
347
347
|
key = Kapusta.kebab_to_snake(value.name).to_sym
|
|
348
|
-
[key, value]
|
|
348
|
+
pair = [key, value]
|
|
349
|
+
pair.define_singleton_method(:shorthand?) { true }
|
|
350
|
+
pair
|
|
349
351
|
else
|
|
350
352
|
[item, value]
|
|
351
353
|
end
|
data/lib/kapusta/version.rb
CHANGED
data/lib/kapusta.rb
CHANGED
|
@@ -11,6 +11,8 @@ require_relative 'kapusta/compiler'
|
|
|
11
11
|
|
|
12
12
|
module Kapusta
|
|
13
13
|
@loaded_kapusta_features = {}
|
|
14
|
+
LOADING_KAPUSTA_FEATURE = Object.new
|
|
15
|
+
private_constant :LOADING_KAPUSTA_FEATURE
|
|
14
16
|
|
|
15
17
|
def self.eval(source, path: '(eval)', **_opts)
|
|
16
18
|
install!
|
|
@@ -29,12 +31,15 @@ module Kapusta
|
|
|
29
31
|
|
|
30
32
|
def self.require(feature, relative_to: nil)
|
|
31
33
|
install!
|
|
32
|
-
feature = feature
|
|
34
|
+
feature = require_feature_name(feature)
|
|
33
35
|
local_path = resolve_require_path(feature, relative_to:)
|
|
34
36
|
|
|
35
37
|
return require_kapusta_file(local_path) if local_path&.end_with?('.kap')
|
|
36
38
|
return Kernel.require(local_path) if local_path
|
|
37
39
|
|
|
40
|
+
kap_path = resolve_load_path_kapusta_feature(feature, relative_to:)
|
|
41
|
+
return require_kapusta_file(kap_path) if kap_path
|
|
42
|
+
|
|
38
43
|
Kernel.require(feature)
|
|
39
44
|
end
|
|
40
45
|
|
|
@@ -99,19 +104,47 @@ module Kapusta
|
|
|
99
104
|
candidates.find { |candidate| File.file?(candidate) }
|
|
100
105
|
end
|
|
101
106
|
|
|
107
|
+
def self.require_feature_name(feature)
|
|
108
|
+
feature.is_a?(Symbol) ? feature.to_s.tr('.', '/') : feature.to_s
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def self.resolve_load_path_kapusta_feature(feature, relative_to:)
|
|
112
|
+
return if local_feature?(feature)
|
|
113
|
+
|
|
114
|
+
load_paths = [require_base_dir(relative_to), *$LOAD_PATH].uniq
|
|
115
|
+
load_paths.each do |load_path|
|
|
116
|
+
candidate = existing_kapusta_feature_path(File.expand_path(feature, load_path))
|
|
117
|
+
return candidate if candidate
|
|
118
|
+
end
|
|
119
|
+
nil
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def self.existing_kapusta_feature_path(path)
|
|
123
|
+
candidates = File.extname(path).empty? ? ["#{path}.kap"] : [path]
|
|
124
|
+
candidates.find { |candidate| File.file?(candidate) && candidate.end_with?('.kap') }
|
|
125
|
+
end
|
|
126
|
+
|
|
102
127
|
def self.require_kapusta_file(path)
|
|
103
128
|
expanded = File.realpath(path)
|
|
104
|
-
|
|
129
|
+
if @loaded_kapusta_features.key?(expanded)
|
|
130
|
+
cached = @loaded_kapusta_features[expanded]
|
|
131
|
+
return false if cached.equal?(LOADING_KAPUSTA_FEATURE)
|
|
132
|
+
|
|
133
|
+
return cached
|
|
134
|
+
end
|
|
105
135
|
|
|
106
|
-
@loaded_kapusta_features[expanded] =
|
|
107
|
-
dofile(expanded)
|
|
136
|
+
@loaded_kapusta_features[expanded] = LOADING_KAPUSTA_FEATURE
|
|
137
|
+
value = dofile(expanded)
|
|
138
|
+
@loaded_kapusta_features[expanded] = value
|
|
108
139
|
$LOADED_FEATURES << expanded unless $LOADED_FEATURES.include?(expanded)
|
|
109
|
-
|
|
140
|
+
value
|
|
110
141
|
rescue StandardError, ScriptError
|
|
111
142
|
@loaded_kapusta_features.delete(expanded) if expanded
|
|
112
143
|
raise
|
|
113
144
|
end
|
|
114
145
|
|
|
115
146
|
private_class_method :resolve_require_path, :local_feature?, :require_base_dir,
|
|
116
|
-
:existing_feature_path, :
|
|
147
|
+
:existing_feature_path, :require_feature_name,
|
|
148
|
+
:resolve_load_path_kapusta_feature, :existing_kapusta_feature_path,
|
|
149
|
+
:require_kapusta_file, :resolve_kap_relative
|
|
117
150
|
end
|
data/spec/cli_spec.rb
CHANGED
|
@@ -138,4 +138,17 @@ RSpec.describe Kapusta::CLI do
|
|
|
138
138
|
expect(status.success?).to eq(true), stderr
|
|
139
139
|
expect(stdout).to eq("kapusta #{Kapusta::VERSION}\n")
|
|
140
140
|
end
|
|
141
|
+
|
|
142
|
+
it 'reports kapusta-ls lint diagnostics for an unknown class body form' do
|
|
143
|
+
Dir.mktmpdir do |dir|
|
|
144
|
+
path = File.join(dir, 'broken.kap')
|
|
145
|
+
File.write(path, "(class BankAccount)\n\n(n initialize [owner balance]\n owner)\n\n(end)\n")
|
|
146
|
+
|
|
147
|
+
stdout, stderr, status = Open3.capture3(RbConfig.ruby, File.expand_path('../exe/kapusta-ls', __dir__),
|
|
148
|
+
'--lint', path)
|
|
149
|
+
|
|
150
|
+
expect(status.success?).to eq(false), stdout
|
|
151
|
+
expect(stderr).to include('class body form must be a declaration or known special form: n')
|
|
152
|
+
end
|
|
153
|
+
end
|
|
141
154
|
end
|
|
@@ -34,7 +34,9 @@ RSpec.describe 'examples-errors' do
|
|
|
34
34
|
|
|
35
35
|
it 'bad-multisym.kap' do
|
|
36
36
|
expect(run_error_example('bad-multisym.kap'))
|
|
37
|
-
.to eq(
|
|
37
|
+
.to eq('bad-multisym.kap:1:8: bad multisym: unbound.foo; unresolved root unbound; ' \
|
|
38
|
+
'bind unbound first, use a capitalized constant path, ' \
|
|
39
|
+
"or call a method on an explicit receiver\n")
|
|
38
40
|
end
|
|
39
41
|
|
|
40
42
|
it 'bad-set-target.kap' do
|
data/spec/examples_spec.rb
CHANGED
|
@@ -226,6 +226,10 @@ RSpec.describe 'examples' do
|
|
|
226
226
|
expect(run_example('count-effects.kap')).to eq("1\n2\n")
|
|
227
227
|
end
|
|
228
228
|
|
|
229
|
+
it 'count-items-matching-rule.kap' do
|
|
230
|
+
expect(run_example('count-items-matching-rule.kap')).to eq("2\n1\n1\n")
|
|
231
|
+
end
|
|
232
|
+
|
|
229
233
|
it 'max-achievable.kap' do
|
|
230
234
|
expect(run_example('max-achievable.kap')).to eq("6\n7\n10\n")
|
|
231
235
|
end
|
|
@@ -250,21 +254,6 @@ RSpec.describe 'examples' do
|
|
|
250
254
|
OUT
|
|
251
255
|
end
|
|
252
256
|
|
|
253
|
-
it 'describe.kap' do
|
|
254
|
-
expect(run_example('describe.kap')).to eq(<<~OUT)
|
|
255
|
-
-3
|
|
256
|
-
"negative"
|
|
257
|
-
0
|
|
258
|
-
"zero"
|
|
259
|
-
1
|
|
260
|
-
"one"
|
|
261
|
-
2
|
|
262
|
-
"many"
|
|
263
|
-
99
|
|
264
|
-
"many"
|
|
265
|
-
OUT
|
|
266
|
-
end
|
|
267
|
-
|
|
268
257
|
it 'destructure.kap' do
|
|
269
258
|
expect(run_example('destructure.kap')).to eq(<<~OUT)
|
|
270
259
|
6
|
|
@@ -352,6 +341,10 @@ RSpec.describe 'examples' do
|
|
|
352
341
|
expect(run_example('gcd.kap')).to eq("12\n6\n")
|
|
353
342
|
end
|
|
354
343
|
|
|
344
|
+
it 'good-pairs.kap' do
|
|
345
|
+
expect(run_example('good-pairs.kap')).to eq("4\n6\n0\n")
|
|
346
|
+
end
|
|
347
|
+
|
|
355
348
|
it 'greet.kap' do
|
|
356
349
|
expect(run_example('greet.kap', argv: ['Ada'])).to eq(<<~OUT)
|
|
357
350
|
"Hello, Ada!"
|
|
@@ -379,6 +372,10 @@ RSpec.describe 'examples' do
|
|
|
379
372
|
expect(run_example('leap-year.kap')).to eq("true\n")
|
|
380
373
|
end
|
|
381
374
|
|
|
375
|
+
it 'left-right-difference.kap' do
|
|
376
|
+
expect(run_example('left-right-difference.kap')).to eq("true\n")
|
|
377
|
+
end
|
|
378
|
+
|
|
382
379
|
it 'length-of-last-word.kap' do
|
|
383
380
|
expect(run_example('length-of-last-word.kap')).to eq("5\n4\n6\n")
|
|
384
381
|
end
|
|
@@ -390,6 +387,17 @@ RSpec.describe 'examples' do
|
|
|
390
387
|
OUT
|
|
391
388
|
end
|
|
392
389
|
|
|
390
|
+
it 'minimum-start-value.kap' do
|
|
391
|
+
expect(run_example('minimum-start-value.kap')).to eq(<<~OUT)
|
|
392
|
+
5
|
|
393
|
+
2
|
|
394
|
+
1
|
|
395
|
+
3
|
|
396
|
+
5
|
|
397
|
+
-4
|
|
398
|
+
OUT
|
|
399
|
+
end
|
|
400
|
+
|
|
393
401
|
it 'module-header.kap' do
|
|
394
402
|
expect(run_example('module-header.kap')).to eq(<<~OUT)
|
|
395
403
|
"Hello, Ada!"
|
|
@@ -426,6 +434,14 @@ RSpec.describe 'examples' do
|
|
|
426
434
|
OUT
|
|
427
435
|
end
|
|
428
436
|
|
|
437
|
+
it 'range-width.kap' do
|
|
438
|
+
expect(run_example('range-width.kap')).to eq(<<~OUT)
|
|
439
|
+
8
|
|
440
|
+
0
|
|
441
|
+
11
|
|
442
|
+
OUT
|
|
443
|
+
end
|
|
444
|
+
|
|
429
445
|
it 'record.kap' do
|
|
430
446
|
expect(run_example('record.kap')).to eq(<<~OUT)
|
|
431
447
|
"Ada / engineer / ruby, lisp"
|
|
@@ -449,6 +465,10 @@ RSpec.describe 'examples' do
|
|
|
449
465
|
OUT
|
|
450
466
|
end
|
|
451
467
|
|
|
468
|
+
it 'running-sum.kap' do
|
|
469
|
+
expect(run_example('running-sum.kap')).to eq("true\ntrue\ntrue\n")
|
|
470
|
+
end
|
|
471
|
+
|
|
452
472
|
it 'kwargs.kap' do
|
|
453
473
|
expect(run_example('kwargs.kap')).to eq(<<~OUT)
|
|
454
474
|
"Ada has 3 tasks"
|
|
@@ -558,6 +578,14 @@ RSpec.describe 'examples' do
|
|
|
558
578
|
expect(run_example('sum.kap')).to eq("100\n")
|
|
559
579
|
end
|
|
560
580
|
|
|
581
|
+
it 'summary-ranges.kap' do
|
|
582
|
+
expect(run_example('summary-ranges.kap')).to eq(<<~OUT)
|
|
583
|
+
"0->2|4->5|7"
|
|
584
|
+
"0|2->4|6|8->9"
|
|
585
|
+
"empty="
|
|
586
|
+
OUT
|
|
587
|
+
end
|
|
588
|
+
|
|
561
589
|
it 'tset.kap' do
|
|
562
590
|
person = { name: 'Ada', city: 'Amsterdam' }
|
|
563
591
|
expect(run_example('tset.kap')).to eq("#{person.inspect}\n#{'Amsterdam'.inspect}\n")
|
|
@@ -571,6 +599,10 @@ RSpec.describe 'examples' do
|
|
|
571
599
|
expect(run_example('two-sum-hash.kap')).to eq("[0, 1]\n[1, 2]\nnil\n")
|
|
572
600
|
end
|
|
573
601
|
|
|
602
|
+
it 'underground-system.kap' do
|
|
603
|
+
expect(run_example('underground-system.kap')).to eq("11\n14\n")
|
|
604
|
+
end
|
|
605
|
+
|
|
574
606
|
it 'bst-iterator.kap' do
|
|
575
607
|
expect(run_example('bst-iterator.kap')).to eq(<<~OUT)
|
|
576
608
|
3
|
|
@@ -715,6 +747,26 @@ RSpec.describe 'examples' do
|
|
|
715
747
|
expect(run_example('macros-import-whole.kap')).to eq("7\n")
|
|
716
748
|
end
|
|
717
749
|
|
|
750
|
+
it 'require-local.kap' do
|
|
751
|
+
expect(run_example('require-local.kap')).to eq(<<~OUT)
|
|
752
|
+
"serve"
|
|
753
|
+
"--port 3000"
|
|
754
|
+
"usage: kapusta <command> [options]"
|
|
755
|
+
OUT
|
|
756
|
+
end
|
|
757
|
+
|
|
758
|
+
it 'require-module.kap' do
|
|
759
|
+
expect(run_example('require-module.kap')).to eq(<<~OUT)
|
|
760
|
+
"deploy -> production"
|
|
761
|
+
OUT
|
|
762
|
+
end
|
|
763
|
+
|
|
764
|
+
it 'require-module-local.kap' do
|
|
765
|
+
expect(run_example('require-module-local.kap')).to eq(<<~OUT)
|
|
766
|
+
"deploy -> production"
|
|
767
|
+
OUT
|
|
768
|
+
end
|
|
769
|
+
|
|
718
770
|
it 'parking-system.kap' do
|
|
719
771
|
expect(run_example('parking-system.kap')).to eq("true\ntrue\nfalse\nfalse\n")
|
|
720
772
|
end
|
data/spec/formatter_spec.rb
CHANGED
|
@@ -98,6 +98,29 @@ RSpec.describe Kapusta::Formatter do
|
|
|
98
98
|
end
|
|
99
99
|
end
|
|
100
100
|
|
|
101
|
+
it 'checks kapm macro modules with quasiquoted function templates' do
|
|
102
|
+
Dir.mktmpdir do |dir|
|
|
103
|
+
path = File.join(dir, 'sample.kapm')
|
|
104
|
+
File.write(path, <<~KAP)
|
|
105
|
+
(fn defcommand [name args runtime-args body1 & body]
|
|
106
|
+
`(fn ,name
|
|
107
|
+
,args
|
|
108
|
+
|
|
109
|
+
(fn ,runtime-args
|
|
110
|
+
,body1
|
|
111
|
+
,(unpack body))))
|
|
112
|
+
|
|
113
|
+
{: defcommand}
|
|
114
|
+
KAP
|
|
115
|
+
|
|
116
|
+
error_output = capture_stderr do
|
|
117
|
+
expect(described_class.new(['--check', path]).run).to eq(0)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
expect(error_output).to eq('')
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
101
124
|
it 'reads stdin when the input path is -' do
|
|
102
125
|
output = with_stdin("(let [name (or (. ARGV 0) \"world\")](puts (.. \"Hello, \" name \"!\")))\n") do
|
|
103
126
|
capture_stdout do
|
|
@@ -271,6 +294,229 @@ RSpec.describe Kapusta::Formatter do
|
|
|
271
294
|
end
|
|
272
295
|
end
|
|
273
296
|
|
|
297
|
+
it 'keeps short vector call arguments on the call line' do
|
|
298
|
+
Dir.mktmpdir do |dir|
|
|
299
|
+
path = File.join(dir, 'sample.kap')
|
|
300
|
+
File.write(path, <<~KAP)
|
|
301
|
+
(fn range-label [lo hi]
|
|
302
|
+
(join ""
|
|
303
|
+
[(.. lo) "->" (.. hi)]))
|
|
304
|
+
KAP
|
|
305
|
+
|
|
306
|
+
output = capture_stdout do
|
|
307
|
+
expect(described_class.new([path]).run).to eq(0)
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
expect(output).to eq(<<~KAP)
|
|
311
|
+
(fn range-label [lo hi]
|
|
312
|
+
(join "" [(.. lo) "->" (.. hi)]))
|
|
313
|
+
KAP
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
it 'keeps short local hash values on the local line' do
|
|
318
|
+
Dir.mktmpdir do |dir|
|
|
319
|
+
path = File.join(dir, 'sample.kap')
|
|
320
|
+
File.write(path, <<~KAP)
|
|
321
|
+
(local rule-keys
|
|
322
|
+
{"type" :type
|
|
323
|
+
"color" :color
|
|
324
|
+
"name" :name})
|
|
325
|
+
KAP
|
|
326
|
+
|
|
327
|
+
output = capture_stdout do
|
|
328
|
+
expect(described_class.new([path]).run).to eq(0)
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
expect(output).to eq(<<~KAP)
|
|
332
|
+
(local rule-keys {"type" :type "color" :color "name" :name})
|
|
333
|
+
KAP
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
it 'hangs hash-first call arguments under the first argument' do
|
|
338
|
+
Dir.mktmpdir do |dir|
|
|
339
|
+
path = File.join(dir, 'sample.kap')
|
|
340
|
+
File.write(path, <<~KAP)
|
|
341
|
+
(local check (fn [expected actual] (= expected actual)))
|
|
342
|
+
(local scroll-state (fn [lines visible offset] [lines visible offset]))
|
|
343
|
+
|
|
344
|
+
(check {:preview-offset 0 :preview-total 3 :preview-visible 5}
|
|
345
|
+
(scroll-state ["a" "b" "c"] 5 10))
|
|
346
|
+
KAP
|
|
347
|
+
|
|
348
|
+
output = capture_stdout do
|
|
349
|
+
expect(described_class.new([path]).run).to eq(0)
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
expect(output).to eq(<<~KAP)
|
|
353
|
+
(local check (fn [expected actual] (= expected actual)))
|
|
354
|
+
(local scroll-state (fn [lines visible offset] [lines visible offset]))
|
|
355
|
+
|
|
356
|
+
(check {:preview-offset 0 :preview-total 3 :preview-visible 5}
|
|
357
|
+
(scroll-state ["a" "b" "c"] 5 10))
|
|
358
|
+
KAP
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
it 'packs overflowing call arguments before hanging the rest' do
|
|
363
|
+
Dir.mktmpdir do |dir|
|
|
364
|
+
path = File.join(dir, 'sample.kap')
|
|
365
|
+
File.write(path, <<~KAP)
|
|
366
|
+
(local preview {})
|
|
367
|
+
(local state {})
|
|
368
|
+
|
|
369
|
+
(preview.apply-horizontal-scroll-limit state ["abcdefghijklmnopqrstuvwxyz"] 20 true)
|
|
370
|
+
KAP
|
|
371
|
+
|
|
372
|
+
output = capture_stdout do
|
|
373
|
+
expect(described_class.new([path]).run).to eq(0)
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
expect(output).to eq(<<~KAP)
|
|
377
|
+
(local preview {})
|
|
378
|
+
(local state {})
|
|
379
|
+
|
|
380
|
+
(preview.apply-horizontal-scroll-limit state ["abcdefghijklmnopqrstuvwxyz"] 20
|
|
381
|
+
true)
|
|
382
|
+
KAP
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
it 'wraps long call values in let bindings with hanging arguments' do
|
|
387
|
+
Dir.mktmpdir do |dir|
|
|
388
|
+
path = File.join(dir, 'sample.kap')
|
|
389
|
+
File.write(path, <<~KAP)
|
|
390
|
+
(local entries [])
|
|
391
|
+
(local update {})
|
|
392
|
+
|
|
393
|
+
(let [state (update.init "HEAD" entries {:version 1 :reviews {}} "scope-long" "src-long-name")]
|
|
394
|
+
state)
|
|
395
|
+
KAP
|
|
396
|
+
|
|
397
|
+
output = capture_stdout do
|
|
398
|
+
expect(described_class.new([path]).run).to eq(0)
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
expect(output).to eq(<<~KAP)
|
|
402
|
+
(local entries [])
|
|
403
|
+
(local update {})
|
|
404
|
+
|
|
405
|
+
(let [state (update.init "HEAD" entries {:version 1 :reviews {}} "scope-long"
|
|
406
|
+
"src-long-name")]
|
|
407
|
+
state)
|
|
408
|
+
KAP
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
it 'keeps multiline hash call arguments hanging in let bindings' do
|
|
413
|
+
Dir.mktmpdir do |dir|
|
|
414
|
+
path = File.join(dir, 'sample.kap')
|
|
415
|
+
File.write(path, <<~KAP)
|
|
416
|
+
(local preview-key {})
|
|
417
|
+
|
|
418
|
+
(let [old-key (preview-key.for-entry "HEAD" {:status "M" :kind "M" :path "old.rb" :reviewed false})]
|
|
419
|
+
old-key)
|
|
420
|
+
KAP
|
|
421
|
+
|
|
422
|
+
output = capture_stdout do
|
|
423
|
+
expect(described_class.new([path]).run).to eq(0)
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
expect(output).to eq(<<~KAP)
|
|
427
|
+
(local preview-key {})
|
|
428
|
+
|
|
429
|
+
(let [old-key (preview-key.for-entry "HEAD"
|
|
430
|
+
{:status "M"
|
|
431
|
+
:kind "M"
|
|
432
|
+
:path "old.rb"
|
|
433
|
+
:reviewed false})]
|
|
434
|
+
old-key)
|
|
435
|
+
KAP
|
|
436
|
+
end
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
it 'separates compound items in multiline vectors' do
|
|
440
|
+
Dir.mktmpdir do |dir|
|
|
441
|
+
path = File.join(dir, 'sample.kap')
|
|
442
|
+
File.write(path, <<~KAP)
|
|
443
|
+
(local tree {})
|
|
444
|
+
|
|
445
|
+
(fn entry [status path]
|
|
446
|
+
[status path])
|
|
447
|
+
|
|
448
|
+
(fn check [expected actual]
|
|
449
|
+
[expected actual])
|
|
450
|
+
|
|
451
|
+
(fn test-tree []
|
|
452
|
+
(let [rows (tree.rows [(entry "A" "script/shorthand_branch.sh") (entry "M"
|
|
453
|
+
"spec/lib/epoxy/version_branch_validation_spec.rb")
|
|
454
|
+
(entry "M" "spec/lib/direct_spec.rb") (entry "M"
|
|
455
|
+
"spec/lib/tasks/helpers/commit_validator_spec.rb")])]
|
|
456
|
+
(check [{:type :folder :depth 0 :name "script/" :path "script"}
|
|
457
|
+
{:type :file :depth 1 :name "shorthand_branch.sh" :entry-index 1}
|
|
458
|
+
{:type :folder :depth 1 :name "epoxy/" :path "spec/lib/epoxy"} {:type :file
|
|
459
|
+
:depth 2
|
|
460
|
+
:name "version_branch_validation_spec.rb"
|
|
461
|
+
:entry-index 2}]
|
|
462
|
+
rows)))
|
|
463
|
+
KAP
|
|
464
|
+
|
|
465
|
+
output = capture_stdout do
|
|
466
|
+
expect(described_class.new([path]).run).to eq(0)
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
expect(output).to eq(<<~KAP)
|
|
470
|
+
(local tree {})
|
|
471
|
+
|
|
472
|
+
(fn entry [status path]
|
|
473
|
+
[status path])
|
|
474
|
+
|
|
475
|
+
(fn check [expected actual]
|
|
476
|
+
[expected actual])
|
|
477
|
+
|
|
478
|
+
(fn test-tree []
|
|
479
|
+
(let [rows (tree.rows [(entry "A" "script/shorthand_branch.sh")
|
|
480
|
+
(entry "M"
|
|
481
|
+
"spec/lib/epoxy/version_branch_validation_spec.rb")
|
|
482
|
+
(entry "M" "spec/lib/direct_spec.rb")
|
|
483
|
+
(entry "M"
|
|
484
|
+
"spec/lib/tasks/helpers/commit_validator_spec.rb")])]
|
|
485
|
+
(check [{:type :folder :depth 0 :name "script/" :path "script"}
|
|
486
|
+
{:type :file :depth 1 :name "shorthand_branch.sh" :entry-index 1}
|
|
487
|
+
{:type :folder :depth 1 :name "epoxy/" :path "spec/lib/epoxy"}
|
|
488
|
+
{:type :file
|
|
489
|
+
:depth 2
|
|
490
|
+
:name "version_branch_validation_spec.rb"
|
|
491
|
+
:entry-index 2}]
|
|
492
|
+
rows)))
|
|
493
|
+
KAP
|
|
494
|
+
end
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
it 'hangs function values in set forms' do
|
|
498
|
+
Dir.mktmpdir do |dir|
|
|
499
|
+
path = File.join(dir, 'sample.kap')
|
|
500
|
+
File.write(path, <<~KAP)
|
|
501
|
+
(local loader {})
|
|
502
|
+
(set loader.preview-output
|
|
503
|
+
(fn [...]
|
|
504
|
+
(error "scroll should not load preview lines")))
|
|
505
|
+
KAP
|
|
506
|
+
|
|
507
|
+
output = capture_stdout do
|
|
508
|
+
expect(described_class.new([path]).run).to eq(0)
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
expect(output).to eq(<<~KAP)
|
|
512
|
+
(local loader {})
|
|
513
|
+
(set loader.preview-output
|
|
514
|
+
(fn [...]
|
|
515
|
+
(error "scroll should not load preview lines")))
|
|
516
|
+
KAP
|
|
517
|
+
end
|
|
518
|
+
end
|
|
519
|
+
|
|
274
520
|
it 'preserves nil-valued let bindings before function bindings' do
|
|
275
521
|
Dir.mktmpdir do |dir|
|
|
276
522
|
path = File.join(dir, 'sample.kap')
|
data/spec/lsp_spec.rb
CHANGED
|
@@ -111,6 +111,16 @@ RSpec.describe Kapusta::LSP do
|
|
|
111
111
|
expect(responses.last.dig('params', 'diagnostics')).to be_empty
|
|
112
112
|
end
|
|
113
113
|
|
|
114
|
+
it 'publishes diagnostics for an unknown class body form' do
|
|
115
|
+
responses = run(
|
|
116
|
+
frame_initialize,
|
|
117
|
+
frame_did_open('file:///x.kap', "(class BankAccount)\n\n(n initialize [owner balance]\n owner)\n\n(end)\n")
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
expect(responses.last.dig('params', 'diagnostics', 0, 'message'))
|
|
121
|
+
.to eq('class body form must be a declaration or known special form: n')
|
|
122
|
+
end
|
|
123
|
+
|
|
114
124
|
it 'returns a TextEdit for formatting' do
|
|
115
125
|
responses = run(
|
|
116
126
|
frame_initialize,
|
|
@@ -123,6 +133,18 @@ RSpec.describe Kapusta::LSP do
|
|
|
123
133
|
expect(edits.first).to include('range', 'newText')
|
|
124
134
|
end
|
|
125
135
|
|
|
136
|
+
it 'formats through the LSP without collapsing full hash pairs into shorthand' do
|
|
137
|
+
text = "(let [active [] id 7]\n{:active active : id})\n"
|
|
138
|
+
responses = run(
|
|
139
|
+
frame_initialize,
|
|
140
|
+
frame_did_open('file:///x.kap', text),
|
|
141
|
+
frame_formatting(uri: 'file:///x.kap')
|
|
142
|
+
)
|
|
143
|
+
edits = result_for(responses)['result']
|
|
144
|
+
|
|
145
|
+
expect(edits.first['newText']).to include('{:active active : id}')
|
|
146
|
+
end
|
|
147
|
+
|
|
126
148
|
it 'rejects requests sent before initialize' do
|
|
127
149
|
responses = run(
|
|
128
150
|
frame(jsonrpc: '2.0', id: 1, method: 'textDocument/formatting',
|
|
@@ -145,6 +167,35 @@ RSpec.describe Kapusta::LSP do
|
|
|
145
167
|
expect(changes.first['edits'].map { |e| e['newText'] }).to eq(%w[y y y])
|
|
146
168
|
end
|
|
147
169
|
|
|
170
|
+
it 'renames only the base binding in a colon hash lookup shorthand' do
|
|
171
|
+
text = "(let [user {:name \"Ada\"}]\n (print user:name))\n"
|
|
172
|
+
responses = run(
|
|
173
|
+
frame_initialize,
|
|
174
|
+
frame_did_open('file:///x.kap', text),
|
|
175
|
+
frame_rename(uri: 'file:///x.kap', **cursor_at(text, 'user:name'), new_name: 'person')
|
|
176
|
+
)
|
|
177
|
+
edits = result_for(responses)['result']['documentChanges'].first['edits']
|
|
178
|
+
|
|
179
|
+
expect(edits.map { |e| e['newText'] }).to eq(%w[person person])
|
|
180
|
+
expect(edits.map { |e| [e['range']['start']['line'], e['range']['start']['character']] })
|
|
181
|
+
.to contain_exactly([0, 6], [1, 9])
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
it 'does not offer rename on a colon hash key segment' do
|
|
185
|
+
text = "(let [user {:name \"Ada\"}]\n (print user:name))\n"
|
|
186
|
+
key_index = text.index('user:name') + 'user:'.length
|
|
187
|
+
prefix = text[0...key_index]
|
|
188
|
+
last_nl = prefix.rindex("\n")
|
|
189
|
+
position = { line: prefix.count("\n"), character: last_nl ? key_index - last_nl - 1 : key_index }
|
|
190
|
+
responses = run(
|
|
191
|
+
frame_initialize,
|
|
192
|
+
frame_did_open('file:///x.kap', text),
|
|
193
|
+
frame_prepare_rename(uri: 'file:///x.kap', **position)
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
expect(result_for(responses)['result']).to be_nil
|
|
197
|
+
end
|
|
198
|
+
|
|
148
199
|
it 'renames a let binding referenced inside an accumulate iterator with multiple binders' do
|
|
149
200
|
text = "(let [xs [1 2 3]\n total (accumulate [s 0 _ x (ipairs xs)] (+ s x))]\n (print total))\n"
|
|
150
201
|
idx = text.index('(ipairs xs)') + 'ipairs '.length + 1
|
|
@@ -359,6 +410,24 @@ RSpec.describe Kapusta::LSP do
|
|
|
359
410
|
)
|
|
360
411
|
end
|
|
361
412
|
|
|
413
|
+
it 'jumps from a colon hash lookup base to its local binding' do
|
|
414
|
+
text = "(let [user {:name \"Ada\"}]\n user:name)\n"
|
|
415
|
+
responses = run(
|
|
416
|
+
frame_initialize,
|
|
417
|
+
frame_did_open('file:///x.kap', text),
|
|
418
|
+
frame_definition(uri: 'file:///x.kap', **cursor_at(text, 'user:name'))
|
|
419
|
+
)
|
|
420
|
+
result = result_for(responses)['result']
|
|
421
|
+
|
|
422
|
+
expect(result).to eq(
|
|
423
|
+
'uri' => 'file:///x.kap',
|
|
424
|
+
'range' => {
|
|
425
|
+
'start' => { 'line' => 0, 'character' => 6 },
|
|
426
|
+
'end' => { 'line' => 0, 'character' => 10 }
|
|
427
|
+
}
|
|
428
|
+
)
|
|
429
|
+
end
|
|
430
|
+
|
|
362
431
|
it 'rejects renaming a class to a lowercase name with a clear message' do
|
|
363
432
|
text = "(class Accumulator)\n\n(end)\n"
|
|
364
433
|
responses = run(
|