Merge branch 'master' into wasm-language

This commit is contained in:
Max Brunsfeld 2023-10-27 11:57:04 +01:00
commit f4e2f68f14
161 changed files with 10293 additions and 4253 deletions

View file

@ -1,16 +1,18 @@
Web Tree-sitter
===============
# Web Tree-sitter
[![Build Status](https://travis-ci.org/tree-sitter/tree-sitter.svg?branch=master)](https://travis-ci.org/tree-sitter/tree-sitter)
[![npmjs.com badge]][npmjs.com]
[npmjs.com]: https://www.npmjs.org/package/web-tree-sitter
[npmjs.com badge]: https://img.shields.io/npm/v/web-tree-sitter.svg?color=%23BF4A4A
WebAssembly bindings to the [Tree-sitter](https://github.com/tree-sitter/tree-sitter) parsing library.
### Setup
You can download the the `tree-sitter.js` and `tree-sitter.wasm` files from [the latest GitHub release](https://github.com/tree-sitter/tree-sitter/releases/latest) and load them using a standalone script:
You can download the `tree-sitter.js` and `tree-sitter.wasm` files from [the latest GitHub release](https://github.com/tree-sitter/tree-sitter/releases/latest) and load them using a standalone script:
```html
<script src="/the/path/to/tree-sitter.js"/>
<script src="/the/path/to/tree-sitter.js"></script>
<script>
const Parser = window.TreeSitter;
@ -118,7 +120,7 @@ First install `tree-sitter-cli` and the tree-sitter language for which to genera
npm install --save-dev tree-sitter-cli tree-sitter-javascript
```
Then just use tree-sitter cli tool to generate the `.wasm`.
Then just use tree-sitter cli tool to generate the `.wasm`.
```sh
npx tree-sitter build-wasm node_modules/tree-sitter-javascript
@ -149,13 +151,13 @@ const Parser = require('web-tree-sitter');
##### Loading the .wasm file
`web-tree-sitter` needs to load the `tree-sitter.wasm` file. By default, it assumes that this file is available in the
`web-tree-sitter` needs to load the `tree-sitter.wasm` file. By default, it assumes that this file is available in the
same path as the JavaScript code. Therefore, if the code is being served from `http://localhost:3000/bundle.js`, then
the wasm file should be at `http://localhost:3000/tree-sitter.wasm`.
For server side frameworks like NextJS, this can be tricky as pages are often served from a path such as
For server side frameworks like NextJS, this can be tricky as pages are often served from a path such as
`http://localhost:3000/_next/static/chunks/pages/index.js`. The loader will therefore look for the wasm file at
`http://localhost:3000/_next/static/chunks/pages/tree-sitter.wasm`. The solution is to pass a `locateFile` function in
`http://localhost:3000/_next/static/chunks/pages/tree-sitter.wasm`. The solution is to pass a `locateFile` function in
the `moduleOptions` argument to `Parser.init()`:
```javascript
@ -166,15 +168,15 @@ await Parser.init({
});
```
`locateFile` takes in two parameters, `scriptName`, i.e. the wasm file name, and `scriptDirectory`, i.e. the directory
`locateFile` takes in two parameters, `scriptName`, i.e. the wasm file name, and `scriptDirectory`, i.e. the directory
where the loader expects the script to be. It returns the path where the loader will look for the wasm file. In the NextJS
case, we want to return just the `scriptName` so that the loader will look at `http://localhost:3000/tree-sitter.wasm`
and not `http://localhost:3000/_next/static/chunks/pages/tree-sitter.wasm`.
##### `Can't resolve 'fs' in 'node_modules/web-tree-sitter'`
Most bundlers will notice that the `tree-sitter.js` file is attempting to import `fs`, i.e. node's file system library.
Since this doesn't exist in the browser, the bundlers will get confused. For webpack you can fix this by adding the
Most bundlers will notice that the `tree-sitter.js` file is attempting to import `fs`, i.e. node's file system library.
Since this doesn't exist in the browser, the bundlers will get confused. For webpack you can fix this by adding the
following to your webpack config:
```javascript

View file

@ -243,6 +243,13 @@ void ts_tree_cursor_reset_wasm(const TSTree *tree) {
marshal_cursor(&cursor);
}
void ts_tree_cursor_reset_to_wasm(const TSTree *_dst, const TSTree *_src) {
TSTreeCursor cursor = unmarshal_cursor(TRANSFER_BUFFER, _dst);
TSTreeCursor src = unmarshal_cursor(&TRANSFER_BUFFER[3], _src);
ts_tree_cursor_reset_to(&cursor, &src);
marshal_cursor(&cursor);
}
bool ts_tree_cursor_goto_first_child_wasm(const TSTree *tree) {
TSTreeCursor cursor = unmarshal_cursor(TRANSFER_BUFFER, tree);
bool result = ts_tree_cursor_goto_first_child(&cursor);
@ -250,6 +257,13 @@ bool ts_tree_cursor_goto_first_child_wasm(const TSTree *tree) {
return result;
}
bool ts_tree_cursor_goto_last_child_wasm(const TSTree *tree) {
TSTreeCursor cursor = unmarshal_cursor(TRANSFER_BUFFER, tree);
bool result = ts_tree_cursor_goto_last_child(&cursor);
marshal_cursor(&cursor);
return result;
}
bool ts_tree_cursor_goto_next_sibling_wasm(const TSTree *tree) {
TSTreeCursor cursor = unmarshal_cursor(TRANSFER_BUFFER, tree);
bool result = ts_tree_cursor_goto_next_sibling(&cursor);
@ -257,6 +271,13 @@ bool ts_tree_cursor_goto_next_sibling_wasm(const TSTree *tree) {
return result;
}
bool ts_tree_cursor_goto_previous_sibling_wasm(const TSTree *tree) {
TSTreeCursor cursor = unmarshal_cursor(TRANSFER_BUFFER, tree);
bool result = ts_tree_cursor_goto_previous_sibling(&cursor);
marshal_cursor(&cursor);
return result;
}
bool ts_tree_cursor_goto_parent_wasm(const TSTree *tree) {
TSTreeCursor cursor = unmarshal_cursor(TRANSFER_BUFFER, tree);
bool result = ts_tree_cursor_goto_parent(&cursor);
@ -270,6 +291,12 @@ uint16_t ts_tree_cursor_current_node_type_id_wasm(const TSTree *tree) {
return ts_node_symbol(node);
}
uint16_t ts_tree_cursor_current_node_state_id_wasm(const TSTree *tree) {
TSTreeCursor cursor = unmarshal_cursor(TRANSFER_BUFFER, tree);
TSNode node = ts_tree_cursor_current_node(&cursor);
return ts_node_parse_state(node);
}
bool ts_tree_cursor_current_node_is_named_wasm(const TSTree *tree) {
TSTreeCursor cursor = unmarshal_cursor(TRANSFER_BUFFER, tree);
TSNode node = ts_tree_cursor_current_node(&cursor);
@ -334,6 +361,16 @@ uint16_t ts_node_symbol_wasm(const TSTree *tree) {
return ts_node_symbol(node);
}
const char *ts_node_field_name_for_child_wasm(const TSTree *tree, uint32_t index) {
TSNode node = unmarshal_node(tree);
return ts_node_field_name_for_child(node, index);
}
uint16_t ts_node_grammar_symbol_wasm(const TSTree *tree) {
TSNode node = unmarshal_node(tree);
return ts_node_grammar_symbol(node);
}
uint32_t ts_node_child_count_wasm(const TSTree *tree) {
TSNode node = unmarshal_node(tree);
return ts_node_child_count(node);
@ -579,11 +616,26 @@ int ts_node_has_error_wasm(const TSTree *tree) {
return ts_node_has_error(node);
}
int ts_node_is_error_wasm(const TSTree *tree) {
TSNode node = unmarshal_node(tree);
return ts_node_is_error(node);
}
int ts_node_is_missing_wasm(const TSTree *tree) {
TSNode node = unmarshal_node(tree);
return ts_node_is_missing(node);
}
uint16_t ts_node_parse_state_wasm(const TSTree *tree) {
TSNode node = unmarshal_node(tree);
return ts_node_parse_state(node);
}
uint16_t ts_node_next_parse_state_wasm(const TSTree *tree) {
TSNode node = unmarshal_node(tree);
return ts_node_next_parse_state(node);
}
/******************/
/* Section - Query */
/******************/

View file

@ -1,6 +1,7 @@
const C = Module;
const INTERNAL = {};
const SIZE_OF_INT = 4;
const SIZE_OF_CURSOR = 3 * SIZE_OF_INT;
const SIZE_OF_NODE = 5 * SIZE_OF_INT;
const SIZE_OF_POINT = 2 * SIZE_OF_INT;
const SIZE_OF_RANGE = 2 * SIZE_OF_INT + 2 * SIZE_OF_POINT;
@ -208,10 +209,19 @@ class Node {
return C._ts_node_symbol_wasm(this.tree[0]);
}
get grammarId() {
marshalNode(this);
return C._ts_node_grammar_symbol_wasm(this.tree[0]);
}
get type() {
return this.tree.language.types[this.typeId] || 'ERROR';
}
get grammarType() {
return this.tree.language.types[this.grammarId] || 'ERROR';
}
get endPosition() {
marshalNode(this);
C._ts_node_end_point_wasm(this.tree[0]);
@ -227,6 +237,16 @@ class Node {
return getText(this.tree, this.startIndex, this.endIndex);
}
get parseState() {
marshalNode(this);
return C._ts_node_parse_state_wasm(this.tree[0]);
}
get nextParseState() {
marshalNode(this);
return C._ts_node_next_parse_state_wasm(this.tree[0]);
}
isNamed() {
marshalNode(this);
return C._ts_node_is_named_wasm(this.tree[0]) === 1;
@ -242,6 +262,11 @@ class Node {
return C._ts_node_has_changes_wasm(this.tree[0]) === 1;
}
isError() {
marshalNode(this);
return C._ts_node_is_error_wasm(this.tree[0]) === 1;
}
isMissing() {
marshalNode(this);
return C._ts_node_is_missing_wasm(this.tree[0]) === 1;
@ -257,6 +282,17 @@ class Node {
return unmarshalNode(this.tree);
}
fieldNameForChild(index) {
marshalNode(this);
const address = C._ts_node_field_name_for_child_wasm(this.tree[0], index);
if (!address) {
return null;
}
const result = AsciiToString(address);
// must not free, the string memory is owned by the language
return result;
}
namedChild(index) {
marshalNode(this);
C._ts_node_named_child_wasm(this.tree[0], index);
@ -505,6 +541,13 @@ class TreeCursor {
unmarshalTreeCursor(this);
}
resetTo(cursor) {
marshalTreeCursor(this, TRANSFER_BUFFER);
marshalTreeCursor(cursor, TRANSFER_BUFFER + SIZE_OF_CURSOR);
C._ts_tree_cursor_reset_to_wasm(this.tree[0], cursor.tree[0]);
unmarshalTreeCursor(this);
}
get nodeType() {
return this.tree.language.types[this.nodeTypeId] || 'ERROR';
}
@ -514,6 +557,11 @@ class TreeCursor {
return C._ts_tree_cursor_current_node_type_id_wasm(this.tree[0]);
}
get nodeStateId() {
marshalTreeCursor(this);
return C._ts_tree_cursor_current_node_state_id_wasm(this.tree[0]);
}
get nodeId() {
marshalTreeCursor(this);
return C._ts_tree_cursor_current_node_id_wasm(this.tree[0]);
@ -580,6 +628,13 @@ class TreeCursor {
return result === 1;
}
gotoLastChild() {
marshalTreeCursor(this);
const result = C._ts_tree_cursor_goto_last_child_wasm(this.tree[0]);
unmarshalTreeCursor(this);
return result === 1;
}
gotoNextSibling() {
marshalTreeCursor(this);
const result = C._ts_tree_cursor_goto_next_sibling_wasm(this.tree[0]);
@ -587,6 +642,13 @@ class TreeCursor {
return result === 1;
}
gotoPreviousSibling() {
marshalTreeCursor(this);
const result = C._ts_tree_cursor_goto_previous_sibling_wasm(this.tree[0]);
unmarshalTreeCursor(this);
return result === 1;
}
gotoParent() {
marshalTreeCursor(this);
const result = C._ts_tree_cursor_goto_parent_wasm(this.tree[0]);
@ -624,6 +686,10 @@ class Language {
return this.fields.length - 1;
}
get stateCount() {
return C._ts_language_state_count(this[0]);
}
fieldIdForName(fieldName) {
const result = this.fields.indexOf(fieldName);
if (result !== -1) {
@ -663,6 +729,15 @@ class Language {
return C._ts_language_type_is_visible_wasm(this[0], typeId) ? true : false;
}
nextState(stateId, typeId) {
return C._ts_language_next_state(this[0], stateId, typeId);
}
lookaheadIterator(stateId) {
const address = C._ts_lookahead_iterator_new(this[0], stateId);
if (address) return new LookaheadIterable(INTERNAL, address, this);
}
query(source) {
const sourceLength = lengthBytesUTF8(source);
const sourceAddress = C._malloc(sourceLength + 1);
@ -766,7 +841,13 @@ class Language {
}
const operator = steps[0].value;
let isPositive = true;
let matchAll = true;
switch (operator) {
case 'any-not-eq?':
isPositive = false;
matchAll = false;
case 'any-eq?':
matchAll = false;
case 'not-eq?':
isPositive = false;
case 'eq?':
@ -780,28 +861,36 @@ class Language {
const captureName1 = steps[1].name;
const captureName2 = steps[2].name;
textPredicates[i].push(function(captures) {
let node1, node2
let nodes_1 = [];
let nodes_2 = [];
for (const c of captures) {
if (c.name === captureName1) node1 = c.node;
if (c.name === captureName2) node2 = c.node;
if (c.name === captureName1) nodes_1.push(c.node);
if (c.name === captureName2) nodes_2.push(c.node);
}
if(node1 === undefined || node2 === undefined) return true;
return (node1.text === node2.text) === isPositive;
return matchAll
? nodes_1.every(n1 => nodes_2.some(n2 => n1.text === n2.text)) === isPositive
: nodes_1.some(n1 => nodes_2.some(n2 => n1.text === n2.text)) === isPositive;
});
} else {
const captureName = steps[1].name;
const stringValue = steps[2].value;
textPredicates[i].push(function(captures) {
let nodes = [];
for (const c of captures) {
if (c.name === captureName) {
return (c.node.text === stringValue) === isPositive;
};
if (c.name === captureName) nodes.push(c.node);
}
return true;
return matchAll
? nodes.every(n => n.text === stringValue) === isPositive
: nodes.some(n => n.text === stringValue) === isPositive;
});
}
break;
case 'not-any-match?':
isPositive = false;
matchAll = false;
case 'any-match?':
matchAll = false;
case 'not-match?':
isPositive = false;
case 'match?':
@ -817,10 +906,14 @@ class Language {
const captureName = steps[1].name;
const regex = new RegExp(steps[2].value);
textPredicates[i].push(function(captures) {
const nodes = [];
for (const c of captures) {
if (c.name === captureName) return regex.test(c.node.text) === isPositive;
if (c.name === captureName) nodes.push(c.node.text);
}
return true;
if (nodes.length === 0) return !isPositive;
return matchAll
? nodes.every(text => regex.test(text)) === isPositive
: nodes.some(text => regex.test(text)) === isPositive;
});
break;
@ -848,6 +941,32 @@ class Language {
properties[i][steps[1].value] = steps[2] ? steps[2].value : null;
break;
case 'not-any-of?':
isPositive = false;
case 'any-of?':
if (steps.length < 2) throw new Error(
`Wrong number of arguments to \`#${operator}\` predicate. Expected at least 1. Got ${steps.length - 1}.`
);
if (steps[1].type !== 'capture') throw new Error(
`First argument of \`#${operator}\` predicate must be a capture. Got "${steps[1].value}".`
);
for (let i = 2; i < steps.length; i++) {
if (steps[i].type !== 'string') throw new Error(
`Arguments to \`#${operator}\` predicate must be a strings.".`
);
}
captureName = steps[1].name;
const values = steps.slice(2).map(s => s.value);
textPredicates[i].push(function(captures) {
const nodes = [];
for (const c of captures) {
if (c.name === captureName) nodes.push(c.node.text);
}
if (nodes.length === 0) return !isPositive;
return nodes.every(text => values.includes(text)) === isPositive;
});
break;
default:
predicates[i].push({operator, operands: steps.slice(1)});
}
@ -918,6 +1037,53 @@ class Language {
}
}
class LookaheadIterable {
constructor(internal, address, language) {
assertInternal(internal);
this[0] = address;
this.language = language;
}
get currentTypeId() {
return C._ts_lookahead_iterator_current_symbol(this[0]);
}
get currentType() {
return this.language.types[this.currentTypeId] || 'ERROR'
}
delete() {
C._ts_lookahead_iterator_delete(this[0]);
this[0] = 0;
}
resetState(stateId) {
return C._ts_lookahead_iterator_reset_state(this[0], stateId);
}
reset(language, stateId) {
if (C._ts_lookahead_iterator_reset(this[0], language[0], stateId)) {
this.language = language;
return true;
}
return false;
}
[Symbol.iterator]() {
const self = this;
return {
next() {
if (C._ts_lookahead_iterator_next(self[0])) {
return { done: false, value: self.currentType };
}
return { done: true, value: "" };
}
};
}
}
class Query {
constructor(
internal, address, captureNames, textPredicates, predicates,

View file

@ -4,22 +4,27 @@
"_malloc",
"_realloc",
"__ZNKSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE4copyEPcmm",
"__ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6__initEPKcm",
"__ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE7reserveEm",
"__ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE9__grow_byEmmmmmm",
"__ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE9push_backEc",
"__ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEED2Ev",
"__ZNSt3__212basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE9push_backEw",
"__ZNKSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE4copyEPcmm",
"__ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE7reserveEm",
"__ZNSt3__212basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE6resizeEmw",
"__ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEED2Ev",
"__ZNSt3__212basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEED2Ev",
"__ZdlPv",
"__Znwm",
"___cxa_atexit",
"_abort",
"_isalpha",
"_isspace",
"_iswalnum",
"_iswalpha",
"_iswblank",
"_iswdigit",
"_iswlower",
"_iswupper",
"_iswspace",
"_memchr",
"_memcmp",
@ -27,7 +32,12 @@
"_memmove",
"_memset",
"_strlen",
"_strcmp",
"_strncpy",
"_tolower",
"_towlower",
"_towupper",
"_stderr",
"_ts_init",
"_ts_language_field_count",
@ -35,10 +45,13 @@
"_ts_language_type_is_named_wasm",
"_ts_language_type_is_visible_wasm",
"_ts_language_symbol_count",
"_ts_language_state_count",
"_ts_language_symbol_for_name",
"_ts_language_symbol_name",
"_ts_language_symbol_type",
"_ts_language_version",
"_ts_language_next_state",
"_ts_node_field_name_for_child_wasm",
"_ts_node_child_by_field_id_wasm",
"_ts_node_child_count_wasm",
"_ts_node_child_wasm",
@ -50,8 +63,11 @@
"_ts_node_end_point_wasm",
"_ts_node_has_changes_wasm",
"_ts_node_has_error_wasm",
"_ts_node_is_error_wasm",
"_ts_node_is_missing_wasm",
"_ts_node_is_named_wasm",
"_ts_node_parse_state_wasm",
"_ts_node_next_parse_state_wasm",
"_ts_node_named_child_count_wasm",
"_ts_node_named_child_wasm",
"_ts_node_named_children_wasm",
@ -65,6 +81,7 @@
"_ts_node_start_index_wasm",
"_ts_node_start_point_wasm",
"_ts_node_symbol_wasm",
"_ts_node_grammar_symbol_wasm",
"_ts_node_to_string_wasm",
"_ts_parser_delete",
"_ts_parser_enable_logger_wasm",
@ -90,19 +107,29 @@
"_ts_tree_cursor_current_node_is_missing_wasm",
"_ts_tree_cursor_current_node_is_named_wasm",
"_ts_tree_cursor_current_node_type_id_wasm",
"_ts_tree_cursor_current_node_state_id_wasm",
"_ts_tree_cursor_current_node_wasm",
"_ts_tree_cursor_delete_wasm",
"_ts_tree_cursor_end_index_wasm",
"_ts_tree_cursor_end_position_wasm",
"_ts_tree_cursor_goto_first_child_wasm",
"_ts_tree_cursor_goto_last_child_wasm",
"_ts_tree_cursor_goto_next_sibling_wasm",
"_ts_tree_cursor_goto_previous_sibling_wasm",
"_ts_tree_cursor_goto_parent_wasm",
"_ts_tree_cursor_new_wasm",
"_ts_tree_cursor_reset_wasm",
"_ts_tree_cursor_reset_to_wasm",
"_ts_tree_cursor_start_index_wasm",
"_ts_tree_cursor_start_position_wasm",
"_ts_tree_delete",
"_ts_tree_edit_wasm",
"_ts_tree_get_changed_ranges_wasm",
"_ts_tree_root_node_wasm"
"_ts_tree_root_node_wasm",
"_ts_lookahead_iterator_new",
"_ts_lookahead_iterator_delete",
"_ts_lookahead_iterator_reset_state",
"_ts_lookahead_iterator_reset",
"_ts_lookahead_iterator_next",
"_ts_lookahead_iterator_current_symbol"
]

View file

@ -1,6 +1,6 @@
{
"name": "web-tree-sitter",
"version": "0.20.7",
"version": "0.20.8",
"description": "Tree-sitter bindings for the web",
"main": "tree-sitter.js",
"types": "tree-sitter-web.d.ts",
@ -27,8 +27,8 @@
},
"homepage": "https://github.com/tree-sitter/tree-sitter/tree/master/lib/binding_web",
"devDependencies": {
"chai": "^4.2.0",
"mocha": "^6.1.4",
"terser": "^3.17.0"
"chai": "^4.3.7",
"mocha": "^10.2.0",
"terser": "^5.16.6"
}
}

View file

@ -42,3 +42,46 @@ describe("Language", () => {
});
});
});
describe("Lookahead iterator", () => {
let lookahead;
let state;
before(async () => {
let Parser;
({ JavaScript, Parser } = await require("./helper"));
const parser = new Parser().setLanguage(JavaScript);
const tree = parser.parse("function fn() {}");
parser.delete();
const cursor = tree.walk();
assert(cursor.gotoFirstChild());
assert(cursor.gotoFirstChild());
state = cursor.currentNode().nextParseState;
lookahead = JavaScript.lookaheadIterator(state);
assert.exists(lookahead);
});
after(() => {
lookahead.delete();
});
const expected = ["identifier", "comment", "(", "*", "formal_parameters"];
it("should iterate over valid symbols in the state", () => {
const symbols = Array.from(lookahead);
assert.includeMembers(symbols, expected);
assert.lengthOf(symbols, expected.length);
});
it("should reset to the initial state", () => {
assert(lookahead.resetState(state));
const symbols = Array.from(lookahead);
assert.includeMembers(symbols, expected);
assert.lengthOf(symbols, expected.length);
});
it("should reset", () => {
assert(lookahead.reset(JavaScript, state));
const symbols = Array.from(lookahead);
assert.includeMembers(symbols, expected);
assert.lengthOf(symbols, expected.length);
});
});

