feat(wasm)!: keep API in-line with upstream and start aligning with node
This commit is contained in:
parent
c070c92722
commit
728793a160
8 changed files with 743 additions and 107 deletions
|
|
@ -7,5 +7,9 @@ function languageURL(name) {
|
|||
module.exports = Parser.init().then(async () => ({
|
||||
Parser,
|
||||
languageURL,
|
||||
EmbeddedTemplate: await Parser.Language.load(languageURL('embedded_template')),
|
||||
HTML: await Parser.Language.load(languageURL('html')),
|
||||
JavaScript: await Parser.Language.load(languageURL('javascript')),
|
||||
JSON: await Parser.Language.load(languageURL('json')),
|
||||
Python: await Parser.Language.load(languageURL('python')),
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
const {assert} = require('chai');
|
||||
let Parser; let JavaScript; let languageURL;
|
||||
let Parser; let JavaScript; let HTML; let languageURL;
|
||||
|
||||
describe('Parser', () => {
|
||||
let parser;
|
||||
|
||||
before(async () =>
|
||||
({Parser, JavaScript, languageURL} = await require('./helper')),
|
||||
({Parser, JavaScript, HTML, languageURL} = await require('./helper')),
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
@ -73,7 +73,7 @@ describe('Parser', () => {
|
|||
|
||||
it('rethrows errors thrown by the logging callback', () => {
|
||||
const error = new Error('The error message');
|
||||
parser.setLogger((msg, params) => {
|
||||
parser.setLogger((_msg, _params) => {
|
||||
throw error;
|
||||
});
|
||||
assert.throws(
|
||||
|
|
@ -83,6 +83,99 @@ describe('Parser', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('multiple included ranges', () => {
|
||||
it('parses the text within multiple ranges', () => {
|
||||
parser.setLanguage(JavaScript);
|
||||
const sourceCode = 'html `<div>Hello, ${name.toUpperCase()}, it\'s <b>${now()}</b>.</div>`';
|
||||
const jsTree = parser.parse(sourceCode);
|
||||
const templateStringNode = jsTree.rootNode.descendantForIndex(sourceCode.indexOf('`<'), sourceCode.indexOf('>`'));
|
||||
assert.equal(templateStringNode.type, 'template_string');
|
||||
|
||||
const openQuoteNode = templateStringNode.child(0);
|
||||
const interpolationNode1 = templateStringNode.child(2);
|
||||
const interpolationNode2 = templateStringNode.child(4);
|
||||
const closeQuoteNode = templateStringNode.child(6);
|
||||
|
||||
parser.setLanguage(HTML);
|
||||
const htmlRanges = [
|
||||
{
|
||||
startIndex: openQuoteNode.endIndex,
|
||||
startPosition: openQuoteNode.endPosition,
|
||||
endIndex: interpolationNode1.startIndex,
|
||||
endPosition: interpolationNode1.startPosition,
|
||||
},
|
||||
{
|
||||
startIndex: interpolationNode1.endIndex,
|
||||
startPosition: interpolationNode1.endPosition,
|
||||
endIndex: interpolationNode2.startIndex,
|
||||
endPosition: interpolationNode2.startPosition,
|
||||
},
|
||||
{
|
||||
startIndex: interpolationNode2.endIndex,
|
||||
startPosition: interpolationNode2.endPosition,
|
||||
endIndex: closeQuoteNode.startIndex,
|
||||
endPosition: closeQuoteNode.startPosition,
|
||||
},
|
||||
];
|
||||
const htmlTree = parser.parse(sourceCode, null, {includedRanges: htmlRanges});
|
||||
|
||||
assert.equal(
|
||||
htmlTree.rootNode.toString(),
|
||||
'(document (element' +
|
||||
' (start_tag (tag_name))' +
|
||||
' (text)' +
|
||||
' (element (start_tag (tag_name)) (end_tag (tag_name)))' +
|
||||
' (text)' +
|
||||
' (end_tag (tag_name))))',
|
||||
);
|
||||
assert.deepEqual(htmlTree.getIncludedRanges(), htmlRanges);
|
||||
|
||||
const divElementNode = htmlTree.rootNode.child(0);
|
||||
const helloTextNode = divElementNode.child(1);
|
||||
const bElementNode = divElementNode.child(2);
|
||||
const bStartTagNode = bElementNode.child(0);
|
||||
const bEndTagNode = bElementNode.child(1);
|
||||
|
||||
assert.equal(helloTextNode.type, 'text');
|
||||
assert.equal(helloTextNode.startIndex, sourceCode.indexOf('Hello'));
|
||||
assert.equal(helloTextNode.endIndex, sourceCode.indexOf(' <b>'));
|
||||
|
||||
assert.equal(bStartTagNode.type, 'start_tag');
|
||||
assert.equal(bStartTagNode.startIndex, sourceCode.indexOf('<b>'));
|
||||
assert.equal(bStartTagNode.endIndex, sourceCode.indexOf('${now()}'));
|
||||
|
||||
assert.equal(bEndTagNode.type, 'end_tag');
|
||||
assert.equal(bEndTagNode.startIndex, sourceCode.indexOf('</b>'));
|
||||
assert.equal(bEndTagNode.endIndex, sourceCode.indexOf('.</div>'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('an included range containing mismatched positions', () => {
|
||||
it('parses the text within the range', () => {
|
||||
const sourceCode = '<div>test</div>{_ignore_this_part_}';
|
||||
|
||||
parser.setLanguage(HTML);
|
||||
|
||||
const endIndex = sourceCode.indexOf('{_ignore_this_part_');
|
||||
|
||||
const rangeToParse = {
|
||||
startIndex: 0,
|
||||
startPosition: {row: 10, column: 12},
|
||||
endIndex,
|
||||
endPosition: {row: 10, column: 12 + endIndex},
|
||||
};
|
||||
|
||||
const htmlTree = parser.parse(sourceCode, null, {includedRanges: [rangeToParse]});
|
||||
|
||||
assert.deepEqual(htmlTree.getIncludedRanges()[0], rangeToParse);
|
||||
|
||||
assert.equal(
|
||||
htmlTree.rootNode.toString(),
|
||||
'(document (element (start_tag (tag_name)) (text) (end_tag (tag_name))))',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.parse', () => {
|
||||
let tree;
|
||||
|
||||
|
|
|
|||
|
|
@ -71,8 +71,10 @@ describe('Query', () => {
|
|||
query = JavaScript.query('(identifier) @element');
|
||||
const matches = query.matches(
|
||||
tree.rootNode,
|
||||
{row: 1, column: 1},
|
||||
{row: 3, column: 1},
|
||||
{
|
||||
startPosition: {row: 1, column: 1},
|
||||
endPosition: {row: 3, column: 1},
|
||||
},
|
||||
);
|
||||
assert.deepEqual(formatMatches(matches), [
|
||||
{pattern: 0, captures: [{name: 'element', text: 'd'}]},
|
||||
|
|
@ -273,7 +275,7 @@ describe('Query', () => {
|
|||
(array (identifier) @pre (identifier) @post)
|
||||
`);
|
||||
|
||||
query.captures(tree.rootNode, null, null, {matchLimit: 32});
|
||||
query.captures(tree.rootNode, {matchLimit: 32});
|
||||
assert.ok(query.didExceedMatchLimit());
|
||||
});
|
||||
|
||||
|
|
@ -295,7 +297,7 @@ describe('Query', () => {
|
|||
|
||||
const expectCount = (tree, queryText, expectedCount) => {
|
||||
query = JavaScript.query(queryText);
|
||||
captures = query.captures(tree.rootNode, null, null);
|
||||
captures = query.captures(tree.rootNode);
|
||||
assert.equal(captures.length, expectedCount);
|
||||
};
|
||||
|
||||
|
|
@ -406,6 +408,49 @@ describe('Query', () => {
|
|||
assert.deepEqual(query.predicatesForPattern(2), []);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.disableCapture', () => {
|
||||
it('disables a capture', () => {
|
||||
const query = JavaScript.query(`
|
||||
(function_declaration
|
||||
(identifier) @name1 @name2 @name3
|
||||
(statement_block) @body1 @body2)
|
||||
`);
|
||||
|
||||
const source = 'function foo() { return 1; }';
|
||||
const tree = parser.parse(source);
|
||||
|
||||
let matches = query.matches(tree.rootNode);
|
||||
assert.deepEqual(formatMatches(matches), [
|
||||
{
|
||||
pattern: 0,
|
||||
captures: [
|
||||
{name: 'name1', text: 'foo'},
|
||||
{name: 'name2', text: 'foo'},
|
||||
{name: 'name3', text: 'foo'},
|
||||
{name: 'body1', text: '{ return 1; }'},
|
||||
{name: 'body2', text: '{ return 1; }'},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
// disabling captures still works when there are multiple captures on a
|
||||
// single node.
|
||||
query.disableCapture('name2');
|
||||
matches = query.matches(tree.rootNode);
|
||||
assert.deepEqual(formatMatches(matches), [
|
||||
{
|
||||
pattern: 0,
|
||||
captures: [
|
||||
{name: 'name1', text: 'foo'},
|
||||
{name: 'name3', text: 'foo'},
|
||||
{name: 'body1', text: '{ return 1; }'},
|
||||
{name: 'body2', text: '{ return 1; }'},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function formatMatches(matches) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue