1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
|
// focused type, inference, and block-documentation hover regressions.
// usage: node tests/drivers/hover_features_test.js path/to/zclsp
const assert = require('assert');
const cp = require('child_process');
const server = process.argv[2];
const uri = 'file:///hover-features.zc';
const frame = m => {
const body = Buffer.from(JSON.stringify(m));
return Buffer.concat([Buffer.from('Content-Length: ' + body.length + '\r\n\r\n'), body]);
};
function hover(text, needle, nth = 0) {
let offset = -1;
for (let i = 0; i <= nth; i++) offset = text.indexOf(needle, offset + 1);
assert.ok(offset >= 0, 'fixture contains ' + needle);
const before = text.slice(0, offset);
const line = before.split('\n').length - 1;
const character = before.length - before.lastIndexOf('\n') - 1;
const messages = [
{ jsonrpc: '2.0', id: 1, method: 'initialize', params: {} },
{ jsonrpc: '2.0', method: 'textDocument/didOpen', params: { textDocument: { uri, version: 1, languageId: 'zc', text } } },
{ jsonrpc: '2.0', id: 2, method: 'textDocument/hover', params: { textDocument: { uri }, position: { line, character } } },
{ jsonrpc: '2.0', method: 'exit' },
];
const result = cp.spawnSync(server, process.argv.slice(3), { input: Buffer.concat(messages.map(frame)), timeout: 10000, maxBuffer: 4 << 20 });
assert.strictEqual(result.status, 0, String(result.stderr));
let data = result.stdout;
while (data.length) {
const end = data.indexOf('\r\n\r\n');
const count = Number(/Content-Length:\s*(\d+)/i.exec(data.subarray(0, end).toString())[1]);
const msg = JSON.parse(data.subarray(end + 4, end + 4 + count).toString());
data = data.subarray(end + 4 + count);
if (msg.id === 2) return msg.result && msg.result.contents.value;
}
return null;
}
for (const type of ['word', 'int', 'char', 'bool', 'float', 'f64', 'f32', 'byte', 'u8', 'i8', 'u16', 'i16', 'u32', 'i32']) {
const value = hover(type + ' global;\nfn main() { return 0; }\n', type);
assert.ok(value && value.startsWith('```zc\n') && value.includes('\n\n'), type + ' has formatted built-in documentation');
}
assert.match(hover('void noop() {}\nfn main() { return 0; }\n', 'void'), /no meaningful result/);
const array = 'fn main() {\n [4]u8 values;\n return values[0];\n}\n';
assert.match(hover(array, '[4]'), /fixed length of 4/);
assert.match(hover(array, 'values', 1), /type \[4\]u8/);
const struct = 'struct pair { int left; float right; };\nfn main() { pair value; return value.left; }\n';
assert.match(hover(struct, 'pair', 1), /int left;[\s\S]*float right;/);
assert.match(hover(struct, 'value', 1), /type pair/);
const fields = 'struct pair { int /* left field */ left, /* right field */ right; };\nfn main() { pair p; return p.left + p.right; }\n';
assert.match(hover(fields, 'left +'), /field; type int[\s\S]*left field/);
assert.match(hover(fields, 'right;'), /field; type int[\s\S]*right field/);
const empty = '/**/ int helper() { return 1; }\nfn main() { return helper(); }\n';
assert.match(hover(empty, 'helper();'), /int helper\(\)/);
const variableDocs = 'fn main() {\n int /* first variable */ first = 1, /* second variable */ second = 2;\n return first + second;\n}\n';
const first = hover(variableDocs, 'first +');
const second = hover(variableDocs, 'second;');
assert.ok(first.includes('first variable') && !first.includes('second variable'), first);
assert.ok(second.includes('second variable') && !second.includes('first variable'), second);
const paramDocs = 'int add(int /* left argument */ a, int /* right argument */ b) { return a + b; }\nfn main() { return add(1, 2); }\n';
const a = hover(paramDocs, 'a +');
const b = hover(paramDocs, 'b;');
assert.ok(a.includes('left argument') && !a.includes('right argument'), a);
assert.ok(b.includes('right argument') && !b.includes('left argument'), b);
const functionDocs = '/**\n * adds **one** to its argument.\n */\nint add(int x) { return x + 1; }\nfn main() { return add(2); }\n';
assert.match(hover(functionDocs, 'add(2)'), /adds \*\*one\*\*/);
const inferred = 'fn twice(x) { return x * 2; }\nfn main() { let value = twice(3); return value; }\n';
assert.match(hover(inferred, 'twice(3)'), /int twice\(int x\)/);
assert.match(hover(inferred, 'x *'), /type int/);
assert.match(hover(inferred, 'value;'), /type int/);
const pointer = 'int read(int* p) { return p[0]; }\nfn main() { int value = 3; return read(&value); }\n';
assert.match(hover(pointer, 'p[0]'), /type int\*[\s\S]*pointer to `int`/);
const alias = 'type row = [3]int;\nfn main() { row values; return values[0]; }\n';
assert.match(hover(alias, 'row values'), /\[3\]int[\s\S]*fixed length of 3/);
const enumeration = 'enum color { red, green = 5, blue };\nfn main() { color shade = green; return shade; }\n';
assert.match(hover(enumeration, 'shade;'), /type color/);
const dynamic = 'fn main() { []int items; push(items, 3); return len(items); }\n';
assert.match(hover(dynamic, 'items);'), /type \[\]int[\s\S]*growable array handle/);
const callable = 'fn main() { let apply = fn(int x) { return x + 1; }; return apply(2); }\n';
assert.match(hover(callable, 'apply(2)'), /type .*\([\s\S]*callable value/);
assert.match(hover(callable, 'apply(2)'), /type int\(int\)/);
const floatClosure = 'fn main() { let f = fn(float x) { return x + 1.5; }; let result = f(2.0); return result; }\n';
assert.match(hover(floatClosure, 'result;'), /type float/);
const inferredPointer = 'struct node { int value; };\nfn make_node() { return new(node); }\nfn main() { let p = make_node(); return p.value; }\n';
assert.match(hover(inferredPointer, 'p.value'), /type node\*/);
const aggregateAlias = 'fn main() { [3]int a; a[1] = 7; let alias = a; return alias[1]; }\n';
assert.match(hover(aggregateAlias, 'alias[1]'), /type int\*/);
console.log('ok built-in and composite types, inferred signatures, and separate block docs');
|