aboutsummaryrefslogtreecommitdiff
path: root/plugin/test/highlighting.test.js
blob: f7a67dbef85b4b9e1cc64f8c94d6e05c3d7dac92 (plain)
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

'use strict';

const assert = require('node:assert/strict');
const { test, before } = require('node:test');
const fs = require('node:fs');
const path = require('node:path');
const { Registry } = require('vscode-textmate');
const { loadWASM, OnigScanner, OnigString } = require('vscode-oniguruma');

let grammar;
before(async () => {
	const wasm = fs.readFileSync(require.resolve('vscode-oniguruma/release/onig.wasm'));
	await loadWASM(wasm.buffer.slice(wasm.byteOffset, wasm.byteOffset + wasm.byteLength));
	const registry = new Registry({
		onigLib: Promise.resolve({
			createOnigScanner: patterns => new OnigScanner(patterns),
			createOnigString: text => new OnigString(text)
		}),
		loadGrammar: async () => JSON.parse(fs.readFileSync(
			path.join(__dirname, '../syntaxes/cie-pseudocode.tmLanguage.json'), 'utf8'))
	});
	grammar = await registry.loadGrammar('source.cie-pseudocode');
});

function scopesAt(text, word) {
	const offset = text.indexOf(word);
	return grammar.tokenizeLine(text).tokens.find(
		token => token.startIndex <= offset && token.endIndex > offset).scopes;
}

test('C++ style scopes distinguish keywords, types, literals, operators and builtins', () => {
	for (const [text, word, scope] of [
		['IF Ready THEN', 'IF', 'keyword.control'],
		['DECLARE X : INTEGER', 'DECLARE', 'storage.modifier'],
		['DECLARE X : INTEGER', 'INTEGER', 'storage.type'],
		['DECLARE X : C_INT', 'C_INT', 'storage.type'],
		['DECLARE X : C_CHAR', 'C_CHAR', 'storage.type'],
		['DECLARE X : C_STRING', 'C_STRING', 'storage.type'],
		['OUTPUT (C_DOUBLE)X', 'C_DOUBLE', 'storage.type'],
		['TYPE Native = C_TYPE("int")', 'C_TYPE', 'storage.type'],
		['X <- TRUE', 'TRUE', 'constant.language'],
		['X <- 12.5', '12.5', 'constant.numeric'],
		['X <- 2 DIV 1', 'DIV', 'keyword.operator.word'],
		['X <- 1', '<-', 'keyword.operator.assignment.cpp'],
		['OUTPUT "text"', 'text', 'string.quoted.double'],
		["X <- 'A'", 'A', 'string.quoted.single']
	]) assert.ok(scopesAt(text, word).some(s => s.startsWith(scope)), text);
	for (const name of ['LENGTH', 'MID', 'RIGHT', 'LEFT', 'LCASE', 'UCASE',
		'ASC', 'CHR', 'IS_NUM', 'NUM_TO_STR', 'STR_TO_NUM', 'EOF']) {
		assert.ok(scopesAt(`${name}(X)`, name).includes('entity.name.function.cie-pseudocode'));
	}
});

test('both assignment characters share the C++ assignment scope', () => {
	const text = 'X <- 1';
	assert.deepEqual(scopesAt(text, '<-'), scopesAt(text, '-'));
	assert.ok(!scopesAt('X < 1', '<').includes('keyword.operator.assignment.cpp'));
	assert.ok(!scopesAt('X - 1', '-').includes('keyword.operator.assignment.cpp'));
});

test('exception keywords reuse C++ scopes and ignore non-keyword occurrences', () => {
	for (const [word, scope] of [
		['TRY', 'keyword.control.try.cpp'],
		['CATCH', 'keyword.control.catch.cpp'],
		['THROW', 'keyword.control.throw.cpp'],
		['ENDTRY', 'keyword.control.try.cpp']
	]) {
		assert.ok(scopesAt(word, word).includes(scope));
		assert.ok(scopesAt(`// ${word}`, word).some(s => s.startsWith('comment.')));
		assert.ok(scopesAt(`OUTPUT "${word}"`, word).some(s => s.startsWith('string.')));
		for (const text of [word.toLowerCase(), `${word}Value`, `My${word}`, `${word}_1`]) {
			assert.ok(!scopesAt(text, text).some(s => s.startsWith('keyword.')), text);
		}
	}
});

test('comments, strings, lowercase and keyword prefixes cannot become keywords', () => {
	assert.ok(scopesAt('// IF [(', 'IF').some(s => s.startsWith('comment.')));
	assert.ok(scopesAt('OUTPUT "IF // ["', 'IF').some(s => s.startsWith('string.')));
	assert.ok(scopesAt('TYPE Native = C_TYPE("C_INT")', 'C_INT').some(s => s.startsWith('string.')));
	assert.ok(scopesAt('// C_TYPE("int")', 'C_TYPE').some(s => s.startsWith('comment.')));
	for (const text of ['c_int', 'C_INTValue', 'MyC_INT', 'C_TYPE_1']) {
		assert.ok(!scopesAt(text, text).some(s => s.startsWith('storage.')), text);
	}
	for (const text of ['if X then', 'IFValue <- 1', 'CASE OF X', 'CLASS Example']) {
		assert.ok(!scopesAt(text, text.split(' ')[0]).some(
			s => /^(keyword|storage)\./.test(s)), text);
	}
	// CIE has neither backslash escapes nor multiline string literals.
	let state = grammar.tokenizeLine('OUTPUT "unfinished').ruleStack;
	assert.ok(grammar.tokenizeLine('IF X THEN', state).tokens[0].scopes.includes(
		'keyword.control.cie-pseudocode'));
	assert.ok(scopesAt('OUTPUT "x\\" + 1', '+').includes('keyword.operator.cie-pseudocode'));
	assert.ok(scopesAt("OUTPUT ''' + 1", '+').includes('keyword.operator.cie-pseudocode'));
});