Reorganize language bindings
* Move rust binding: lib/binding -> lib/binding_rust * Move wasm bindinig: lib/web -> lib/binding_web * Add wasm readme
This commit is contained in:
parent
a3ceb8f3a5
commit
3fc459a84b
23 changed files with 125 additions and 18 deletions
9
lib/binding_web/test/helper.js
Normal file
9
lib/binding_web/test/helper.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
const release = '../../../target/release'
|
||||
const Parser = require(`${release}/tree-sitter.js`);
|
||||
const languageURL = name => require.resolve(`${release}/tree-sitter-${name}.wasm`);
|
||||
|
||||
module.exports = Parser.init().then(async () => ({
|
||||
Parser,
|
||||
languageURL,
|
||||
JavaScript: await Parser.Language.load(languageURL('javascript')),
|
||||
}));
|
||||
358
lib/binding_web/test/node-test.js
Normal file
358
lib/binding_web/test/node-test.js
Normal file
|
|
@ -0,0 +1,358 @@
|
|||
const {assert} = require('chai');
|
||||
let Parser, JavaScript;
|
||||
|
||||
describe("Node", () => {
|
||||
let parser, tree;
|
||||
|
||||
before(async () =>
|
||||
({Parser, JavaScript} = await require('./helper'))
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
tree = null;
|
||||
parser = new Parser().setLanguage(JavaScript);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
parser.delete();
|
||||
tree.delete();
|
||||
});
|
||||
|
||||
describe(".children", () => {
|
||||
it("returns an array of child nodes", () => {
|
||||
tree = parser.parse("x10 + 1000");
|
||||
assert.equal(1, tree.rootNode.children.length);
|
||||
const sumNode = tree.rootNode.firstChild.firstChild;
|
||||
assert.deepEqual(
|
||||
sumNode.children.map(child => child.type),
|
||||
["identifier", "+", "number"]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe(".namedChildren", () => {
|
||||
it("returns an array of named child nodes", () => {
|
||||
tree = parser.parse("x10 + 1000");
|
||||
const sumNode = tree.rootNode.firstChild.firstChild;
|
||||
assert.equal(1, tree.rootNode.namedChildren.length);
|
||||
assert.deepEqual(
|
||||
["identifier", "number"],
|
||||
sumNode.namedChildren.map(child => child.type)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe(".startIndex and .endIndex", () => {
|
||||
it("returns the character index where the node starts/ends in the text", () => {
|
||||
tree = parser.parse("a👍👎1 / b👎c👎");
|
||||
const quotientNode = tree.rootNode.firstChild.firstChild;
|
||||
|
||||
assert.equal(0, quotientNode.startIndex);
|
||||
assert.equal(15, quotientNode.endIndex);
|
||||
assert.deepEqual(
|
||||
[0, 7, 9],
|
||||
quotientNode.children.map(child => child.startIndex)
|
||||
);
|
||||
assert.deepEqual(
|
||||
[6, 8, 15],
|
||||
quotientNode.children.map(child => child.endIndex)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe(".startPosition and .endPosition", () => {
|
||||
it("returns the row and column where the node starts/ends in the text", () => {
|
||||
tree = parser.parse("x10 + 1000");
|
||||
const sumNode = tree.rootNode.firstChild.firstChild;
|
||||
assert.equal("binary_expression", sumNode.type);
|
||||
|
||||
assert.deepEqual({ row: 0, column: 0 }, sumNode.startPosition);
|
||||
assert.deepEqual({ row: 0, column: 10 }, sumNode.endPosition);
|
||||
assert.deepEqual(
|
||||
[{ row: 0, column: 0 }, { row: 0, column: 4 }, { row: 0, column: 6 }],
|
||||
sumNode.children.map(child => child.startPosition)
|
||||
);
|
||||
assert.deepEqual(
|
||||
[{ row: 0, column: 3 }, { row: 0, column: 5 }, { row: 0, column: 10 }],
|
||||
sumNode.children.map(child => child.endPosition)
|
||||
);
|
||||
});
|
||||
|
||||
it("handles characters that occupy two UTF16 code units", () => {
|
||||
tree = parser.parse("a👍👎1 /\n b👎c👎");
|
||||
const sumNode = tree.rootNode.firstChild.firstChild;
|
||||
assert.deepEqual(
|
||||
[
|
||||
[{ row: 0, column: 0 }, { row: 0, column: 6 }],
|
||||
[{ row: 0, column: 7 }, { row: 0, column: 8 }],
|
||||
[{ row: 1, column: 1 }, { row: 1, column: 7 }]
|
||||
],
|
||||
sumNode.children.map(child => [child.startPosition, child.endPosition])
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe(".parent", () => {
|
||||
it("returns the node's parent", () => {
|
||||
tree = parser.parse("x10 + 1000");
|
||||
const sumNode = tree.rootNode.firstChild;
|
||||
const variableNode = sumNode.firstChild;
|
||||
assert.notEqual(sumNode.id, variableNode.id);
|
||||
assert.equal(sumNode.id, variableNode.parent.id);
|
||||
assert.equal(tree.rootNode.id, sumNode.parent.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.child(), .firstChild, .lastChild', () => {
|
||||
it('returns null when the node has no children', () => {
|
||||
tree = parser.parse("x10 + 1000");
|
||||
const sumNode = tree.rootNode.firstChild.firstChild;
|
||||
const variableNode = sumNode.firstChild;
|
||||
assert.equal(variableNode.firstChild, null);
|
||||
assert.equal(variableNode.lastChild, null);
|
||||
assert.equal(variableNode.firstNamedChild, null);
|
||||
assert.equal(variableNode.lastNamedChild, null);
|
||||
assert.equal(variableNode.child(1), null);
|
||||
})
|
||||
});
|
||||
|
||||
describe(".nextSibling and .previousSibling", () => {
|
||||
it("returns the node's next and previous sibling", () => {
|
||||
tree = parser.parse("x10 + 1000");
|
||||
const sumNode = tree.rootNode.firstChild.firstChild;
|
||||
assert.equal(sumNode.children[1].id, sumNode.children[0].nextSibling.id);
|
||||
assert.equal(sumNode.children[2].id, sumNode.children[1].nextSibling.id);
|
||||
assert.equal(
|
||||
sumNode.children[0].id,
|
||||
sumNode.children[1].previousSibling.id
|
||||
);
|
||||
assert.equal(
|
||||
sumNode.children[1].id,
|
||||
sumNode.children[2].previousSibling.id
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe(".nextNamedSibling and .previousNamedSibling", () => {
|
||||
it("returns the node's next and previous named sibling", () => {
|
||||
tree = parser.parse("x10 + 1000");
|
||||
const sumNode = tree.rootNode.firstChild.firstChild;
|
||||
assert.equal(
|
||||
sumNode.namedChildren[1].id,
|
||||
sumNode.namedChildren[0].nextNamedSibling.id
|
||||
);
|
||||
assert.equal(
|
||||
sumNode.namedChildren[0].id,
|
||||
sumNode.namedChildren[1].previousNamedSibling.id
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe(".descendantForIndex(min, max)", () => {
|
||||
it("returns the smallest node that spans the given range", () => {
|
||||
tree = parser.parse("x10 + 1000");
|
||||
const sumNode = tree.rootNode.firstChild.firstChild;
|
||||
assert.equal("identifier", sumNode.descendantForIndex(1, 2).type);
|
||||
assert.equal("+", sumNode.descendantForIndex(4, 4).type);
|
||||
|
||||
assert.throws(() => {
|
||||
sumNode.descendantForIndex(1, {});
|
||||
}, "Arguments must be numbers");
|
||||
|
||||
assert.throws(() => {
|
||||
sumNode.descendantForIndex();
|
||||
}, "Arguments must be numbers");
|
||||
});
|
||||
});
|
||||
|
||||
describe(".namedDescendantForIndex", () => {
|
||||
it("returns the smallest node that spans the given range", () => {
|
||||
tree = parser.parse("x10 + 1000");
|
||||
const sumNode = tree.rootNode.firstChild;
|
||||
assert.equal("identifier", sumNode.descendantForIndex(1, 2).type);
|
||||
assert.equal("+", sumNode.descendantForIndex(4, 4).type);
|
||||
});
|
||||
});
|
||||
|
||||
describe(".descendantForPosition(min, max)", () => {
|
||||
it("returns the smallest node that spans the given range", () => {
|
||||
tree = parser.parse("x10 + 1000");
|
||||
const sumNode = tree.rootNode.firstChild;
|
||||
|
||||
assert.equal(
|
||||
"identifier",
|
||||
sumNode.descendantForPosition(
|
||||
{ row: 0, column: 1 },
|
||||
{ row: 0, column: 2 }
|
||||
).type
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
"+",
|
||||
sumNode.descendantForPosition({ row: 0, column: 4 }).type
|
||||
);
|
||||
|
||||
assert.throws(() => {
|
||||
sumNode.descendantForPosition(1, {});
|
||||
}, "Arguments must be {row, column} objects");
|
||||
|
||||
assert.throws(() => {
|
||||
sumNode.descendantForPosition();
|
||||
}, "Arguments must be {row, column} objects");
|
||||
});
|
||||
});
|
||||
|
||||
describe(".namedDescendantForPosition(min, max)", () => {
|
||||
it("returns the smallest named node that spans the given range", () => {
|
||||
tree = parser.parse("x10 + 1000");
|
||||
const sumNode = tree.rootNode.firstChild;
|
||||
|
||||
assert.equal(
|
||||
sumNode.namedDescendantForPosition(
|
||||
{ row: 0, column: 1 },
|
||||
{ row: 0, column: 2 }
|
||||
).type,
|
||||
"identifier",
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
sumNode.namedDescendantForPosition({ row: 0, column: 4 }).type,
|
||||
'binary_expression'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe(".hasError()", () => {
|
||||
it("returns true if the node contains an error", () => {
|
||||
tree = parser.parse("1 + 2 * * 3");
|
||||
const node = tree.rootNode;
|
||||
assert.equal(
|
||||
node.toString(),
|
||||
'(program (expression_statement (binary_expression (number) (binary_expression (number) (ERROR) (number)))))'
|
||||
);
|
||||
|
||||
const sum = node.firstChild.firstChild;
|
||||
assert(sum.hasError());
|
||||
assert(!sum.children[0].hasError());
|
||||
assert(!sum.children[1].hasError());
|
||||
assert(sum.children[2].hasError());
|
||||
});
|
||||
});
|
||||
|
||||
describe(".isMissing()", () => {
|
||||
it("returns true if the node is missing from the source and was inserted via error recovery", () => {
|
||||
tree = parser.parse("(2 ||)");
|
||||
const node = tree.rootNode;
|
||||
assert.equal(
|
||||
node.toString(),
|
||||
"(program (expression_statement (parenthesized_expression (binary_expression (number) (MISSING identifier)))))"
|
||||
);
|
||||
|
||||
const sum = node.firstChild.firstChild.firstNamedChild;
|
||||
assert.equal(sum.type, 'binary_expression')
|
||||
assert(sum.hasError());
|
||||
assert(!sum.children[0].isMissing());
|
||||
assert(!sum.children[1].isMissing());
|
||||
assert(sum.children[2].isMissing());
|
||||
});
|
||||
});
|
||||
|
||||
describe(".text", () => {
|
||||
const text = "α0 / b👎c👎";
|
||||
|
||||
Object.entries({
|
||||
'.parse(String)': text,
|
||||
'.parse(Function)': offset => text.substr(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)
|
||||
const quotientNode = tree.rootNode.firstChild.firstChild;
|
||||
const [numerator, slash, denominator] = quotientNode.children;
|
||||
|
||||
assert.equal(text, tree.rootNode.text, 'root node text');
|
||||
assert.equal(denominatorSrc, denominator.text, 'denominator text');
|
||||
assert.equal(text, quotientNode.text, 'quotient text');
|
||||
assert.equal(numeratorSrc, numerator.text, 'numerator text');
|
||||
assert.equal('/', slash.text, '"/" text');
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
describe.skip('.descendantsOfType(type, min, max)', () => {
|
||||
it('finds all of the descendants of the given type in the given range', () => {
|
||||
tree = parser.parse("a + 1 * b * 2 + c + 3");
|
||||
const outerSum = tree.rootNode.firstChild.firstChild;
|
||||
let descendants = outerSum.descendantsOfType('number', {row: 0, column: 2}, {row: 0, column: 15})
|
||||
assert.deepEqual(
|
||||
descendants.map(node => node.startIndex),
|
||||
[4, 12]
|
||||
);
|
||||
|
||||
descendants = outerSum.descendantsOfType('identifier', {row: 0, column: 2}, {row: 0, column: 15})
|
||||
assert.deepEqual(
|
||||
descendants.map(node => node.startIndex),
|
||||
[8]
|
||||
);
|
||||
|
||||
descendants = outerSum.descendantsOfType('identifier', {row: 0, column: 0}, {row: 0, column: 30})
|
||||
assert.deepEqual(
|
||||
descendants.map(node => node.startIndex),
|
||||
[0, 8, 16]
|
||||
);
|
||||
|
||||
descendants = outerSum.descendantsOfType('number', {row: 0, column: 0}, {row: 0, column: 30})
|
||||
assert.deepEqual(
|
||||
descendants.map(node => node.startIndex),
|
||||
[4, 12, 20]
|
||||
);
|
||||
|
||||
descendants = outerSum.descendantsOfType(
|
||||
['identifier', 'number'],
|
||||
{row: 0, column: 0},
|
||||
{row: 0, column: 30}
|
||||
)
|
||||
assert.deepEqual(
|
||||
descendants.map(node => node.startIndex),
|
||||
[0, 4, 8, 12, 16, 20]
|
||||
);
|
||||
|
||||
descendants = outerSum.descendantsOfType('number')
|
||||
assert.deepEqual(
|
||||
descendants.map(node => node.startIndex),
|
||||
[4, 12, 20]
|
||||
);
|
||||
|
||||
descendants = outerSum.firstChild.descendantsOfType('number', {row: 0, column: 0}, {row: 0, column: 30})
|
||||
assert.deepEqual(
|
||||
descendants.map(node => node.startIndex),
|
||||
[4, 12]
|
||||
);
|
||||
})
|
||||
});
|
||||
|
||||
describe.skip('.closest(type)', () => {
|
||||
it('returns the closest ancestor of the given type', () => {
|
||||
tree = parser.parse("a(b + -d.e)");
|
||||
const property = tree.rootNode.descendantForIndex("a(b + -d.".length);
|
||||
assert.equal(property.type, 'property_identifier');
|
||||
|
||||
const unary = property.closest('unary_expression')
|
||||
assert.equal(unary.type, 'unary_expression')
|
||||
assert.equal(unary.startIndex, 'a(b + '.length)
|
||||
assert.equal(unary.endIndex, 'a(b + -d.e'.length)
|
||||
|
||||
const sum = property.closest(['binary_expression', 'call_expression'])
|
||||
assert.equal(sum.type, 'binary_expression')
|
||||
assert.equal(sum.startIndex, 2)
|
||||
assert.equal(sum.endIndex, 'a(b + -d.e'.length)
|
||||
});
|
||||
|
||||
it('throws an exception when an invalid argument is given', () => {
|
||||
tree = parser.parse("a + 1 * b * 2 + c + 3");
|
||||
const number = tree.rootNode.descendantForIndex(4)
|
||||
|
||||
assert.throws(() => number.closest({a: 1}), /Argument must be a string or array of strings/)
|
||||
});
|
||||
});
|
||||
});
|
||||
209
lib/binding_web/test/parser-test.js
Normal file
209
lib/binding_web/test/parser-test.js
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
const {assert} = require('chai');
|
||||
let Parser, JavaScript, languageURL;
|
||||
|
||||
describe("Parser", () => {
|
||||
let parser;
|
||||
|
||||
before(async () =>
|
||||
({Parser, JavaScript, languageURL} = await require('./helper'))
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
parser = new Parser();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
parser.delete()
|
||||
});
|
||||
|
||||
describe(".setLanguage", () => {
|
||||
it("allows setting the language to null", () => {
|
||||
assert.equal(parser.getLanguage(), null);
|
||||
parser.setLanguage(JavaScript);
|
||||
assert.equal(parser.getLanguage(), JavaScript);
|
||||
parser.setLanguage(null);
|
||||
assert.equal(parser.getLanguage(), null);
|
||||
});
|
||||
|
||||
it("throws an exception when the given object is not a tree-sitter language", () => {
|
||||
assert.throws(() => parser.setLanguage({}), /Argument must be a Language/);
|
||||
assert.throws(() => parser.setLanguage(1), /Argument must be a Language/);
|
||||
});
|
||||
});
|
||||
|
||||
describe(".setLogger", () => {
|
||||
beforeEach(() => {
|
||||
parser.setLanguage(JavaScript)
|
||||
});
|
||||
|
||||
it("calls the given callback for each parse event", () => {
|
||||
const debugMessages = [];
|
||||
parser.setLogger((message) => debugMessages.push(message));
|
||||
parser.parse("a + b + c");
|
||||
assert.includeMembers(debugMessages, [
|
||||
"skip character:' '",
|
||||
"consume character:'b'",
|
||||
"reduce sym:program, child_count:1",
|
||||
"accept"
|
||||
]);
|
||||
});
|
||||
|
||||
it("allows the callback to be retrieved later", () => {
|
||||
const callback = () => {}
|
||||
parser.setLogger(callback);
|
||||
assert.equal(parser.getLogger(), callback);
|
||||
parser.setLogger(false);
|
||||
assert.equal(parser.getLogger(), null);
|
||||
});
|
||||
|
||||
it("disables debugging when given a falsy value", () => {
|
||||
const debugMessages = [];
|
||||
parser.setLogger((message) => debugMessages.push(message));
|
||||
parser.setLogger(false);
|
||||
parser.parse("a + b * c");
|
||||
assert.equal(debugMessages.length, 0);
|
||||
});
|
||||
|
||||
it("throws an error when given a truthy value that isn't a function ", () => {
|
||||
assert.throws(
|
||||
() => parser.setLogger("5"),
|
||||
"Logger callback must be a function"
|
||||
);
|
||||
});
|
||||
|
||||
it("rethrows errors thrown by the logging callback", () => {
|
||||
const error = new Error("The error message");
|
||||
parser.setLogger((msg, params) => { throw error; });
|
||||
assert.throws(
|
||||
() => parser.parse("ok;"),
|
||||
"The error message"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe(".parse", () => {
|
||||
let tree;
|
||||
|
||||
beforeEach(() => {
|
||||
tree = null;
|
||||
parser.setLanguage(JavaScript)
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (tree) tree.delete();
|
||||
});
|
||||
|
||||
it("reads from the given input", () => {
|
||||
const parts = ["first", "_", "second", "_", "third"];
|
||||
tree = parser.parse(() => parts.shift());
|
||||
assert.equal(tree.rootNode.toString(), "(program (expression_statement (identifier)))");
|
||||
});
|
||||
|
||||
it("stops reading when the input callback return something that's not a string", () => {
|
||||
const parts = ["abc", "def", "ghi", {}, {}, {}, "second-word", " "];
|
||||
tree = parser.parse(() => parts.shift());
|
||||
assert.equal(
|
||||
tree.rootNode.toString(),
|
||||
"(program (expression_statement (identifier)))"
|
||||
);
|
||||
assert.equal(tree.rootNode.endIndex, 9);
|
||||
assert.equal(parts.length, 2);
|
||||
});
|
||||
|
||||
it("throws an exception when the given input is not a function", () => {
|
||||
assert.throws(() => parser.parse(null), "Argument must be a string or a function");
|
||||
assert.throws(() => parser.parse(5), "Argument must be a string or a function");
|
||||
assert.throws(() => parser.parse({}), "Argument must be a string or a function");
|
||||
});
|
||||
|
||||
it("handles long input strings", () => {
|
||||
const repeatCount = 10000;
|
||||
const inputString = "[" + "0,".repeat(repeatCount) + "]";
|
||||
|
||||
tree = parser.parse(inputString);
|
||||
assert.equal(tree.rootNode.type, "program");
|
||||
assert.equal(tree.rootNode.firstChild.firstChild.namedChildCount, repeatCount);
|
||||
}).timeout(5000);
|
||||
|
||||
it("can use the bash parser", async () => {
|
||||
parser.setLanguage(await Parser.Language.load(languageURL('bash')));
|
||||
tree = parser.parse("FOO=bar echo <<EOF 2> err.txt > hello.txt \nhello\nEOF");
|
||||
assert.equal(
|
||||
tree.rootNode.toString(),
|
||||
'(program (redirected_statement (command ' +
|
||||
'(variable_assignment (variable_name) (word)) ' +
|
||||
'(command_name (word))) ' +
|
||||
'(heredoc_redirect (heredoc_start)) ' +
|
||||
'(file_redirect (file_descriptor) (word)) ' +
|
||||
'(file_redirect (word))) ' +
|
||||
'(heredoc_body))'
|
||||
);
|
||||
}).timeout(5000);
|
||||
|
||||
it("can use the c++ parser", async () => {
|
||||
parser.setLanguage(await Parser.Language.load(languageURL('cpp')));
|
||||
tree = parser.parse("const char *s = R\"EOF(HELLO WORLD)EOF\";");
|
||||
assert.equal(
|
||||
tree.rootNode.toString(),
|
||||
'(translation_unit (declaration (type_qualifier) (primitive_type) (init_declarator (pointer_declarator (identifier)) (raw_string_literal))))'
|
||||
);
|
||||
}).timeout(5000);
|
||||
|
||||
it("can use the HTML parser", async () => {
|
||||
parser.setLanguage(await Parser.Language.load(languageURL('html')));
|
||||
tree = parser.parse("<div><span><custom></custom></span></div>");
|
||||
assert.equal(
|
||||
tree.rootNode.toString(),
|
||||
'(fragment (element (start_tag (tag_name)) (element (start_tag (tag_name)) (element (start_tag (tag_name)) (end_tag (tag_name))) (end_tag (tag_name))) (end_tag (tag_name))))'
|
||||
);
|
||||
}).timeout(5000);
|
||||
|
||||
it("can use the python parser", async () => {
|
||||
parser.setLanguage(await Parser.Language.load(languageURL('python')));
|
||||
tree = parser.parse("class A:\n def b():\n c()");
|
||||
assert.equal(
|
||||
tree.rootNode.toString(),
|
||||
'(module (class_definition (identifier) (function_definition (identifier) (parameters) (expression_statement (call (identifier) (argument_list))))))'
|
||||
);
|
||||
}).timeout(5000);
|
||||
|
||||
it("can use the rust parser", async () => {
|
||||
parser.setLanguage(await Parser.Language.load(languageURL('rust')));
|
||||
tree = parser.parse("const x: &'static str = r###\"hello\"###;");
|
||||
assert.equal(
|
||||
tree.rootNode.toString(),
|
||||
'(source_file (const_item (identifier) (reference_type (lifetime (identifier)) (primitive_type)) (raw_string_literal)))'
|
||||
);
|
||||
}).timeout(5000);
|
||||
|
||||
it('parses only the text within the `includedRanges` if they are specified', () => {
|
||||
const sourceCode = "<% foo() %> <% bar %>";
|
||||
|
||||
const start1 = sourceCode.indexOf('foo');
|
||||
const end1 = start1 + 5
|
||||
const start2 = sourceCode.indexOf('bar');
|
||||
const end2 = start2 + 3
|
||||
|
||||
const tree = parser.parse(sourceCode, null, {
|
||||
includedRanges: [
|
||||
{
|
||||
startIndex: start1,
|
||||
endIndex: end1,
|
||||
startPosition: {row: 0, column: start1},
|
||||
endPosition: {row: 0, column: end1}
|
||||
},
|
||||
{
|
||||
startIndex: start2,
|
||||
endIndex: end2,
|
||||
startPosition: {row: 0, column: start2},
|
||||
endPosition: {row: 0, column: end2}
|
||||
},
|
||||
]
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
tree.rootNode.toString(),
|
||||
'(program (expression_statement (call_expression (identifier) (arguments))) (expression_statement (identifier)))'
|
||||
);
|
||||
})
|
||||
});});
|
||||
291
lib/binding_web/test/tree-test.js
Normal file
291
lib/binding_web/test/tree-test.js
Normal file
|
|
@ -0,0 +1,291 @@
|
|||
const {assert} = require('chai');
|
||||
let Parser, JavaScript;
|
||||
|
||||
describe("Tree", () => {
|
||||
let parser, tree;
|
||||
|
||||
before(async () =>
|
||||
({Parser, JavaScript} = await require('./helper'))
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
parser = new Parser().setLanguage(JavaScript);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
parser.delete();
|
||||
tree.delete();
|
||||
});
|
||||
|
||||
describe('.edit', () => {
|
||||
let input, edit
|
||||
|
||||
it('updates the positions of nodes', () => {
|
||||
input = 'abc + cde';
|
||||
tree = parser.parse(input);
|
||||
assert.equal(
|
||||
tree.rootNode.toString(),
|
||||
"(program (expression_statement (binary_expression (identifier) (identifier))))"
|
||||
);
|
||||
|
||||
let sumNode = tree.rootNode.firstChild.firstChild;
|
||||
let variableNode1 = sumNode.firstChild;
|
||||
let variableNode2 = sumNode.lastChild;
|
||||
assert.equal(variableNode1.startIndex, 0);
|
||||
assert.equal(variableNode1.endIndex, 3);
|
||||
assert.equal(variableNode2.startIndex, 6);
|
||||
assert.equal(variableNode2.endIndex, 9);
|
||||
|
||||
([input, edit] = spliceInput(input, input.indexOf('bc'), 0, ' * '));
|
||||
assert.equal(input, 'a * bc + cde');
|
||||
tree.edit(edit);
|
||||
|
||||
sumNode = tree.rootNode.firstChild.firstChild;
|
||||
variableNode1 = sumNode.firstChild;
|
||||
variableNode2 = sumNode.lastChild;
|
||||
assert.equal(variableNode1.startIndex, 0);
|
||||
assert.equal(variableNode1.endIndex, 6);
|
||||
assert.equal(variableNode2.startIndex, 9);
|
||||
assert.equal(variableNode2.endIndex, 12);
|
||||
|
||||
tree = parser.parse(input, tree);
|
||||
assert.equal(
|
||||
tree.rootNode.toString(),
|
||||
"(program (expression_statement (binary_expression (binary_expression (identifier) (identifier)) (identifier))))"
|
||||
);
|
||||
});
|
||||
|
||||
it("handles non-ascii characters", () => {
|
||||
input = 'αβδ + cde';
|
||||
|
||||
tree = parser.parse(input);
|
||||
assert.equal(
|
||||
tree.rootNode.toString(),
|
||||
"(program (expression_statement (binary_expression (identifier) (identifier))))"
|
||||
);
|
||||
|
||||
let variableNode = tree.rootNode.firstChild.firstChild.lastChild;
|
||||
|
||||
([input, edit] = spliceInput(input, input.indexOf('δ'), 0, '👍 * '));
|
||||
assert.equal(input, 'αβ👍 * δ + cde');
|
||||
tree.edit(edit);
|
||||
|
||||
variableNode = tree.rootNode.firstChild.firstChild.lastChild;
|
||||
assert.equal(variableNode.startIndex, input.indexOf('cde'));
|
||||
|
||||
tree = parser.parse(input, tree);
|
||||
assert.equal(
|
||||
tree.rootNode.toString(),
|
||||
"(program (expression_statement (binary_expression (binary_expression (identifier) (identifier)) (identifier))))"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe(".walk()", () => {
|
||||
let cursor
|
||||
|
||||
afterEach(() => {
|
||||
cursor.delete();
|
||||
})
|
||||
|
||||
it('returns a cursor that can be used to walk the tree', () => {
|
||||
tree = parser.parse('a * b + c / d');
|
||||
cursor = tree.walk();
|
||||
|
||||
assertCursorState(cursor, {
|
||||
nodeType: 'program',
|
||||
nodeIsNamed: true,
|
||||
startPosition: {row: 0, column: 0},
|
||||
endPosition: {row: 0, column: 13},
|
||||
startIndex: 0,
|
||||
endIndex: 13
|
||||
});
|
||||
|
||||
assert(cursor.gotoFirstChild());
|
||||
assertCursorState(cursor, {
|
||||
nodeType: 'expression_statement',
|
||||
nodeIsNamed: true,
|
||||
startPosition: {row: 0, column: 0},
|
||||
endPosition: {row: 0, column: 13},
|
||||
startIndex: 0,
|
||||
endIndex: 13
|
||||
});
|
||||
|
||||
assert(cursor.gotoFirstChild());
|
||||
assertCursorState(cursor, {
|
||||
nodeType: 'binary_expression',
|
||||
nodeIsNamed: true,
|
||||
startPosition: {row: 0, column: 0},
|
||||
endPosition: {row: 0, column: 13},
|
||||
startIndex: 0,
|
||||
endIndex: 13
|
||||
});
|
||||
|
||||
assert(cursor.gotoFirstChild());
|
||||
assertCursorState(cursor, {
|
||||
nodeType: 'binary_expression',
|
||||
nodeIsNamed: true,
|
||||
startPosition: {row: 0, column: 0},
|
||||
endPosition: {row: 0, column: 5},
|
||||
startIndex: 0,
|
||||
endIndex: 5
|
||||
});
|
||||
|
||||
assert(cursor.gotoFirstChild());
|
||||
assertCursorState(cursor, {
|
||||
nodeType: 'identifier',
|
||||
nodeIsNamed: true,
|
||||
startPosition: {row: 0, column: 0},
|
||||
endPosition: {row: 0, column: 1},
|
||||
startIndex: 0,
|
||||
endIndex: 1
|
||||
});
|
||||
|
||||
assert(!cursor.gotoFirstChild())
|
||||
assert(cursor.gotoNextSibling());
|
||||
assertCursorState(cursor, {
|
||||
nodeType: '*',
|
||||
nodeIsNamed: false,
|
||||
startPosition: {row: 0, column: 2},
|
||||
endPosition: {row: 0, column: 3},
|
||||
startIndex: 2,
|
||||
endIndex: 3
|
||||
});
|
||||
|
||||
assert(cursor.gotoNextSibling());
|
||||
assertCursorState(cursor, {
|
||||
nodeType: 'identifier',
|
||||
nodeIsNamed: true,
|
||||
startPosition: {row: 0, column: 4},
|
||||
endPosition: {row: 0, column: 5},
|
||||
startIndex: 4,
|
||||
endIndex: 5
|
||||
});
|
||||
|
||||
assert(!cursor.gotoNextSibling());
|
||||
assert(cursor.gotoParent());
|
||||
assertCursorState(cursor, {
|
||||
nodeType: 'binary_expression',
|
||||
nodeIsNamed: true,
|
||||
startPosition: {row: 0, column: 0},
|
||||
endPosition: {row: 0, column: 5},
|
||||
startIndex: 0,
|
||||
endIndex: 5
|
||||
});
|
||||
|
||||
assert(cursor.gotoNextSibling());
|
||||
assertCursorState(cursor, {
|
||||
nodeType: '+',
|
||||
nodeIsNamed: false,
|
||||
startPosition: {row: 0, column: 6},
|
||||
endPosition: {row: 0, column: 7},
|
||||
startIndex: 6,
|
||||
endIndex: 7
|
||||
});
|
||||
|
||||
assert(cursor.gotoNextSibling());
|
||||
assertCursorState(cursor, {
|
||||
nodeType: 'binary_expression',
|
||||
nodeIsNamed: true,
|
||||
startPosition: {row: 0, column: 8},
|
||||
endPosition: {row: 0, column: 13},
|
||||
startIndex: 8,
|
||||
endIndex: 13
|
||||
});
|
||||
|
||||
// const childIndex = cursor.gotoFirstChildForIndex(12);
|
||||
// assertCursorState(cursor, {
|
||||
// nodeType: 'identifier',
|
||||
// nodeIsNamed: true,
|
||||
// startPosition: {row: 0, column: 12},
|
||||
// endPosition: {row: 0, column: 13},
|
||||
// startIndex: 12,
|
||||
// endIndex: 13
|
||||
// });
|
||||
// assert.equal(childIndex, 2);
|
||||
// assert(!cursor.gotoNextSibling());
|
||||
// assert(cursor.gotoParent());
|
||||
|
||||
assert(cursor.gotoParent());
|
||||
assert.equal(cursor.nodeType, 'binary_expression')
|
||||
assert(cursor.gotoParent());
|
||||
assert.equal(cursor.nodeType, 'expression_statement')
|
||||
assert(cursor.gotoParent());
|
||||
assert.equal(cursor.nodeType, 'program')
|
||||
assert(!cursor.gotoParent());
|
||||
});
|
||||
|
||||
it('returns a cursor that can be reset anywhere in the tree', () => {
|
||||
tree = parser.parse('a * b + c / d');
|
||||
cursor = tree.walk();
|
||||
const root = tree.rootNode.firstChild;
|
||||
|
||||
cursor.reset(root.firstChild.firstChild);
|
||||
assertCursorState(cursor, {
|
||||
nodeType: 'binary_expression',
|
||||
nodeIsNamed: true,
|
||||
startPosition: {row: 0, column: 0},
|
||||
endPosition: {row: 0, column: 5},
|
||||
startIndex: 0,
|
||||
endIndex: 5
|
||||
});
|
||||
|
||||
cursor.gotoFirstChild()
|
||||
assertCursorState(cursor, {
|
||||
nodeType: 'identifier',
|
||||
nodeIsNamed: true,
|
||||
startPosition: {row: 0, column: 0},
|
||||
endPosition: {row: 0, column: 1},
|
||||
startIndex: 0,
|
||||
endIndex: 1
|
||||
});
|
||||
|
||||
assert(cursor.gotoParent());
|
||||
assert(!cursor.gotoParent());
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
function spliceInput(input, startIndex, lengthRemoved, newText) {
|
||||
const oldEndIndex = startIndex + lengthRemoved;
|
||||
const newEndIndex = startIndex + newText.length;
|
||||
const startPosition = getExtent(input.slice(0, startIndex));
|
||||
const oldEndPosition = getExtent(input.slice(0, oldEndIndex));
|
||||
input = input.slice(0, startIndex) + newText + input.slice(oldEndIndex);
|
||||
const newEndPosition = getExtent(input.slice(0, newEndIndex));
|
||||
return [
|
||||
input,
|
||||
{
|
||||
startIndex, startPosition,
|
||||
oldEndIndex, oldEndPosition,
|
||||
newEndIndex, newEndPosition
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
function getExtent(text) {
|
||||
let row = 0
|
||||
let index;
|
||||
for (index = 0; index != -1; index = text.indexOf('\n', index)) {
|
||||
index++
|
||||
row++;
|
||||
}
|
||||
return {row, column: text.length - index};
|
||||
}
|
||||
|
||||
function assertCursorState(cursor, params) {
|
||||
assert.equal(cursor.nodeType, params.nodeType);
|
||||
assert.equal(cursor.nodeIsNamed, params.nodeIsNamed);
|
||||
assert.deepEqual(cursor.startPosition, params.startPosition);
|
||||
assert.deepEqual(cursor.endPosition, params.endPosition);
|
||||
assert.deepEqual(cursor.startIndex, params.startIndex);
|
||||
assert.deepEqual(cursor.endIndex, params.endIndex);
|
||||
|
||||
const node = cursor.currentNode()
|
||||
assert.equal(node.type, params.nodeType);
|
||||
assert.equal(node.isNamed(), params.nodeIsNamed);
|
||||
assert.deepEqual(node.startPosition, params.startPosition);
|
||||
assert.deepEqual(node.endPosition, params.endPosition);
|
||||
assert.deepEqual(node.startIndex, params.startIndex);
|
||||
assert.deepEqual(node.endIndex, params.endIndex);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue