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
|
// include: a path is found next to the file that has it, then in the workspace folder, then as written; the
// error for one that cannot be read is on its string; include std; needs no file; a server with no files of its own
// is sent the text of each (zc/file). usage: node tests/drivers/include_test.js path/to/zclsp [path/to/lsp.wasm]
const assert = require('assert');
const cp = require('child_process');
const fs = require('fs');
const os = require('os');
const path = require('path');
const server = path.resolve(process.argv[2]);
const wasm = process.argv[3];
const frame = m => { const b = Buffer.from(JSON.stringify(m)); return Buffer.concat([Buffer.from('Content-Length: ' + b.length + '\r\n\r\n'), b]); };
function parse(out) {
const res = [];
let d = out;
while (d.length) {
const e = d.indexOf('\r\n\r\n'), n = Number(/Content-Length:\s*(\d+)/i.exec(d.subarray(0, e).toString())[1]);
res.push(JSON.parse(d.subarray(e + 4, e + 4 + n))); d = d.subarray(e + 4 + n);
}
return res;
}
// runs one session on the native server (cwd is where includes are read as written) or the wasm host
function check(uri, text, { files = [], root = null, cwd = os.tmpdir(), host = 'native', navigation = false } = {}) {
const msgs = [{ jsonrpc: '2.0', id: 1, method: 'initialize', params: { rootUri: root } },
...files.map(([p, t]) => ({ jsonrpc: '2.0', method: 'zc/file', params: { path: p, text: t } })),
{ jsonrpc: '2.0', method: 'textDocument/didOpen', params: { textDocument: { uri, version: 1, languageId: 'zc', text } } },
...(navigation ? [2, 3].map(id => ({ jsonrpc: '2.0', id, method: 'textDocument/definition', params: { textDocument: { uri }, position: { line: 2, character: 20 } } })) : []),
{ jsonrpc: '2.0', method: 'exit' }];
let out;
if (host === 'native') out = cp.spawnSync(server, [], { input: Buffer.concat(msgs.map(frame)), cwd, maxBuffer: 1 << 26 }).stdout;
else {
const { wasmProcess } = require(path.join(__dirname, '..', '..', 'editors', 'vscode', 'client', 'wasmproc.js'));
const p = wasmProcess(path.resolve(wasm), { log: () => {} });
const chunks = [];
p.stdout.on('data', d => chunks.push(d));
for (const m of msgs) p.stdin.write(frame(m));
out = Buffer.concat(chunks);
}
const replies = parse(out);
if (navigation) {
for (const id of [2, 3]) assert.deepStrictEqual(replies.find(m => m.id === id).result, [{ uri: 'file:///nowhere/proj/lib/virt.zc', range: { start: { line: 0, character: 4 }, end: { line: 0, character: 10 } } }], host + ': navigation uses client-provided includes repeatedly');
}
const note = replies.filter(m => m.method === 'textDocument/publishDiagnostics').pop();
return note ? note.params.diagnostics : null;
}
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'zcinc-'));
const w = (f, t) => { fs.mkdirSync(path.dirname(path.join(dir, f)), { recursive: true }); fs.writeFileSync(path.join(dir, f), t); };
w('src/lib/util.zc', 'int twice(int v) { return v * 2; }\n');
w('src/lib/deep.zc', 'include "util.zc";\nint quad(int v) { return twice(twice(v)); }\n');
w('src/tools/main.zc', 'include "../lib/deep.zc";\nfn main() { return quad(1); }\n');
w('top.zc', 'int fromtop() { return 7; }\n');
const uri = f => 'file://' + path.join(dir, f);
const read = f => fs.readFileSync(path.join(dir, f), 'utf8');
const errors = d => d.filter(x => x.severity === 1);
// next to the file, and nested includes next to the file that has them (the cwd is not the document's directory)
assert.deepStrictEqual(errors(check(uri('src/tools/main.zc'), read('src/tools/main.zc'))), [], 'a relative include, nested');
// the original build.zc reproduction, checked from an unrelated working directory
const buildFile = path.resolve(__dirname, '../../src/tools/build.zc');
const buildText = fs.readFileSync(buildFile, 'utf8').replace('include std;', 'include "../lib/std.zc";');
assert.deepStrictEqual(errors(check('file://' + buildFile, buildText)), [], 'build.zc finds ../lib/std.zc');
// the workspace folder
assert.deepStrictEqual(errors(check(uri('src/tools/a.zc'), 'include "top.zc";\nfn main() { return fromtop(); }\n', { root: 'file://' + dir })), [], 'found in the workspace folder');
// as written, from the working directory
assert.deepStrictEqual(errors(check(uri('src/tools/b.zc'), 'include "top.zc";\nfn main() { return fromtop(); }\n', { cwd: dir })), [], 'found from the working directory');
// an include that is not there: the error is on its string
let d = check(uri('src/tools/c.zc'), 'include "../lib/nothere.zc";\nfn main() { return 0; }\n');
assert.strictEqual(errors(d).length, 1);
assert.deepStrictEqual(errors(d)[0].range, { start: { line: 0, character: 8 }, end: { line: 0, character: 27 } }, 'the error is on the string: ' + JSON.stringify(errors(d)[0].range));
assert.match(errors(d)[0].message, /can't read that include/);
// include std; and the same twice
assert.deepStrictEqual(errors(check(uri('src/tools/d.zc'), 'include std;\ninclude std;\nfn main() { return std.Text.atoi("4"); }\n')), [], 'include std;');
d = check(uri('src/tools/e.zc'), 'include std\nfn main() { return 0; }\n');
assert.ok(errors(d).length >= 1, 'include std without a semicolon is an error');
d = check(uri('src/tools/f.zc'), 'include other;\nfn main() { return 0; }\n');
assert.match(errors(d)[0].message, /can't read that module include/);
assert.deepStrictEqual(errors(d)[0].range.start, { line: 0, character: 8 }, 'an unknown library is reported at its name');
// findings inside an included file: hints are left out, an error is shown on the include with where it is
w('src/lib/loud.zc', 'int a() { int unused = 1; return 2 * 2; }\nint b() { return 1 }\n');
d = check(uri('src/tools/g.zc'), 'include "../lib/loud.zc";\nfn main() { return a(); }\n');
assert.strictEqual(d.filter(x => x.severity !== 1).length, 0, 'no hints from a library: ' + JSON.stringify(d));
assert.strictEqual(errors(d).length, 1);
assert.match(errors(d)[0].message, /^in .*loud\.zc, line 2: .*expected ';'/);
assert.deepStrictEqual(errors(d)[0].range.start, { line: 0, character: 8 });
// a server that cannot read files is sent them
const virtual = '/nowhere/proj/lib/virt.zc';
const vdoc = 'file:///nowhere/proj/app/main.zc';
const vtext = 'include "../lib/virt.zc";\ninclude "../lib/gone.zc";\nfn main() { return thrice(2); }\n';
const files = [[virtual, 'int thrice(int v) { return v * 3; }\n']];
for (const host of wasm ? ['native', 'wasm'] : ['native']) {
d = check(vdoc, vtext, { files, host, navigation: true });
assert.strictEqual(errors(d).length, 1, host + ': ' + JSON.stringify(d));
assert.deepStrictEqual(errors(d)[0].range.start, { line: 1, character: 8 }, host + ': only the missing file is an error');
}
fs.rmSync(dir, { recursive: true });
console.log('ok includes: found next to the file, in the workspace and as written; errors on the string; include std; files sent by the client');
|