View file

@ -268,6 +268,24 @@ describe("Node", () => {
});
});
describe(".isError()", () => {
it("returns true if the node is an error", () => {
tree = parser.parse("2 * * 3");
const node = tree.rootNode;
assert.equal(
node.toString(),
'(program (expression_statement (binary_expression left: (number) (ERROR) right: (number))))'
);
const multi = node.firstChild.firstChild;
assert(multi.hasError());
assert(!multi.children[0].isError());
assert(!multi.children[1].isError());
assert(multi.children[2].isError());
assert(!multi.children[3].isError());
});
});
describe(".isMissing()", () => {
it("returns true if the node is missing from the source and was inserted via error recovery", () => {
tree = parser.parse("(2 ||)");
@ -308,6 +326,34 @@ describe("Node", () => {
);
});
describe(".parseState, .nextParseState", () => {
const text = "10 / 5";
it("returns node parse state ids", async () => {
tree = await parser.parse(text)
const quotientNode = tree.rootNode.firstChild.firstChild;
const [numerator, slash, denominator] = quotientNode.children;
assert.equal(tree.rootNode.parseState, 0);
// parse states will change on any change to the grammar so test that it
// returns something instead
assert.isAbove(numerator.parseState, 0);
assert.isAbove(slash.parseState, 0);
assert.isAbove(denominator.parseState, 0);
})
it("returns next parse state equal to the language", async () => {
tree = await parser.parse(text);
const quotientNode = tree.rootNode.firstChild.firstChild;
quotientNode.children.forEach(node => {
assert.equal(
node.nextParseState,
JavaScript.nextState(node.parseState, node.grammarId)
);
});
});
});
describe('.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");
@ -408,4 +454,20 @@ describe("Node", () => {
assert(!node1.equals(node2));
});
});
describe('.fieldNameForChild(index)', () => {
it('returns the field of a child or null', () => {
tree = parser.parse('let a = 5');
const noField = tree.rootNode.fieldNameForChild(0);
const name = tree.rootNode.firstChild.children[1].fieldNameForChild(0);
const value = tree.rootNode.firstChild.children[1].fieldNameForChild(2);
const overflow = tree.rootNode.firstChild.children[1].fieldNameForChild(3);
assert.equal(noField, null);
assert.equal(name, 'name');
assert.equal(value, 'value');
assert.equal(overflow, null);
});
});
});

