feat(wasm)!: keep API in-line with upstream and start aligning with node

This commit is contained in:
Amaan Qureshi 2024-03-09 01:28:35 -05:00
parent c070c92722
commit 728793a160
8 changed files with 743 additions and 107 deletions

View file

@ -1,11 +1,41 @@
const {assert} = require('chai');
let Parser; let JavaScript;
let Parser; let JavaScript; let JSON; let EmbeddedTemplate; let Python;
const JSON_EXAMPLE = `
[
123,
false,
{
"x": null
}
]
`;
function getAllNodes(tree) {
const result = [];
let visitedChildren = false;
const cursor = tree.walk();
while (true) {
if (!visitedChildren) {
result.push(cursor.currentNode);
if (!cursor.gotoFirstChild()) {
visitedChildren = true;
}
} else if (cursor.gotoNextSibling()) {
visitedChildren = false;
} else if (!cursor.gotoParent()) {
break;
}
}
return result;
}
describe('Node', () => {
let parser; let tree;
before(async () =>
({Parser, JavaScript} = await require('./helper')),
({Parser, EmbeddedTemplate, JavaScript, JSON, Python} = await require('./helper')),
);
beforeEach(() => {
@ -42,6 +72,31 @@ describe('Node', () => {
});
});
describe('.childrenForFieldName', () => {
it('returns an array of child nodes for the given field name', () => {
parser.setLanguage(Python);
const source = `
if one:
a()
elif two:
b()
elif three:
c()
elif four:
d()`;
tree = parser.parse(source);
const node = tree.rootNode.firstChild;
assert.equal(node.type, 'if_statement');
const alternatives = node.childrenForFieldName('alternative');
const alternativeTexts = alternatives.map((n) => {
const condition = n.childForFieldName('condition');
return source.slice(condition.startIndex, condition.endIndex);
});
assert.deepEqual(alternativeTexts, ['two', 'three', 'four']);
});
});
describe('.startIndex and .endIndex', () => {
it('returns the character index where the node starts/ends in the text', () => {
tree = parser.parse('a👍👎1 / b👎c👎');
@ -304,13 +359,26 @@ describe('Node', () => {
});
});
describe('.isExtra', () => {
it('returns true if the node is an extra node like comments', () => {
tree = parser.parse('foo(/* hi */);');
const node = tree.rootNode;
const commentNode = node.descendantForIndex(7, 7);
assert.equal(node.type, 'program');
assert.equal(commentNode.type, 'comment');
assert(!node.isExtra);
assert(commentNode.isExtra);
});
});
describe('.text', () => {
const text = 'α0 / b👎c👎';
Object.entries({
'.parse(String)': text,
'.parse(Function)': (offset) => text.substr(offset, 4),
}).forEach(([method, parse]) =>
'.parse(Function)': (offset) => text.slice(offset, 4),
}).forEach(([method, _parse]) =>
it(`returns the text of a node generated by ${method}`, async () => {
const [numeratorSrc, denominatorSrc] = text.split(/\s*\/\s+/);
tree = await parser.parse(text);
@ -326,6 +394,78 @@ describe('Node', () => {
);
});
describe('.descendantCount', () => {
it('returns the number of descendants', () => {
parser.setLanguage(JSON);
tree = parser.parse(JSON_EXAMPLE);
const valueNode = tree.rootNode;
const allNodes = getAllNodes(tree);
assert.equal(valueNode.descendantCount, allNodes.length);
const cursor = tree.walk();
for (let i = 0; i < allNodes.length; i++) {
const node = allNodes[i];
cursor.gotoDescendant(i);
assert.equal(cursor.currentNode.id, node.id, `index ${i}`);
}
for (let i = allNodes.length - 1; i >= 0; i--) {
const node = allNodes[i];
cursor.gotoDescendant(i);
assert.equal(cursor.currentNode.id, node.id, `rev index ${i}`);
}
});
it('tests a single node tree', () => {
parser.setLanguage(EmbeddedTemplate);
tree = parser.parse('hello');
const nodes = getAllNodes(tree);
assert.equal(nodes.length, 2);
assert.equal(tree.rootNode.descendantCount, 2);
const cursor = tree.walk();
cursor.gotoDescendant(0);
assert.equal(cursor.currentDepth, 0);
assert.equal(cursor.currentNode.id, nodes[0].id);
cursor.gotoDescendant(1);
assert.equal(cursor.currentDepth, 1);
assert.equal(cursor.currentNode.id, nodes[1].id);
});
});
describe('.rootNodeWithOffset', () => {
it('returns the root node of the tree, offset by the given byte offset', () => {
tree = parser.parse(' if (a) b');
const node = tree.rootNodeWithOffset(6, {row: 2, column: 2});
assert.equal(node.startIndex, 8);
assert.equal(node.endIndex, 16);
assert.deepEqual(node.startPosition, {row: 2, column: 4});
assert.deepEqual(node.endPosition, {row: 2, column: 12});
let child = node.firstChild.child(2);
assert.equal(child.type, 'expression_statement');
assert.equal(child.startIndex, 15);
assert.equal(child.endIndex, 16);
assert.deepEqual(child.startPosition, {row: 2, column: 11});
assert.deepEqual(child.endPosition, {row: 2, column: 12});
const cursor = node.walk();
cursor.gotoFirstChild();
cursor.gotoFirstChild();
cursor.gotoNextSibling();
child = cursor.currentNode;
assert.equal(child.type, 'parenthesized_expression');
assert.equal(child.startIndex, 11);
assert.equal(child.endIndex, 14);
assert.deepEqual(child.startPosition, {row: 2, column: 7});
assert.deepEqual(child.endPosition, {row: 2, column: 10});
});
});
describe('.parseState, .nextParseState', () => {
const text = '10 / 5';
@ -435,6 +575,29 @@ describe('Node', () => {
});
});
describe('.firstChildForIndex(index)', () => {
it('returns the first child that extends beyond the given index', () => {
tree = parser.parse('x10 + 1000');
const sumNode = tree.rootNode.firstChild.firstChild;
assert.equal('identifier', sumNode.firstChildForIndex(0).type);
assert.equal('identifier', sumNode.firstChildForIndex(1).type);
assert.equal('+', sumNode.firstChildForIndex(3).type);
assert.equal('number', sumNode.firstChildForIndex(5).type);
});
});
describe('.firstNamedChildForIndex(index)', () => {
it('returns the first child that extends beyond the given index', () => {
tree = parser.parse('x10 + 1000');
const sumNode = tree.rootNode.firstChild.firstChild;
assert.equal('identifier', sumNode.firstNamedChildForIndex(0).type);
assert.equal('identifier', sumNode.firstNamedChildForIndex(1).type);
assert.equal('number', sumNode.firstNamedChildForIndex(3).type);
});
});
describe('.equals(other)', () => {
it('returns true if the nodes are the same', () => {
tree = parser.parse('1 + 2');