aboutsummaryrefslogtreecommitdiff
path: root/tests/drivers/hover_features_test.js
blob: d6e1226c1ad0b37ae3eb3929bd55bc145374128b (plain) (blame)
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// 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 u8[4] values;\n return values[0];\n}\n';
assert.match(hover(array, '[4]'), /fixed length of 4/);
assert.match(hover(array, 'values', 1), /\(u8\[4\]\) let values/);
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), /\(pair\) let value/);
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 +'), /\(int\) let left \/\/ field;[\s\S]*left field/);
assert.match(hover(fields, 'right;'), /\(int\) let right \/\/ field;[\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 *'), /\(int\) let x \/\/ parameter;/);
assert.match(hover(inferred, 'value;'), /\(int\) let value/);
const pointer = 'int read(int* p) { return p[0]; }\nfn main() { int value = 3; return read(&value); }\n';
assert.match(hover(pointer, 'p[0]'), /\(int\*\) let p \/\/ parameter;[\s\S]*pointer to `int`/);
const alias = 'type row = int[3];\nfn main() { row values; return values[0]; }\n';
assert.match(hover(alias, 'row values'), /int\[3\][\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;'), /\(color\) let shade/);
const dynamic = 'fn main() { int[?] items; push(items, 3); return len(items); }\n';
assert.match(hover(dynamic, 'items);'), /\(int\[\?\]\) let items[\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)'), /\(.*\([\s\S]*callable value/);
assert.match(hover(callable, 'apply(2)'), /\(int\(int\)\) let.m apply/);
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;'), /\(float\) let result/);
const inferredPointer = 'include rt;\nstruct 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'), /\(node\*\) let.m p/);
const aggregateAlias = 'fn main() { int[3] a; a[1] = 7; let alias = a; return alias[1]; }\n';
assert.match(hover(aggregateAlias, 'alias[1]'), /\(int\*\) let alias/);
const sections = '// --- values ---\n// these declarations form a group.\nconst int limit = 7;\nfn main() { return limit; }';
assert.ok(!hover(sections, 'limit;').includes('these declarations form a group'));
assert.ok(!hover(sections, 'limit;').includes('--- values ---'));
const blockSection = '/* ---- values ---- */\nint limit = 7;\nfn main() { return limit; }';
assert.ok(!hover(blockSection, 'limit;').includes('---- values ----'));
const trailing = 'fn main() {\n int n = 7; // the current count\n int next = 2;\n return n + next;\n}';
assert.match(hover(trailing, 'n +'), /the current count/);
assert.ok(!hover(trailing, 'next;').includes('the current count'));
const trailingBlock = 'fn main() { int a = 1, b = 2; /* final count */\n return a + b; }';
assert.ok(!hover(trailingBlock, 'a +').includes('final count'));
assert.match(hover(trailingBlock, 'b;'), /final count/);
const adjacent = 'fn main() { int a /* first count */ = 1, b /* second count */ = 2; return a + b; }';
assert.match(hover(adjacent, 'a +'), /first count/);
assert.ok(!hover(adjacent, 'a +').includes('second count'));
assert.match(hover(adjacent, 'b;'), /second count/);
// section headings end a doc block; trailing comments belong to their own line's declaration
const sectionDocs = '// ---- one ----\n\n// adds.\nint add(int a, int b) { return a + b; } // after add\n// overview\n// ---- two ----\nint sub(int a, int b) { return a - b; }\n// ---- three ----\n// own doc\nint mul(int a, int b) { return a * b; }\nint g = 3; /* after g */\nfn main() { return add(1, 2) + sub(3, 1) + mul(2, 2) + g; }\n';
const addHover = hover(sectionDocs, 'add(1');
assert.ok(addHover.includes('adds.') && addHover.includes('after add') && !addHover.includes('overview') && !addHover.includes('after g'), addHover);
assert.strictEqual(hover(sectionDocs, 'sub(3'), '```zc\nint sub(int a, int b)\n```');
const mulHover = hover(sectionDocs, 'mul(2');
assert.ok(!mulHover.includes('own doc') && !mulHover.includes('three') && !mulHover.includes('after'), mulHover);
assert.ok(hover(sectionDocs, 'g; }').includes('after g'));
// ownership and transfers explained in hovers
const lifeDocs = 'include rt; struct node { int v; node* next; };\nint*.u make(int n) { int* q = new(int); *q = n; return u*q; }\nfn take(.d p) { return p[0]; }\nfn main() {\n let a = make(1);\n node* b = new(node), c = new(node);\n b.next = c;\n node* r = b.next;\n let d = new(3);\n take(d*d);\n return a[0] + r.v;\n}\n';
assert.match(hover(lifeDocs, 'q = new'), /let.m q[\s\S]*passed up: returned on line 2/);
assert.match(hover(lifeDocs, 'a ='), /let.m a[\s\S]*freed where its block ends on line 12/);
assert.match(hover(lifeDocs, 'r.v'), /borrows from `b`, declared on line 6/);
assert.match(hover(lifeDocs, 'c = new'), /passed down: stored in a container `b` on line 7/);
assert.match(hover(lifeDocs, 'd = new'), /passed down: given to a d parameter on line 10/);
const declaredResult = 'fn main() { let f = fn:float(int x) { return x; }; return f(2); }\n';
assert.match(hover(declaredResult, 'f(2)'), /\(float\(int\)\) let.m f/);
const nonDocs = '//: scratch region overview\nint scratch = 1;\n/* public count */ int count = 2; //: implementation note\nfn main() { return scratch + count; }';
assert.ok(!hover(nonDocs, 'scratch +').includes('scratch region overview'));
assert.match(hover(nonDocs, 'count;'), /public count/);
assert.ok(!hover(nonDocs, 'count;').includes('implementation note'));
const globals = 'let hello = "hello", prog[20000];\nint count = 3;\nfn main() { return hello[0] + prog[0] + count; }\n';
for (const [name, type, use] of [['hello', 'char*', 'hello[0]'], ['prog', 'word[20000]', 'prog[0]'], ['count', 'int', 'count;']]) {
    for (const needle of [name, use]) {
        const value = hover(globals, needle);
        assert.ok(value.startsWith('```zc\n(' + type + ') let ' + name + ' // global;\n```'), value);
        assert.ok(!value.includes('; type '), value);
    }
}
assert.match(hover('include run; fn main() { return run.sval[0]; }', 'sval'), /symbol[\s\S]*value/i);
assert.match(hover('include rt; fn main() { return rt.len_s(b"bytes"); }', 'len_s'), /number of bytes in a packed byte string/i);
console.log('ok   built-in and composite types, inferred signatures, and separate block docs');