View file

@ -127,19 +127,19 @@ describe("Parser", () => {
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");
tree = parser.parse("FOO=bar echo <<EOF 2> err.txt > hello.txt \nhello${FOO}\nEOF");
assert.equal(
tree.rootNode.toString(),
'(program (redirected_statement ' +
'body: (command ' +
'(variable_assignment ' +
'name: (variable_name) ' +
'value: (word)) ' +
'name: (command_name (word))) ' +
'redirect: (heredoc_redirect (heredoc_start)) ' +
'redirect: (file_redirect descriptor: (file_descriptor) destination: (word)) ' +
'redirect: (file_redirect destination: (word))) ' +
'(heredoc_body))'
'(program ' +
'(redirected_statement ' +
'body: (command ' +
'(variable_assignment name: (variable_name) value: (word)) ' +
'name: (command_name (word))) ' +
'redirect: (heredoc_redirect (heredoc_start) ' +
'redirect: (file_redirect descriptor: (file_descriptor) destination: (word)) ' +
'redirect: (file_redirect destination: (word)) ' +
'(heredoc_body ' +
'(expansion (variable_name)) (heredoc_content)) (heredoc_end))))'
);
}).timeout(5000);
@ -153,7 +153,7 @@ describe("Parser", () => {
'type: (primitive_type) ' +
'declarator: (init_declarator ' +
'declarator: (pointer_declarator declarator: (identifier)) ' +
'value: (raw_string_literal))))'
'value: (raw_string_literal delimiter: (raw_string_delimiter) (raw_string_content) (raw_string_delimiter)))))'
);
}).timeout(5000);

View file

@ -244,6 +244,50 @@ describe("Tree", () => {
endIndex: 13
});
{
const copy = tree.walk();
copy.resetTo(cursor);
assert(copy.gotoPreviousSibling());
assertCursorState(copy, {
nodeType: '+',
nodeIsNamed: false,
startPosition: {row: 0, column: 6},
endPosition: {row: 0, column: 7},
startIndex: 6,
endIndex: 7
});
assert(copy.gotoPreviousSibling());
assertCursorState(copy, {
nodeType: 'binary_expression',
nodeIsNamed: true,
startPosition: {row: 0, column: 0},
endPosition: {row: 0, column: 5},
startIndex: 0,
endIndex: 5
});
assert(copy.gotoLastChild());
assertCursorState(copy, {
nodeType: "identifier",
nodeIsNamed: true,
startPosition: {row: 0, column: 4},
endPosition: {row: 0, column: 5},
startIndex: 4,
endIndex: 5
})
assert(copy.gotoParent());
assert(copy.gotoParent());
assert.equal(copy.nodeType, 'binary_expression')
assert(copy.gotoParent());
assert.equal(copy.nodeType, 'expression_statement')
assert(copy.gotoParent());
assert.equal(copy.nodeType, 'program')
assert(!copy.gotoParent());
}
// const childIndex = cursor.gotoFirstChildForIndex(12);
// assertCursorState(cursor, {
// nodeType: 'identifier',

View file

@ -55,10 +55,14 @@ declare module 'web-tree-sitter' {
) => string | null;
export interface SyntaxNode {
id: number;
typeId: number;
grammarId: number;
tree: Tree;
type: string;
grammarType: string;
text: string;
parseState: number;
nextParseState: number;
startPosition: Point;
endPosition: Point;
startIndex: number;
@ -80,6 +84,7 @@ declare module 'web-tree-sitter' {
hasChanges(): boolean;
hasError(): boolean;
equals(other: SyntaxNode): boolean;
isError(): boolean;
isMissing(): boolean;
isNamed(): boolean;
toString(): string;
@ -104,6 +109,7 @@ declare module 'web-tree-sitter' {
export interface TreeCursor {
nodeType: string;
nodeTypeId: number;
nodeStateId: number;
nodeText: string;
nodeId: number;
nodeIsNamed: boolean;
@ -114,14 +120,17 @@ declare module 'web-tree-sitter' {
endIndex: number;
reset(node: SyntaxNode): void;
resetTo(cursor: TreeCursor): void;
delete(): void;
currentNode(): SyntaxNode;
currentFieldId(): number;
currentFieldName(): string;
gotoParent(): boolean;
gotoFirstChild(): boolean;
gotoLastChild(): boolean;
gotoFirstChildForIndex(index: number): boolean;
gotoNextSibling(): boolean;
gotoPreviousSibling(): boolean;
}
export interface Tree {
@ -141,6 +150,7 @@ declare module 'web-tree-sitter' {
readonly version: number;
readonly fieldCount: number;
readonly stateCount: number;
readonly nodeTypeCount: number;
fieldNameForId(fieldId: number): string | null;
@ -149,7 +159,20 @@ declare module 'web-tree-sitter' {
nodeTypeForId(typeId: number): string | null;
nodeTypeIsNamed(typeId: number): boolean;
nodeTypeIsVisible(typeId: number): boolean;
nextState(stateId: number, typeId: number): number;
query(source: string): Query;
lookaheadIterator(stateId: number): LookaheadIterable | null;
}
class LookaheadIterable {
readonly language: Language;
readonly currentTypeId: number;
readonly currentType: string;
delete(): void;
resetState(stateId: number): boolean;
reset(language: Language, stateId: number): boolean;
[Symbol.iterator](): Iterator<string>;
}
interface QueryCapture {