From 2d25e62aa21e887e6e271481533013d50eece457 Mon Sep 17 00:00:00 2001 From: Amaan Qureshi Date: Wed, 28 Feb 2024 13:44:12 -0500 Subject: [PATCH 1/8] build(deps): update submodule --- vendor/tree-sitter | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/tree-sitter b/vendor/tree-sitter index ff89b539..f8c41f74 160000 --- a/vendor/tree-sitter +++ b/vendor/tree-sitter @@ -1 +1 @@ -Subproject commit ff89b539f276e2bd19248ffa462d8a426341993d +Subproject commit f8c41f74f8c1bda0fdd82c07357ae601b6b2f62e From 98a68e40da6cba0edfd89e6d2efe6d081e264b02 Mon Sep 17 00:00:00 2001 From: Segev Finer Date: Sun, 25 Feb 2024 00:00:50 +0200 Subject: [PATCH 2/8] refactor!: use NAPI instead of Nan --- .github/workflows/build.yml | 10 +- binding.gyp | 21 +- jest-tests/test.test.js | 245 +++++++------ package.json | 11 +- src/addon_data.h | 31 +- src/binding.cc | 32 +- src/conversions.cc | 154 ++++---- src/conversions.h | 19 +- src/language.cc | 98 +++-- src/language.h | 7 +- src/logger.cc | 54 +-- src/logger.h | 8 +- src/node.cc | 711 ++++++++++++++++++------------------ src/node.h | 16 +- src/parser.cc | 260 ++++++------- src/parser.h | 25 +- src/query.cc | 275 +++++++------- src/query.h | 23 +- src/tree.cc | 212 ++++++----- src/tree.h | 34 +- src/tree_cursor.cc | 219 +++++------ src/tree_cursor.h | 44 ++- src/util.cc | 22 -- src/util.h | 14 +- 24 files changed, 1171 insertions(+), 1374 deletions(-) delete mode 100644 src/util.cc diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 658af321..8d1c24cd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,8 +12,7 @@ on: - released env: - NODE_PREBUILD_CMD: npx prebuild -t 14.0.0 -t 16.0.0 -t 18.0.0 -t 20.0.0 --strip -u ${{ secrets.GH_TOKEN }} - ELECTRON_PREBUILD_CMD: npx prebuild -r electron -t 12.0.0 -t 13.0.0 -t 14.0.0 -t 15.0.0 -t 16.0.0 -t 17.0.0 -t 18.0.0 -t 19.0.0 -t 20.0.0 -t 21.0.0 -t 22.0.0 -t 23.0.0 -t 24.0.0 -t 25.0.0 --strip -u ${{ secrets.GH_TOKEN }} + PREBUILD_CMD: npx prebuild -r napi --all --strip -u ${{ secrets.GH_TOKEN }} jobs: @@ -86,9 +85,6 @@ jobs: with: node-version: 20 - run: npm install + - run: ${{ env.PREBUILD_CMD }} - if: runner.os == 'macOS' - run: ${{ env.NODE_PREBUILD_CMD }} --arch arm64 - - if: runner.os == 'macOS' - run: ${{ env.ELECTRON_PREBUILD_CMD }} --arch arm64 - - run: ${{ env.NODE_PREBUILD_CMD }} - - run: ${{ env.ELECTRON_PREBUILD_CMD }} + run: ${{ env.PREBUILD_CMD }} --arch arm64 diff --git a/binding.gyp b/binding.gyp index 5e3d479a..314b7b70 100644 --- a/binding.gyp +++ b/binding.gyp @@ -2,7 +2,7 @@ "targets": [ { "target_name": "tree_sitter_runtime_binding", - "dependencies": ["tree_sitter"], + "dependencies": ["tree_sitter", " { it("should work with cursors", () => { const tree = jsParser.parse("a * b + c / d"); - - const 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, - }); - expect(childIndex).toBe(2); - - assert(!cursor.gotoNextSibling()); - assert(cursor.gotoParent()); - assert(cursor.gotoParent()); - assert(cursor.gotoParent()); - assert(cursor.gotoParent()); - assert(!cursor.gotoParent()); + if (tree.rootNode) { + const 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, + }); + expect(childIndex).toBe(2); + + assert(!cursor.gotoNextSibling()); + assert(cursor.gotoParent()); + assert(cursor.gotoParent()); + assert(cursor.gotoParent()); + assert(cursor.gotoParent()); + assert(!cursor.gotoParent()); + } }); it("returns all of the matches for the given query", () => { - const tree = jsParser.parse("function one() { two(); function three() {} }"); + const tree = jsParser.parse( + "function one() { two(); function three() {} }" + ); const query = new Query( Javascript, ` diff --git a/package.json b/package.json index 55de0325..70aacefd 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "main": "index.js", "types": "tree-sitter.d.ts", "dependencies": { - "nan": "^2.18.0", + "node-addon-api": "^7.1.0", "prebuild-install": "^7.1.1" }, "devDependencies": { @@ -25,11 +25,16 @@ "mocha": "^8.4.0", "node-gyp": "^10.0.1", "prebuild": "^13.0.0", - "tree-sitter-javascript": "^0.20.3" + "tree-sitter-javascript": "github:segevfiner/tree-sitter-javascript#napi-2" }, "scripts": { - "install": "prebuild-install || node-gyp rebuild", + "install": "prebuild-install -r napi || node-gyp rebuild", "build": "node-gyp build", "test": "mocha && jest" + }, + "binary": { + "napi_versions": [ + 8 + ] } } diff --git a/src/addon_data.h b/src/addon_data.h index 9700d14a..e79a543e 100644 --- a/src/addon_data.h +++ b/src/addon_data.h @@ -1,4 +1,4 @@ -#include +#include #include #include @@ -9,49 +9,34 @@ namespace node_tree_sitter { class AddonData { public: - explicit AddonData(v8::Isolate* isolate) { - // Ensure this per-addon-instance data is deleted at environment cleanup. - node::AddEnvironmentCleanupHook(isolate, DeleteInstance, this); - } + explicit AddonData(Napi::Env env) {} ~AddonData() { ts_query_cursor_delete(ts_query_cursor); } // conversions - Nan::Persistent row_key; - Nan::Persistent column_key; - Nan::Persistent start_index_key; - Nan::Persistent start_position_key; - Nan::Persistent end_index_key; - Nan::Persistent end_position_key; uint32_t *point_transfer_buffer = nullptr; // node uint32_t *transfer_buffer = nullptr; uint32_t transfer_buffer_length = 0; - Nan::Persistent module_exports; + Napi::ObjectReference module_exports; TSTreeCursor scratch_cursor = {nullptr, nullptr, {0, 0}}; // parser - Nan::Persistent parser_constructor; + Napi::FunctionReference parser_constructor; + Napi::FunctionReference string_slice; // query TSQueryCursor *ts_query_cursor = nullptr; - Nan::Persistent query_constructor; - Nan::Persistent query_constructor_template; + Napi::FunctionReference query_constructor; // tree_cursor - Nan::Persistent tree_cursor_constructor; + Napi::FunctionReference tree_cursor_constructor; // tree - Nan::Persistent tree_constructor; - Nan::Persistent tree_constructor_template; - -private: - static void DeleteInstance(void* data) { - delete static_cast(data); - } + Napi::FunctionReference tree_constructor; }; } diff --git a/src/binding.cc b/src/binding.cc index 31868827..e0fa0051 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -1,5 +1,4 @@ -#include -#include +#include #include "./addon_data.h" #include "./language.h" #include "./node.h" @@ -9,26 +8,25 @@ #include "./tree_cursor.h" #include "./conversions.h" -namespace node_tree_sitter { - -using namespace v8; +using namespace Napi; -void InitAll(Local exports, Local m_, Local context) { - Isolate* isolate = context->GetIsolate(); +namespace node_tree_sitter { - AddonData* data = new AddonData(isolate); +Napi::Object InitAll(Napi::Env env, Napi::Object exports) { + AddonData* data = new AddonData(env); + env.SetInstanceData(data); - Local data_ext = External::New(isolate, data); + InitConversions(env, exports); + node_methods::Init(env, exports); + language_methods::Init(env, exports); + Parser::Init(env, exports); + Query::Init(env, exports); + Tree::Init(env, exports); + TreeCursor::Init(env, exports); - InitConversions(exports, data_ext); - node_methods::Init(exports, data_ext); - language_methods::Init(exports); - Parser::Init(exports, data_ext); - Query::Init(exports, data_ext); - Tree::Init(exports, data_ext); - TreeCursor::Init(exports, data_ext); + return exports; } -NODE_MODULE_CONTEXT_AWARE(tree_sitter_runtime_binding, InitAll) +NODE_API_MODULE(tree_sitter_runtime_binding, InitAll) } // namespace node_tree_sitter diff --git a/src/conversions.cc b/src/conversions.cc index 6551cd86..82ab7750 100644 --- a/src/conversions.cc +++ b/src/conversions.cc @@ -1,142 +1,140 @@ -#include "./node.h" -#include "./addon_data.h" -#include -#include -#include #include "./conversions.h" +#include +#include #include +#include "./node.h" +#include "./addon_data.h" -namespace node_tree_sitter { +using namespace Napi; -using namespace v8; +namespace node_tree_sitter { static const unsigned BYTES_PER_CHARACTER = 2; -void InitConversions(Local exports, Local data_ext) { - AddonData* data = static_cast(data_ext->Value()); - - data->row_key.Reset(Nan::Persistent(Nan::New("row").ToLocalChecked())); - data->column_key.Reset(Nan::Persistent(Nan::New("column").ToLocalChecked())); - data->start_index_key.Reset(Nan::Persistent(Nan::New("startIndex").ToLocalChecked())); - data->start_position_key.Reset(Nan::Persistent(Nan::New("startPosition").ToLocalChecked())); - data->end_index_key.Reset(Nan::Persistent(Nan::New("endIndex").ToLocalChecked())); - data->end_position_key.Reset(Nan::Persistent(Nan::New("endPosition").ToLocalChecked())); +void InitConversions(Napi::Env env, Napi::Object exports) { + auto data = env.GetInstanceData(); - auto js_point_transfer_buffer = Nan::NewBuffer(2 * sizeof(uint32_t)).ToLocalChecked(); - data->point_transfer_buffer = reinterpret_cast(node::Buffer::Data(js_point_transfer_buffer)); + auto js_point_transfer_buffer = Uint32Array::New(env, 2); + data->point_transfer_buffer = js_point_transfer_buffer.Data(); - Nan::Set(exports, Nan::New("pointTransferArray").ToLocalChecked(), Uint32Array::New(js_point_transfer_buffer.As()->Buffer(), 0, 2)); + exports["pointTransferArray"] = js_point_transfer_buffer; } -void TransferPoint(AddonData* data, const TSPoint &point) { +void TransferPoint(Napi::Env env, const TSPoint &point) { + auto data = env.GetInstanceData(); data->point_transfer_buffer[0] = point.row; data->point_transfer_buffer[1] = point.column / 2; } -Local RangeToJS(AddonData* data, const TSRange &range) { - Local result = Nan::New(); - Nan::Set(result, Nan::New(data->start_position_key), PointToJS(data, range.start_point)); - Nan::Set(result, Nan::New(data->start_index_key), ByteCountToJS(data, range.start_byte)); - Nan::Set(result, Nan::New(data->end_position_key), PointToJS(data, range.end_point)); - Nan::Set(result, Nan::New(data->end_index_key), ByteCountToJS(data, range.end_byte)); +Napi::Object RangeToJS(Napi::Env env, const TSRange &range) { + Object result = Object::New(env); + result.Set("startPosition", PointToJS(env, range.start_point)); + result.Set("startIndex", ByteCountToJS(env, range.start_byte)); + result.Set("endPosition", PointToJS(env, range.end_point)); + result.Set("endIndex", ByteCountToJS(env, range.end_byte)); return result; } -Nan::Maybe RangeFromJS(AddonData* data, const Local &arg) { - if (!arg->IsObject()) { - Nan::ThrowTypeError("Range must be a {startPosition, endPosition, startIndex, endIndex} object"); - return Nan::Nothing(); +Napi::Maybe RangeFromJS(const Napi::Value& arg) { + Env env = arg.Env(); + if (!arg.IsObject()) { + TypeError::New(env, "Range must be a {startPosition, endPosition, startIndex, endIndex} object").ThrowAsJavaScriptException(); + return Napi::Nothing(); } TSRange result; - Local js_range = Local::Cast(arg); + Object js_range = arg.As(); #define INIT(field, key, Convert) { \ - auto value = Nan::Get(js_range, Nan::New(key)); \ + auto value = js_range.Get(key); \ if (value.IsEmpty()) { \ - Nan::ThrowTypeError("Range must be a {startPosition, endPosition, startIndex, endIndex} object"); \ - return Nan::Nothing(); \ + TypeError::New(env, "Range must be a {startPosition, endPosition, startIndex, endIndex} object").ThrowAsJavaScriptException(); \ + return Napi::Nothing(); \ } \ - auto field = Convert(data, value.ToLocalChecked()); \ + auto field = Convert(value); \ if (field.IsJust()) { \ - result.field = field.FromJust(); \ + result.field = field.Unwrap(); \ } else { \ - return Nan::Nothing(); \ + return Napi::Nothing(); \ } \ } - INIT(start_point, data->start_position_key, PointFromJS); - INIT(end_point, data->end_position_key, PointFromJS); - INIT(start_byte, data->start_index_key, ByteCountFromJS); - INIT(end_byte, data->end_index_key, ByteCountFromJS); + INIT(start_point, "startPosition", PointFromJS); + INIT(end_point, "endPosition", PointFromJS); + INIT(start_byte, "startIndex", ByteCountFromJS); + INIT(end_byte, "endIndex", ByteCountFromJS); #undef INIT - return Nan::Just(result); + return Napi::Just(result); } -Local PointToJS(AddonData* data, const TSPoint &point) { - Local result = Nan::New(); - Nan::Set(result, Nan::New(data->row_key), Nan::New(point.row)); - Nan::Set(result, Nan::New(data->column_key), ByteCountToJS(data, point.column)); +Napi::Object PointToJS(Napi::Env env, const TSPoint &point) { + Object result = Object::New(env); + result["row"] = Number::New(env, point.row); + result["column"] = ByteCountToJS(env, point.column); return result; } -Nan::Maybe PointFromJS(AddonData* data, const Local &arg) { - Local js_point; - if (!arg->IsObject() || !Nan::To(arg).ToLocal(&js_point)) { - Nan::ThrowTypeError("Point must be a {row, column} object"); - return Nan::Nothing(); +Napi::Maybe PointFromJS(const Napi::Value& arg) { + Env env = arg.Env(); + + if (!arg.IsObject()) { + TypeError::New(env, "Point must be a {row, column} object").ThrowAsJavaScriptException(); + return Napi::Nothing(); } + Object js_point = arg.As(); - Local js_row; - if (!Nan::Get(js_point, Nan::New(data->row_key)).ToLocal(&js_row)) { - Nan::ThrowTypeError("Point must be a {row, column} object"); - return Nan::Nothing(); + Number js_row = js_point.Get("row").As(); + if (!js_row.IsNumber()) { + TypeError::New(env, "Point must be a {row, column} object").ThrowAsJavaScriptException(); + return Napi::Nothing(); } - Local js_column; - if (!Nan::Get(js_point, Nan::New(data->column_key)).ToLocal(&js_column)) { - Nan::ThrowTypeError("Point must be a {row, column} object"); - return Nan::Nothing(); + Number js_column = js_point.Get("column").As(); + if (!js_column.IsNumber()) { + TypeError::New(env, "Point must be a {row, column} object").ThrowAsJavaScriptException(); + return Napi::Nothing(); } uint32_t row; - if (!std::isfinite(Nan::To(js_row).FromMaybe(0))) { + if (!std::isfinite(js_row.DoubleValue())) { row = UINT32_MAX; - } else if (js_row->IsNumber()) { - row = Nan::To(js_row).FromJust(); + } else if (js_row.IsNumber()) { + row = js_row.Uint32Value(); } else { - Nan::ThrowTypeError("Point.row must be a number"); - return Nan::Nothing(); + TypeError::New(env, "Point.row must be a number").ThrowAsJavaScriptException(); + return Napi::Nothing(); } uint32_t column; - if (!std::isfinite(Nan::To(js_column).FromMaybe(0))) { + if (!std::isfinite(js_column.DoubleValue())) { column = UINT32_MAX; - } else if (js_column->IsNumber()) { - column = Nan::To(js_column).FromMaybe(0) * BYTES_PER_CHARACTER; + } else if (js_column.IsNumber()) { + column = js_column.Uint32Value() * BYTES_PER_CHARACTER; } else { - Nan::ThrowTypeError("Point.column must be a number"); - return Nan::Nothing(); + TypeError::New(env, "Point.column must be a number").ThrowAsJavaScriptException(); + return Napi::Nothing(); } - return Nan::Just({row, column}); + return Napi::Just({row, column}); } -Local ByteCountToJS(AddonData* data, uint32_t byte_count) { - return Nan::New(byte_count / BYTES_PER_CHARACTER); +Napi::Number ByteCountToJS(Napi::Env env, uint32_t byte_count) { + return Number::New(env, byte_count / BYTES_PER_CHARACTER); } -Nan::Maybe ByteCountFromJS(AddonData* data, const v8::Local &arg) { - auto result = Nan::To(arg); - if (!arg->IsNumber()) { - Nan::ThrowTypeError("Character index must be a number"); - return Nan::Nothing(); +Napi::Maybe ByteCountFromJS(const Napi::Value &arg) { + Napi::Env env = arg.Env(); + + if (!arg.IsNumber()) { + TypeError::New(env, "Character index must be a number").ThrowAsJavaScriptException(); + return Napi::Nothing(); } + auto result = arg.As(); - return Nan::Just(result.FromJust() * BYTES_PER_CHARACTER); + return Napi::Just(result.Uint32Value() * BYTES_PER_CHARACTER); } } // namespace node_tree_sitter diff --git a/src/conversions.h b/src/conversions.h index 38b1c62c..22147257 100644 --- a/src/conversions.h +++ b/src/conversions.h @@ -1,21 +1,20 @@ #ifndef NODE_TREE_SITTER_CONVERSIONS_H_ #define NODE_TREE_SITTER_CONVERSIONS_H_ -#include -#include +#include #include #include "./addon_data.h" namespace node_tree_sitter { -void InitConversions(v8::Local exports, v8::Local data_ext); -v8::Local RangeToJS(AddonData* data, const TSRange &); -v8::Local PointToJS(AddonData* data, const TSPoint &); -void TransferPoint(AddonData* data, const TSPoint &); -v8::Local ByteCountToJS(AddonData* data, uint32_t); -Nan::Maybe PointFromJS(AddonData* data, const v8::Local &); -Nan::Maybe ByteCountFromJS(AddonData* data, const v8::Local &); -Nan::Maybe RangeFromJS(AddonData* data, const v8::Local &); +void InitConversions(Napi::Env env, Napi::Object exports); +Napi::Object RangeToJS(Napi::Env env, const TSRange &); +Napi::Object PointToJS(Napi::Env env, const TSPoint &); +void TransferPoint(Napi::Env env, const TSPoint &); +Napi::Number ByteCountToJS(Napi::Env env, uint32_t); +Napi::Maybe PointFromJS(const Napi::Value &); +Napi::Maybe ByteCountFromJS(const Napi::Value &); +Napi::Maybe RangeFromJS(const Napi::Value&); } // namespace node_tree_sitter diff --git a/src/language.cc b/src/language.cc index bdfb90f1..8ba6af72 100644 --- a/src/language.cc +++ b/src/language.cc @@ -1,91 +1,89 @@ #include "./language.h" -#include +#include #include #include #include -#include + +using namespace Napi; +using std::vector; namespace node_tree_sitter { namespace language_methods { -using std::vector; -using namespace v8; +const TSLanguage *UnwrapLanguage(Napi::Value value) { + Napi::Env env = value.Env(); + + if (value.IsObject()) { + value = value.As()["language"]; + } -const TSLanguage *UnwrapLanguage(const v8::Local &value) { - if (value->IsObject()) { - Local arg = Local::Cast(value); - if (arg->InternalFieldCount() == 1) { - const TSLanguage *language = (const TSLanguage *)Nan::GetInternalFieldPointer(arg, 0); - if (language) { - uint16_t version = ts_language_version(language); - if ( - version < TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION || - version > TREE_SITTER_LANGUAGE_VERSION - ) { - std::string message = - "Incompatible language version. Compatible range: " + - std::to_string(TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION) + " - " + - std::to_string(TREE_SITTER_LANGUAGE_VERSION) + ". Got: " + - std::to_string(ts_language_version(language)); - Nan::ThrowError(Nan::RangeError(message.c_str())); - return nullptr; - } - return language; + if (value.IsExternal()) { + External arg = value.As>(); + const TSLanguage *language = arg.Data(); + if (language) { + uint16_t version = ts_language_version(language); + if ( + version < TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION || + version > TREE_SITTER_LANGUAGE_VERSION + ) { + std::string message = + "Incompatible language version. Compatible range: " + + std::to_string(TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION) + " - " + + std::to_string(TREE_SITTER_LANGUAGE_VERSION) + ". Got: " + + std::to_string(ts_language_version(language)); + RangeError::New(env, message.c_str()); + return nullptr; } + return language; } } - Nan::ThrowTypeError("Invalid language object"); - return nullptr; + throw TypeError::New(env, "Invalid language object"); } -static void GetNodeTypeNamesById(const Nan::FunctionCallbackInfo &info) { +static Napi::Value GetNodeTypeNamesById(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const TSLanguage *language = UnwrapLanguage(info[0]); - if (!language) return; + if (!language) return env.Undefined(); - auto result = Nan::New(); + auto result = Array::New(env); uint32_t length = ts_language_symbol_count(language); for (uint32_t i = 0; i < length; i++) { const char *name = ts_language_symbol_name(language, i); TSSymbolType type = ts_language_symbol_type(language, i); if (type == TSSymbolTypeRegular) { - Nan::Set(result, i, Nan::New(name).ToLocalChecked()); + result[i] = String::New(env, name); } else { - Nan::Set(result, i, Nan::Null()); + result[i] = env.Null(); } } - info.GetReturnValue().Set(result); + return result; } -static void GetNodeFieldNamesById(const Nan::FunctionCallbackInfo &info) { +static Napi::Value GetNodeFieldNamesById(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const TSLanguage *language = UnwrapLanguage(info[0]); - if (!language) return; + if (!language) return env.Undefined(); - auto result = Nan::New(); + auto result = Array::New(env); uint32_t length = ts_language_field_count(language); for (uint32_t i = 0; i < length + 1; i++) { const char *name = ts_language_field_name_for_id(language, i); if (name) { - Nan::Set(result, i, Nan::New(name).ToLocalChecked()); + result[i] = String::New(env, name); } else { - Nan::Set(result, i, Nan::Null()); + result[i] = env.Null(); } } - info.GetReturnValue().Set(result); -} -void Init(Local exports) { - Nan::Set( - exports, - Nan::New("getNodeTypeNamesById").ToLocalChecked(), - Nan::GetFunction(Nan::New(GetNodeTypeNamesById)).ToLocalChecked() - ); + return result; +} - Nan::Set( - exports, - Nan::New("getNodeFieldNamesById").ToLocalChecked(), - Nan::GetFunction(Nan::New(GetNodeFieldNamesById)).ToLocalChecked() - ); +void Init(Napi::Env env, Napi::Object exports) { + exports["getNodeTypeNamesById"] = Function::New(env, GetNodeTypeNamesById); + exports["getNodeFieldNamesById"] = Function::New(env, GetNodeFieldNamesById); } } // namespace language_methods diff --git a/src/language.h b/src/language.h index 76f7ac28..4df74149 100644 --- a/src/language.h +++ b/src/language.h @@ -1,8 +1,7 @@ #ifndef NODE_TREE_SITTER_LANGUAGE_H_ #define NODE_TREE_SITTER_LANGUAGE_H_ -#include -#include +#include #include #include #include "./tree.h" @@ -10,9 +9,9 @@ namespace node_tree_sitter { namespace language_methods { -void Init(v8::Local); +void Init(Napi::Env env, Napi::Object); -const TSLanguage *UnwrapLanguage(const v8::Local &); +const TSLanguage *UnwrapLanguage(Napi::Value); } // namespace language_methods } // namespace node_tree_sitter diff --git a/src/logger.cc b/src/logger.cc index 9c3d47da..e7416a74 100644 --- a/src/logger.cc +++ b/src/logger.cc @@ -1,28 +1,25 @@ #include "./logger.h" #include -#include -#include +#include #include #include "./util.h" -namespace node_tree_sitter { - -using namespace v8; +using namespace Napi; using std::string; +namespace node_tree_sitter { + void Logger::Log(void *payload, TSLogType type, const char *message_str) { - Logger *debugger = (Logger *)payload; - Local fn = Nan::New(debugger->func); - if (!fn->IsFunction()) - return; + auto debugger = reinterpret_cast(payload); + Env env = debugger->func.Env(); string message(message_str); string param_sep = " "; size_t param_sep_pos = message.find(param_sep, 0); - Local type_name = Nan::New((type == TSLogTypeParse) ? "parse" : "lex").ToLocalChecked(); - Local name = Nan::New(message.substr(0, param_sep_pos)).ToLocalChecked(); - Local params = Nan::New(); + String type_name = String::New(env, (type == TSLogTypeParse) ? "parse" : "lex");; + String name = String::New(env, message.substr(0, param_sep_pos)); + Object params = Object::New(env); while (param_sep_pos != string::npos) { size_t key_pos = param_sep_pos + param_sep.size(); @@ -37,28 +34,33 @@ void Logger::Log(void *payload, TSLogType type, const char *message_str) { string key = message.substr(key_pos, (value_sep_pos - key_pos)); string value = message.substr(val_pos, (param_sep_pos - val_pos)); - Nan::Set(params, Nan::New(key).ToLocalChecked(), Nan::New(value).ToLocalChecked()); + params[key] = String::New(env, value); } - Local argv[3] = { name, params, type_name }; - TryCatch try_catch(Isolate::GetCurrent()); - Nan::Call(fn, GetGlobal(fn), 3, argv); - if (try_catch.HasCaught()) { - Local log_argv[2] = { - Nan::New("Error in debug callback:").ToLocalChecked(), - try_catch.Exception() - }; + try { + debugger->func({ name, params, type_name }); + } catch (const Error &error) { + Value console = env.Global()["console"]; + if (!console.IsObject()) { + return; + } + + Value error_fn = console.As()["error"]; + if (!error_fn.IsFunction()) { + return; + } - Local console = Local::Cast(Nan::Get(GetGlobal(fn), Nan::New("console").ToLocalChecked()).ToLocalChecked()); - Local error_fn = Local::Cast(Nan::Get(console, Nan::New("error").ToLocalChecked()).ToLocalChecked()); - Nan::Call(error_fn, console, 2, log_argv); + error_fn.As()({ + String::New(env, "Error in debug callback:"), + error.Value() + }); } } -TSLogger Logger::Make(Local func) { +TSLogger Logger::Make(const Napi::Function &func) { TSLogger result; Logger *logger = new Logger(); - logger->func.Reset(Nan::Persistent(func)); + logger->func = Napi::Persistent(func); result.payload = (void *)logger; result.log = Log; return result; diff --git a/src/logger.h b/src/logger.h index af68413f..a6ed770e 100644 --- a/src/logger.h +++ b/src/logger.h @@ -1,20 +1,18 @@ #ifndef NODE_TREE_SITTER_LOGGER_H_ #define NODE_TREE_SITTER_LOGGER_H_ -#include -#include +#include #include namespace node_tree_sitter { class Logger { public: - static TSLogger Make(v8::Local); - Nan::Persistent func; + static TSLogger Make(const Napi::Function &); + Napi::FunctionReference func; static void Log(void *, TSLogType, const char *); }; - } // namespace node_tree_sitter #endif // NODE_TREE_SITTER_LOGGER_H_ diff --git a/src/node.cc b/src/node.cc index 1f93fe7a..48181bd9 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1,34 +1,31 @@ #include "./node.h" -#include +#include #include #include -#include #include "./util.h" #include "./conversions.h" #include "./tree.h" #include "./tree_cursor.h" +using std::vector; +using namespace Napi; + namespace node_tree_sitter { namespace node_methods { -using std::vector; -using namespace v8; - static const uint32_t FIELD_COUNT_PER_NODE = 6; -static inline void setup_transfer_buffer(AddonData* data, uint32_t node_count) { +static inline void setup_transfer_buffer(Napi::Env env, uint32_t node_count) { + auto data = env.GetInstanceData(); + uint32_t new_length = node_count * FIELD_COUNT_PER_NODE; if (new_length > data->transfer_buffer_length) { data->transfer_buffer_length = new_length; - auto js_transfer_buffer = Nan::NewBuffer(data->transfer_buffer_length * sizeof(uint32_t)).ToLocalChecked(); - data->transfer_buffer = reinterpret_cast(node::Buffer::Data(js_transfer_buffer)); + auto js_transfer_buffer = Uint32Array::New(env, data->transfer_buffer_length); + data->transfer_buffer = js_transfer_buffer.Data(); - Nan::Set( - Nan::New(data->module_exports), - Nan::New("nodeTransferArray").ToLocalChecked(), - Uint32Array::New(js_transfer_buffer.As()->Buffer(), 0, data->transfer_buffer_length) - ); + data->module_exports.Value()["nodeTransferArray"] = js_transfer_buffer; } } @@ -38,20 +35,21 @@ static inline bool operator<=(const TSPoint &left, const TSPoint &right) { return left.column <= right.column; } -static void MarshalNodes(const Nan::FunctionCallbackInfo &info, +static Napi::Value MarshalNodes(const Napi::CallbackInfo &info, const Tree *tree, const TSNode *nodes, uint32_t node_count) { - info.GetReturnValue().Set(GetMarshalNodes(info, tree, nodes, node_count)); + return GetMarshalNodes(info, tree, nodes, node_count); } -void MarshalNode(const Nan::FunctionCallbackInfo &info, const Tree *tree, TSNode node) { - info.GetReturnValue().Set(GetMarshalNode(info, tree, node)); +Napi::Value MarshalNode(const Napi::CallbackInfo &info, const Tree *tree, TSNode node) { + return GetMarshalNode(info, tree, node); } -Local GetMarshalNodes(const Nan::FunctionCallbackInfo &info, +Napi::Value GetMarshalNodes(const Napi::CallbackInfo &info, const Tree *tree, const TSNode *nodes, uint32_t node_count) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - auto result = Nan::New(); - setup_transfer_buffer(data, node_count); + Env env = info.Env(); + auto data = env.GetInstanceData(); + auto result = Array::New(env, node_count); + setup_transfer_buffer(env, node_count); uint32_t *p = data->transfer_buffer; for (unsigned i = 0; i < node_count; i++) { TSNode node = nodes[i]; @@ -64,22 +62,23 @@ Local GetMarshalNodes(const Nan::FunctionCallbackInfo &info, *(p++) = node.context[2]; *(p++) = node.context[3]; if (node.id) { - Nan::Set(result, i, Nan::New(ts_node_symbol(node))); + result[i] = Number::New(env, ts_node_symbol(node)); } else { - Nan::Set(result, i, Nan::Null()); + result[i] = env.Null(); } } else { - Nan::Set(result, i, Nan::New(cache_entry->second->node)); + result[i] = cache_entry->second->node.Value(); } } return result; } -Local GetMarshalNode(const Nan::FunctionCallbackInfo &info, const Tree *tree, TSNode node) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); +Napi::Value GetMarshalNode(const Napi::CallbackInfo &info, const Tree *tree, TSNode node) { + Env env = info.Env(); + AddonData* data = env.GetInstanceData(); const auto &cache_entry = tree->cached_nodes_.find(node.id); if (cache_entry == tree->cached_nodes_.end()) { - setup_transfer_buffer(data, 1); + setup_transfer_buffer(env, 1); uint32_t *p = data->transfer_buffer; MarshalNodeId(node.id, p); p += 2; @@ -88,24 +87,26 @@ Local GetMarshalNode(const Nan::FunctionCallbackInfo &info, const *(p++) = node.context[2]; *(p++) = node.context[3]; if (node.id) { - return Nan::New(ts_node_symbol(node)); + return Number::New(env, ts_node_symbol(node)); } } else { - return Nan::New(cache_entry->second->node); + return cache_entry->second->node.Value(); } - return Nan::Null(); + return env.Null(); } -void MarshalNullNode(AddonData* data) { +static Napi::Value MarshalNullNode(Napi::Env env) { + auto data = env.GetInstanceData(); memset(data->transfer_buffer, 0, FIELD_COUNT_PER_NODE * sizeof(data->transfer_buffer[0])); + return env.Undefined(); } -TSNode UnmarshalNode(AddonData* data, const Tree *tree) { +TSNode UnmarshalNode(Napi::Env env, const Tree *tree) { + AddonData* data = env.GetInstanceData(); TSNode result = {{0, 0, 0, 0}, nullptr, nullptr}; result.tree = tree->tree_; if (!result.tree) { - Nan::ThrowTypeError("Argument must be a tree"); - return result; + throw TypeError::New(env, "Argument must be a tree"); } result.id = UnmarshalNodeId(&data->transfer_buffer[0]); @@ -116,380 +117,385 @@ TSNode UnmarshalNode(AddonData* data, const Tree *tree) { return result; } -static void ToString(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value ToString(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - const char *string = ts_node_string(node); - info.GetReturnValue().Set(Nan::New(string).ToLocalChecked()); - free((char *)string); + char *string = ts_node_string(node); + String result = String::New(env, string); + free(string); + return result; } + + return env.Undefined(); } -static void IsMissing(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value IsMissing(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id) { bool result = ts_node_is_missing(node); - info.GetReturnValue().Set(Nan::New(result)); + return Boolean::New(env, result); } + + return env.Undefined(); } -static void HasChanges(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value HasChanges(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id) { bool result = ts_node_has_changes(node); - info.GetReturnValue().Set(Nan::New(result)); + return Boolean::New(env, result); } + + return env.Undefined(); } -static void HasError(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value HasError(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id) { bool result = ts_node_has_error(node); - info.GetReturnValue().Set(Nan::New(result)); + return Boolean::New(env, result); } + + return env.Undefined(); } -static void FirstNamedChildForIndex(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value FirstNamedChildForIndex(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - Nan::Maybe byte = ByteCountFromJS(data, info[1]); + Napi::Maybe byte = ByteCountFromJS(info[1]); if (byte.IsJust()) { - MarshalNode(info, tree, ts_node_first_named_child_for_byte(node, byte.FromJust())); - return; + return MarshalNode(info, tree, ts_node_first_named_child_for_byte(node, byte.Unwrap())); } } - MarshalNullNode(data); + return MarshalNullNode(env); } -static void FirstChildForIndex(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value FirstChildForIndex(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id && info.Length() > 1) { - Nan::Maybe byte = ByteCountFromJS(data, info[1]); + Napi::Maybe byte = ByteCountFromJS(info[1]); if (byte.IsJust()) { - MarshalNode(info, tree, ts_node_first_child_for_byte(node, byte.FromJust())); - return; + return MarshalNode(info, tree, ts_node_first_child_for_byte(node, byte.Unwrap())); } } - MarshalNullNode(data); + return MarshalNullNode(env); } -static void NamedDescendantForIndex(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value NamedDescendantForIndex(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - Nan::Maybe maybe_min = ByteCountFromJS(data, info[1]); - Nan::Maybe maybe_max = ByteCountFromJS(data, info[2]); + Napi::Maybe maybe_min = ByteCountFromJS(info[1]); + Napi::Maybe maybe_max = ByteCountFromJS(info[2]); if (maybe_min.IsJust() && maybe_max.IsJust()) { - uint32_t min = maybe_min.FromJust(); - uint32_t max = maybe_max.FromJust(); - MarshalNode(info, tree, ts_node_named_descendant_for_byte_range(node, min, max)); - return; + uint32_t min = maybe_min.Unwrap(); + uint32_t max = maybe_max.Unwrap(); + return MarshalNode(info, tree, ts_node_named_descendant_for_byte_range(node, min, max)); } } - MarshalNullNode(data); + return MarshalNullNode(env); } -static void DescendantForIndex(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value DescendantForIndex(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - Nan::Maybe maybe_min = ByteCountFromJS(data, info[1]); - Nan::Maybe maybe_max = ByteCountFromJS(data, info[2]); + Napi::Maybe maybe_min = ByteCountFromJS(info[1]); + Napi::Maybe maybe_max = ByteCountFromJS(info[2]); if (maybe_min.IsJust() && maybe_max.IsJust()) { - uint32_t min = maybe_min.FromJust(); - uint32_t max = maybe_max.FromJust(); - MarshalNode(info, tree, ts_node_descendant_for_byte_range(node, min, max)); - return; + uint32_t min = maybe_min.Unwrap(); + uint32_t max = maybe_max.Unwrap(); + return MarshalNode(info, tree, ts_node_descendant_for_byte_range(node, min, max)); } } - MarshalNullNode(data); + return MarshalNullNode(env); } -static void NamedDescendantForPosition(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value NamedDescendantForPosition(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - Nan::Maybe maybe_min = PointFromJS(data, info[1]); - Nan::Maybe maybe_max = PointFromJS(data, info[2]); + Napi::Maybe maybe_min = PointFromJS(info[1]); + Napi::Maybe maybe_max = PointFromJS(info[2]); if (maybe_min.IsJust() && maybe_max.IsJust()) { - TSPoint min = maybe_min.FromJust(); - TSPoint max = maybe_max.FromJust(); - MarshalNode(info, tree, ts_node_named_descendant_for_point_range(node, min, max)); - return; + TSPoint min = maybe_min.Unwrap(); + TSPoint max = maybe_max.Unwrap(); + return MarshalNode(info, tree, ts_node_named_descendant_for_point_range(node, min, max)); } } - MarshalNullNode(data); + return MarshalNullNode(env); } -static void DescendantForPosition(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value DescendantForPosition(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - Nan::Maybe maybe_min = PointFromJS(data, info[1]); - Nan::Maybe maybe_max = PointFromJS(data, info[2]); + Napi::Maybe maybe_min = PointFromJS(info[1]); + Napi::Maybe maybe_max = PointFromJS(info[2]); if (maybe_min.IsJust() && maybe_max.IsJust()) { - TSPoint min = maybe_min.FromJust(); - TSPoint max = maybe_max.FromJust(); - MarshalNode(info, tree, ts_node_descendant_for_point_range(node, min, max)); - return; + TSPoint min = maybe_min.Unwrap(); + TSPoint max = maybe_max.Unwrap(); + return MarshalNode(info, tree, ts_node_descendant_for_point_range(node, min, max)); } } - MarshalNullNode(data); + return MarshalNullNode(env); } -static void Type(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value Type(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - const char *result = ts_node_type(node); - info.GetReturnValue().Set(Nan::New(result).ToLocalChecked()); + return String::New(env, ts_node_type(node)); } + + return env.Undefined(); } -static void TypeId(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value TypeId(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - TSSymbol result = ts_node_symbol(node); - info.GetReturnValue().Set(Nan::New(result)); + return Number::New(env, ts_node_symbol(node)); } + + return env.Undefined(); } -static void IsNamed(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value IsNamed(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - bool result = ts_node_is_named(node); - info.GetReturnValue().Set(Nan::New(result)); + return Boolean::New(env, ts_node_is_named(node)); } + + return env.Undefined(); } -static void StartIndex(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value StartIndex(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id) { int32_t result = ts_node_start_byte(node) / 2; - info.GetReturnValue().Set(Nan::New(result)); + return Number::New(env, result); } + + return env.Undefined(); } -static void EndIndex(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value EndIndex(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id) { int32_t result = ts_node_end_byte(node) / 2; - info.GetReturnValue().Set(Nan::New(result)); + return Number::New(env, result); } + + return env.Undefined(); } -static void StartPosition(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value StartPosition(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - TransferPoint(data, ts_node_start_point(node)); + TransferPoint(env, ts_node_start_point(node)); } + + return env.Undefined(); } -static void EndPosition(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value EndPosition(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - TransferPoint(data, ts_node_end_point(node)); + TransferPoint(env, ts_node_end_point(node)); } + + return env.Undefined(); } -static void Child(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value Child(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - if (!info[1]->IsUint32()) { - Nan::ThrowTypeError("Second argument must be an integer"); - return; + if (!info[1].IsNumber()) { + throw TypeError::New(env, "Second argument must be an integer"); } - uint32_t index = Nan::To(info[1]).FromJust(); - MarshalNode(info, tree, ts_node_child(node, index)); - return; + uint32_t index = info[1].As().Uint32Value(); + return MarshalNode(info, tree, ts_node_child(node, index)); } - MarshalNullNode(data); + return MarshalNullNode(env); } -static void NamedChild(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value NamedChild(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - if (!info[1]->IsUint32()) { - Nan::ThrowTypeError("Second argument must be an integer"); - return; + if (!info[1].IsNumber()) { + throw TypeError::New(env, "Second argument must be an integer"); } - uint32_t index = Nan::To(info[1]).FromJust(); - MarshalNode(info, tree, ts_node_named_child(node, index)); - return; + uint32_t index = info[1].As().Uint32Value(); + return MarshalNode(info, tree, ts_node_named_child(node, index)); } - MarshalNullNode(data); + return MarshalNullNode(env); } -static void ChildCount(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value ChildCount(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - info.GetReturnValue().Set(Nan::New(ts_node_child_count(node))); + return Number::New(env, ts_node_child_count(node)); } + + return env.Undefined(); } -static void NamedChildCount(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value NamedChildCount(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - info.GetReturnValue().Set(Nan::New(ts_node_named_child_count(node))); + return Number::New(env, ts_node_named_child_count(node)); } + + return env.Undefined(); } -static void FirstChild(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value FirstChild(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - MarshalNode(info, tree, ts_node_child(node, 0)); - return; + return MarshalNode(info, tree, ts_node_child(node, 0)); } - MarshalNullNode(data); + return MarshalNullNode(env); } -static void FirstNamedChild(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value FirstNamedChild(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - MarshalNode(info, tree, ts_node_named_child(node, 0)); - return; + return MarshalNode(info, tree, ts_node_named_child(node, 0)); } - MarshalNullNode(data); + return MarshalNullNode(env); } -static void LastChild(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value LastChild(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id) { uint32_t child_count = ts_node_child_count(node); if (child_count > 0) { - MarshalNode(info, tree, ts_node_child(node, child_count - 1)); - return; + return MarshalNode(info, tree, ts_node_child(node, child_count - 1)); } } - MarshalNullNode(data); + return MarshalNullNode(env); } -static void LastNamedChild(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value LastNamedChild(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id) { uint32_t child_count = ts_node_named_child_count(node); if (child_count > 0) { - MarshalNode(info, tree, ts_node_named_child(node, child_count - 1)); - return; + return MarshalNode(info, tree, ts_node_named_child(node, child_count - 1)); } } - MarshalNullNode(data); + return MarshalNullNode(env); } -static void Parent(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value Parent(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - MarshalNode(info, tree, ts_node_parent(node)); - return; + return MarshalNode(info, tree, ts_node_parent(node)); } - MarshalNullNode(data); + return MarshalNullNode(env); } -static void NextSibling(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value NextSibling(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - MarshalNode(info, tree, ts_node_next_sibling(node)); - return; + return MarshalNode(info, tree, ts_node_next_sibling(node)); } - MarshalNullNode(data); + return MarshalNullNode(env); } -static void NextNamedSibling(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value NextNamedSibling(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - MarshalNode(info, tree, ts_node_next_named_sibling(node)); - return; + return MarshalNode(info, tree, ts_node_next_named_sibling(node)); } - MarshalNullNode(data); + return MarshalNullNode(env); } -static void PreviousSibling(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value PreviousSibling(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - MarshalNode(info, tree, ts_node_prev_sibling(node)); - return; + return MarshalNode(info, tree, ts_node_prev_sibling(node)); } - MarshalNullNode(data); + return MarshalNullNode(env); } -static void PreviousNamedSibling(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value PreviousNamedSibling(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - MarshalNode(info, tree, ts_node_prev_named_sibling(node)); - return; + return MarshalNode(info, tree, ts_node_prev_named_sibling(node)); } - MarshalNullNode(data); + return MarshalNullNode(env); } struct SymbolSet { @@ -498,63 +504,45 @@ struct SymbolSet { bool contains(TSSymbol symbol) { return symbols.find(symbol) != symbols.npos; } }; -bool symbol_set_from_js(SymbolSet *symbols, const Local &value, const TSLanguage *language) { - if (!value->IsArray()) { - Nan::ThrowTypeError("Argument must be a string or array of strings"); - return false; +void symbol_set_from_js(SymbolSet *symbols, const Napi::Value &value, const TSLanguage *language) { + Env env = value.Env(); + + if (!value.IsArray()) { + throw TypeError::New(env, "Argument must be a string or array of strings"); } unsigned symbol_count = ts_language_symbol_count(language); - Local js_types = Local::Cast(value); - for (unsigned i = 0, n = js_types->Length(); i < n; i++) { - Local js_node_type_value; - if (Nan::Get(js_types, i).ToLocal(&js_node_type_value)) { - Local js_node_type; - if (Nan::To(js_node_type_value).ToLocal(&js_node_type)) { - auto length = js_node_type->Utf8Length( - #if NODE_MAJOR_VERSION >= 12 - Isolate::GetCurrent() - #endif - ); - - std::string node_type(length, '\0'); - js_node_type->WriteUtf8( - - // Nan doesn't wrap this functionality - #if NODE_MAJOR_VERSION >= 12 - Isolate::GetCurrent(), - #endif - - &node_type[0] - ); - - if (node_type == "ERROR") { - symbols->add(static_cast(-1)); - } else { - for (TSSymbol j = 0; j < symbol_count; j++) { - if (node_type == ts_language_symbol_name(language, j)) { - symbols->add(j); - } + auto js_types = value.As(); + for (unsigned i = 0, n = js_types.Length(); i < n; i++) { + Value js_node_type_value = js_types[i]; + if (js_node_type_value.IsString()) { + String js_node_type = js_node_type_value.As(); + std::string node_type = js_node_type.Utf8Value(); + + if (node_type == "ERROR") { + symbols->add(static_cast(-1)); + } else { + for (TSSymbol j = 0; j < symbol_count; j++) { + if (node_type == ts_language_symbol_name(language, j)) { + symbols->add(j); } } - - continue; } + + continue; } - Nan::ThrowTypeError("Argument must be a string or array of strings"); - return false; + throw TypeError::New(env, "Argument must be a string or array of strings"); } - - return true; } -static void Children(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); - if (!node.id) return; +static Napi::Value Children(const Napi::CallbackInfo &info) { + Env env = info.Env(); + AddonData* data = env.GetInstanceData(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); + if (!node.id) return env.Undefined(); vector result; ts_tree_cursor_reset(&data->scratch_cursor, node); @@ -565,14 +553,15 @@ static void Children(const Nan::FunctionCallbackInfo &info) { } while (ts_tree_cursor_goto_next_sibling(&data->scratch_cursor)); } - MarshalNodes(info, tree, result.data(), result.size()); + return MarshalNodes(info, tree, result.data(), result.size()); } -static void NamedChildren(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); - if (!node.id) return; +static Napi::Value NamedChildren(const Napi::CallbackInfo &info) { + Env env = info.Env(); + AddonData* data = env.GetInstanceData(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); + if (!node.id) return env.Undefined(); vector result; ts_tree_cursor_reset(&data->scratch_cursor, node); @@ -585,31 +574,32 @@ static void NamedChildren(const Nan::FunctionCallbackInfo &info) { } while (ts_tree_cursor_goto_next_sibling(&data->scratch_cursor)); } - MarshalNodes(info, tree, result.data(), result.size()); + return MarshalNodes(info, tree, result.data(), result.size()); } -static void DescendantsOfType(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); - if (!node.id) return; +static Napi::Value DescendantsOfType(const Napi::CallbackInfo &info) { + Env env = info.Env(); + AddonData* data = env.GetInstanceData(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); + if (!node.id) return env.Undefined(); SymbolSet symbols; - if (!symbol_set_from_js(&symbols, info[1], ts_tree_language(node.tree))) return; + symbol_set_from_js(&symbols, info[1], ts_tree_language(node.tree)); TSPoint start_point = {0, 0}; TSPoint end_point = {UINT32_MAX, UINT32_MAX}; - if (info.Length() > 2 && info[2]->IsObject()) { - auto maybe_start_point = PointFromJS(data, info[2]); - if (maybe_start_point.IsNothing()) return; - start_point = maybe_start_point.FromJust(); + if (info.Length() > 2 && info[2].IsObject()) { + auto maybe_start_point = PointFromJS(info[2]); + if (maybe_start_point.IsNothing()) return env.Undefined(); + start_point = maybe_start_point.Unwrap(); } - if (info.Length() > 3 && info[3]->IsObject()) { - auto maybe_end_point = PointFromJS(data, info[3]); - if (maybe_end_point.IsNothing()) return; - end_point = maybe_end_point.FromJust(); + if (info.Length() > 3 && info[3].IsObject()) { + auto maybe_end_point = PointFromJS(info[3]); + if (maybe_end_point.IsNothing()) return env.Undefined(); + end_point = maybe_end_point.Unwrap(); } vector found; @@ -652,21 +642,21 @@ static void DescendantsOfType(const Nan::FunctionCallbackInfo &info) { } } - MarshalNodes(info, tree, found.data(), found.size()); + return MarshalNodes(info, tree, found.data(), found.size()); } -static void ChildNodesForFieldId(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); - if (!node.id) return; +static Napi::Value ChildNodesForFieldId(const Napi::CallbackInfo &info) { + Env env = info.Env(); + AddonData* data = env.GetInstanceData(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); + if (!node.id) return env.Undefined(); - auto maybe_field_id = Nan::To(info[1]); - if (!maybe_field_id.IsJust()) { - Nan::ThrowTypeError("Second argument must be an integer"); - return; + if (!info[1].IsNumber()) { + throw TypeError::New(env, "Second argument must be an integer"); } - uint32_t field_id = maybe_field_id.FromJust(); + auto maybe_field_id = info[1].As(); + uint32_t field_id = maybe_field_id.Uint32Value(); vector result; ts_tree_cursor_reset(&data->scratch_cursor, node); @@ -679,61 +669,58 @@ static void ChildNodesForFieldId(const Nan::FunctionCallbackInfo &info) { } while (ts_tree_cursor_goto_next_sibling(&data->scratch_cursor)); } - MarshalNodes(info, tree, result.data(), result.size()); + return MarshalNodes(info, tree, result.data(), result.size()); } -static void ChildNodeForFieldId(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value ChildNodeForFieldId(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - auto maybe_field_id = Nan::To(info[1]); - if (!maybe_field_id.IsJust()) { - Nan::ThrowTypeError("Second argument must be an integer"); - return; + if (!info[1].IsNumber()) { + throw TypeError::New(env, "Second argument must be an integer"); } - uint32_t field_id = maybe_field_id.FromJust(); - MarshalNode(info, tree, ts_node_child_by_field_id(node, field_id)); - return; + auto maybe_field_id = info[1].As(); + uint32_t field_id = maybe_field_id.Uint32Value(); + return MarshalNode(info, tree, ts_node_child_by_field_id(node, field_id)); } - MarshalNullNode(data); + return MarshalNullNode(env); } -static void Closest(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); - if (!node.id) return; +static Napi::Value Closest(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); + if (!node.id) return env.Undefined(); SymbolSet symbols; - if (!symbol_set_from_js(&symbols, info[1], ts_tree_language(node.tree))) return; + symbol_set_from_js(&symbols, info[1], ts_tree_language(node.tree)); for (;;) { TSNode parent = ts_node_parent(node); if (!parent.id) break; if (symbols.contains(ts_node_symbol(parent))) { - MarshalNode(info, tree, parent); - return; + return MarshalNode(info, tree, parent); } node = parent; } - MarshalNullNode(data); + return MarshalNullNode(env); } -static void Walk(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - TSNode node = UnmarshalNode(data, tree); +static Napi::Value Walk(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); TSTreeCursor cursor = ts_tree_cursor_new(node); - info.GetReturnValue().Set(TreeCursor::NewInstance(data, cursor)); + return TreeCursor::NewInstance(env, cursor); } -void Init(Local exports, Local data_ext) { - AddonData* data = reinterpret_cast(data_ext->Value()); +void Init(Napi::Env env, Napi::Object exports) { + auto data = env.GetInstanceData(); - Local result = Nan::New(); + Object result = Object::New(env); FunctionPair methods[] = { {"startIndex", StartIndex}, @@ -776,17 +763,13 @@ void Init(Local exports, Local data_ext) { }; for (size_t i = 0; i < length_of_array(methods); i++) { - Nan::Set( - result, - Nan::New(methods[i].name).ToLocalChecked(), - Nan::GetFunction(Nan::New(methods[i].callback, data_ext)).ToLocalChecked() - ); + result[methods[i].name] = Napi::Function::New(env, methods[i].callback); } - data->module_exports.Reset(exports); - setup_transfer_buffer(data, 1); + data->module_exports = Napi::Persistent(exports); + setup_transfer_buffer(env, 1); - Nan::Set(exports, Nan::New("NodeMethods").ToLocalChecked(), result); + exports["NodeMethods"] = result; } } // namespace node_methods diff --git a/src/node.h b/src/node.h index 872fa14b..f4415ff2 100644 --- a/src/node.h +++ b/src/node.h @@ -1,22 +1,20 @@ #ifndef NODE_TREE_SITTER_NODE_H_ #define NODE_TREE_SITTER_NODE_H_ -#include -#include -#include +#include #include #include "./tree.h" -using namespace v8; +using namespace Napi; namespace node_tree_sitter { namespace node_methods { -void Init(v8::Local, v8::Local); -void MarshalNode(const Nan::FunctionCallbackInfo &info, const Tree *, TSNode); -Local GetMarshalNode(const Nan::FunctionCallbackInfo &info, const Tree *tree, TSNode node); -Local GetMarshalNodes(const Nan::FunctionCallbackInfo &info, const Tree *tree, const TSNode *nodes, uint32_t node_count); -TSNode UnmarshalNode(AddonData* data, const Tree *tree); +void Init(Napi::Env, Napi::Object); +Napi::Value MarshalNode(const Napi::CallbackInfo &info, const Tree *, TSNode); +Napi::Value GetMarshalNode(const Napi::CallbackInfo &info, const Tree *tree, TSNode node); +Napi::Value GetMarshalNodes(const Napi::CallbackInfo &info, const Tree *tree, const TSNode *nodes, uint32_t node_count); +TSNode UnmarshalNode(Napi::Env env, const Tree *tree); static inline const void *UnmarshalNodeId(const uint32_t *buffer) { const void *result; diff --git a/src/parser.cc b/src/parser.cc index 8507afb0..beabb08c 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -2,8 +2,7 @@ #include #include #include -#include -#include +#include #include "./conversions.h" #include "./language.h" #include "./logger.h" @@ -11,22 +10,22 @@ #include "./util.h" #include -namespace node_tree_sitter { - -using namespace v8; +using namespace Napi; using std::vector; using std::pair; +namespace node_tree_sitter { + class CallbackInput { - public: - CallbackInput(AddonData* data, v8::Local callback, v8::Local js_buffer_size) - : data(data), - callback(callback), - byte_offset(0), - partial_string_offset(0) { - uint32_t buffer_size = Nan::To(js_buffer_size).FromMaybe(0); - if (buffer_size == 0) buffer_size = 32 * 1024; - buffer.resize(buffer_size); + public: + CallbackInput(Function callback, Napi::Value js_buffer_size) + : byte_offset(0) { + this->callback.Reset(callback, 1); + if (js_buffer_size.IsNumber()) { + buffer.resize(js_buffer_size.As().Uint32Value()); + } else { + buffer.resize(32 * 1024); + } } TSInput Input() { @@ -37,114 +36,104 @@ class CallbackInput { return result; } - private: + private: + static String slice(String s, uint32_t offset) { + Env env = s.Env(); + auto data = env.GetInstanceData(); + return data->string_slice.Call(s, {Number::New(s.Env(), offset)}).As(); + } + static const char * Read(void *payload, uint32_t byte, TSPoint position, uint32_t *bytes_read) { CallbackInput *reader = (CallbackInput *)payload; + Napi::Env env = reader->callback.Env(); if (byte != reader->byte_offset) { reader->byte_offset = byte; - reader->partial_string_offset = 0; reader->partial_string.Reset(); } *bytes_read = 0; - Local result; - uint32_t start = 0; - if (reader->partial_string_offset) { - result = Nan::New(reader->partial_string); - start = reader->partial_string_offset; + String result; + if (!reader->partial_string.IsEmpty()) { + result = reader->partial_string.Value().As(); } else { - Local callback = Nan::New(reader->callback); - uint32_t utf16_unit = byte / 2; - Local argv[2] = { Nan::New(utf16_unit), PointToJS(reader->data, position) }; - TryCatch try_catch(Isolate::GetCurrent()); - auto maybe_result_value = Nan::Call(callback, GetGlobal(callback), 2, argv); - if (try_catch.HasCaught()) return nullptr; - - Local result_value; - if (!maybe_result_value.ToLocal(&result_value)) return nullptr; - if (!result_value->IsString()) return nullptr; - if (!Nan::To(result_value).ToLocal(&result)) return nullptr; + Function callback = reader->callback.Value(); + Napi::Value result_value = callback({ + ByteCountToJS(env, byte), + PointToJS(env, position), + }); + if (env.IsExceptionPending()) return nullptr; + if (!result_value.IsString()) return nullptr; + result = result_value.As(); } - int utf16_units_read = result->Write( - - // Nan doesn't wrap this functionality - #if NODE_MAJOR_VERSION >= 12 - Isolate::GetCurrent(), - #endif - - reader->buffer.data(), - start, - reader->buffer.size(), - String::NO_NULL_TERMINATION + size_t length = 0; + size_t utf16_units_read = 0; + napi_status status; + status = napi_get_value_string_utf16( + env, result, nullptr, 0, &length ); - int end = start + utf16_units_read; - *bytes_read = 2 * utf16_units_read; + if (status != napi_ok) return nullptr; + status = napi_get_value_string_utf16( + env, result, (char16_t *)&reader->buffer[0], reader->buffer.size(), &utf16_units_read + ); + if (status != napi_ok) return nullptr; + *bytes_read = 2 * utf16_units_read; reader->byte_offset += *bytes_read; - if (end < result->Length()) { - reader->partial_string_offset = end; - reader->partial_string.Reset(result); + if (utf16_units_read < length) { + reader->partial_string.Reset(slice(result, utf16_units_read)); } else { - reader->partial_string_offset = 0; reader->partial_string.Reset(); } return (const char *)reader->buffer.data(); } - AddonData* data; - Nan::Persistent callback; + FunctionReference callback; std::vector buffer; size_t byte_offset; - Nan::Persistent partial_string; - size_t partial_string_offset; + Reference partial_string; }; -void Parser::Init(Local exports, Local data_ext) { - AddonData* data = reinterpret_cast(data_ext->Value()); - Local tpl = Nan::New(New, data_ext); - tpl->InstanceTemplate()->SetInternalFieldCount(1); - Local class_name = Nan::New("Parser").ToLocalChecked(); - tpl->SetClassName(class_name); - - FunctionPair methods[] = { - {"getLogger", GetLogger}, - {"setLogger", SetLogger}, - {"setLanguage", SetLanguage}, - {"printDotGraphs", PrintDotGraphs}, - {"parse", Parse}, - }; - - for (size_t i = 0; i < length_of_array(methods); i++) { - Nan::SetPrototypeMethod(tpl, methods[i].name, methods[i].callback, data_ext); - } +void Parser::Init(Napi::Env env, Napi::Object exports) { + auto data = env.GetInstanceData(); + + Function ctor = DefineClass(env, "Parser", { + InstanceMethod("getLogger", &Parser::GetLogger, napi_default_method), + InstanceMethod("setLogger", &Parser::SetLogger, napi_default_method), + InstanceMethod("setLanguage", &Parser::SetLanguage, napi_default_method), + InstanceMethod("printDotGraphs", &Parser::PrintDotGraphs, napi_default_method), + InstanceMethod("parse", &Parser::Parse, napi_default_method), + }); - data->parser_constructor.Reset(Nan::Persistent(Nan::GetFunction(tpl).ToLocalChecked())); - Nan::Set(exports, class_name, Nan::New(data->parser_constructor)); - Nan::Set(exports, Nan::New("LANGUAGE_VERSION").ToLocalChecked(), Nan::New(TREE_SITTER_LANGUAGE_VERSION)); + data->parser_constructor = Napi::Persistent(ctor); + exports["Parser"] = ctor; + exports["LANGUAGE_VERSION"] = Number::New(env, TREE_SITTER_LANGUAGE_VERSION); + + String s = String::New(env, ""); + Napi::Value string_slice_value = s.As()["slice"]; + data->string_slice = Napi::Persistent(string_slice_value.As()); } -Parser::Parser() : parser_(ts_parser_new()) {} +Parser::Parser(const Napi::CallbackInfo &info) : Napi::ObjectWrap(info), parser_(ts_parser_new()) {} Parser::~Parser() { ts_parser_delete(parser_); } -static bool handle_included_ranges(AddonData* data, TSParser *parser, Local arg) { +static bool handle_included_ranges(Napi::Env env, TSParser *parser, Napi::Value arg) { uint32_t last_included_range_end = 0; - if (arg->IsArray()) { - auto js_included_ranges = Local::Cast(arg); + if (arg.IsArray()) { + auto js_included_ranges = arg.As(); vector included_ranges; - for (unsigned i = 0; i < js_included_ranges->Length(); i++) { - Local range_value; - if (!Nan::Get(js_included_ranges, i).ToLocal(&range_value)) return false; - auto maybe_range = RangeFromJS(data, range_value); + for (unsigned i = 0; i < js_included_ranges.Length(); i++) { + Value range_value = js_included_ranges[i]; + if (!range_value.IsObject()) return false; + auto maybe_range = RangeFromJS(range_value); if (!maybe_range.IsJust()) return false; - auto range = maybe_range.FromJust(); + auto range = maybe_range.Unwrap(); if (range.start_byte < last_included_range_end) { - Nan::ThrowRangeError("Overlapping ranges"); - return false; + throw RangeError::New(env, "Overlapping ranges"); } last_included_range_end = range.end_byte; included_ranges.push_back(range); @@ -157,107 +146,80 @@ static bool handle_included_ranges(AddonData* data, TSParser *parser, Local &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - if (info.IsConstructCall()) { - Parser *parser = new Parser(); - parser->Wrap(info.This()); - info.GetReturnValue().Set(info.This()); - } else { - Local self; - MaybeLocal maybe_self = Nan::New(data->parser_constructor)->NewInstance(Nan::GetCurrentContext()); - if (maybe_self.ToLocal(&self)) { - info.GetReturnValue().Set(self); - } else { - info.GetReturnValue().Set(Nan::Null()); - } - } -} - -void Parser::SetLanguage(const Nan::FunctionCallbackInfo &info) { - Parser *parser = ObjectWrap::Unwrap(info.This()); - +Napi::Value Parser::SetLanguage(const Napi::CallbackInfo &info) { const TSLanguage *language = language_methods::UnwrapLanguage(info[0]); if (language) { - ts_parser_set_language(parser->parser_, language); - info.GetReturnValue().Set(info.This()); + ts_parser_set_language(parser_, language); + return info.This(); } + return info.Env().Undefined(); } -void Parser::Parse(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - Parser *parser = ObjectWrap::Unwrap(info.This()); +Napi::Value Parser::Parse(const CallbackInfo &info) { + Napi::Env env = info.Env(); - if (!info[0]->IsFunction()) { - Nan::ThrowTypeError("Input must be a function"); - return; + if (!info[0].IsFunction()) { + throw TypeError::New(env, "Input must be a function"); } - Local callback = Local::Cast(info[0]); + Function callback = info[0].As(); - Local js_old_tree; + Object js_old_tree; const TSTree *old_tree = nullptr; - if (info.Length() > 1 && !info[1]->IsNull() && !info[1]->IsUndefined() && Nan::To(info[1]).ToLocal(&js_old_tree)) { - const Tree *tree = Tree::UnwrapTree(data, js_old_tree); + if (info.Length() > 1 && !info[1].IsNull() && !info[1].IsUndefined() && info[1].IsObject()) { + js_old_tree = info[1].As(); + const Tree *tree = Tree::UnwrapTree(js_old_tree); if (!tree) { - Nan::ThrowTypeError("Second argument must be a tree"); - return; + throw TypeError::New(env, "Second argument must be a tree"); } old_tree = tree->tree_; } - Local buffer_size = Nan::Null(); + Napi::Value buffer_size = env.Null(); if (info.Length() > 2) buffer_size = info[2]; - if (!handle_included_ranges(data, parser->parser_, info[3])) return; + if (!handle_included_ranges(env, parser_, info[3])) return env.Undefined(); - CallbackInput callback_input(data, callback, buffer_size); - TSTree *tree = ts_parser_parse(parser->parser_, old_tree, callback_input.Input()); - Local result = Tree::NewInstance(data, tree); - info.GetReturnValue().Set(result); + CallbackInput callback_input(callback, buffer_size); + TSTree *tree = ts_parser_parse(parser_, old_tree, callback_input.Input()); + Napi::Value result = Tree::NewInstance(env, tree); + return result; } -void Parser::GetLogger(const Nan::FunctionCallbackInfo &info) { - Parser *parser = ObjectWrap::Unwrap(info.This()); - - TSLogger current_logger = ts_parser_logger(parser->parser_); +Napi::Value Parser::GetLogger(const Napi::CallbackInfo &info) { + TSLogger current_logger = ts_parser_logger(parser_); if (current_logger.payload && current_logger.log == Logger::Log) { Logger *logger = (Logger *)current_logger.payload; - info.GetReturnValue().Set(Nan::New(logger->func)); + return logger->func.Value(); } else { - info.GetReturnValue().Set(Nan::Null()); + return info.Env().Null(); } } -void Parser::SetLogger(const Nan::FunctionCallbackInfo &info) { - Parser *parser = ObjectWrap::Unwrap(info.This()); - - TSLogger current_logger = ts_parser_logger(parser->parser_); +Napi::Value Parser::SetLogger(const Napi::CallbackInfo &info) { + TSLogger current_logger = ts_parser_logger(parser_); - if (info[0]->IsFunction()) { + if (info[0].IsFunction()) { if (current_logger.payload) delete (Logger *)current_logger.payload; - ts_parser_set_logger(parser->parser_, Logger::Make(Local::Cast(info[0]))); - } else if (!Nan::To(info[0]).FromMaybe(true)) { + ts_parser_set_logger(parser_, Logger::Make(info[0].As())); + } else if (info[0].IsEmpty() || info[0].IsUndefined() || info[0].IsNull() || (info[0].IsBoolean() && !info[0].As())) { if (current_logger.payload) delete (Logger *)current_logger.payload; - ts_parser_set_logger(parser->parser_, { 0, 0 }); + ts_parser_set_logger(parser_, { 0, 0 }); } else { - Nan::ThrowTypeError("Logger callback must either be a function or a falsy value"); - return; + throw TypeError::New(info.Env(), "Logger callback must either be a function or a falsy value"); } - info.GetReturnValue().Set(info.This()); + return info.This(); } -void Parser::PrintDotGraphs(const Nan::FunctionCallbackInfo &info) { - Parser *parser = ObjectWrap::Unwrap(info.This()); - - if (Nan::To(info[0]).FromMaybe(false)) { - ts_parser_print_dot_graphs(parser->parser_, 2); +Napi::Value Parser::PrintDotGraphs(const Napi::CallbackInfo &info) { + if (info[0].IsBoolean() && info[0].As()) { + ts_parser_print_dot_graphs(parser_, 2); } else { - ts_parser_print_dot_graphs(parser->parser_, -1); + ts_parser_print_dot_graphs(parser_, -1); } - info.GetReturnValue().Set(info.This()); + return info.This(); } } // namespace node_tree_sitter diff --git a/src/parser.h b/src/parser.h index 07868c4b..0756529c 100644 --- a/src/parser.h +++ b/src/parser.h @@ -1,29 +1,26 @@ #ifndef NODE_TREE_SITTER_PARSER_H_ #define NODE_TREE_SITTER_PARSER_H_ -#include -#include +#include #include #include namespace node_tree_sitter { -class Parser : public Nan::ObjectWrap { +class Parser : public Napi::ObjectWrap { public: - static void Init(v8::Local exports, v8::Local data_ext); + static void Init(Napi::Env env, Napi::Object exports); + explicit Parser(const Napi::CallbackInfo &info); + ~Parser(); +private: TSParser *parser_; - private: - explicit Parser(); - ~Parser(); - - static void New(const Nan::FunctionCallbackInfo &); - static void SetLanguage(const Nan::FunctionCallbackInfo &); - static void GetLogger(const Nan::FunctionCallbackInfo &); - static void SetLogger(const Nan::FunctionCallbackInfo &); - static void Parse(const Nan::FunctionCallbackInfo &); - static void PrintDotGraphs(const Nan::FunctionCallbackInfo &); + Napi::Value SetLanguage(const Napi::CallbackInfo &); + Napi::Value GetLogger(const Napi::CallbackInfo &); + Napi::Value SetLogger(const Napi::CallbackInfo &); + Napi::Value Parse(const Napi::CallbackInfo &); + Napi::Value PrintDotGraphs(const Napi::CallbackInfo &); }; } // namespace node_tree_sitter diff --git a/src/query.cc b/src/query.cc index 19f6c184..05aecc5f 100644 --- a/src/query.cc +++ b/src/query.cc @@ -1,18 +1,18 @@ #include "./query.h" #include #include -#include -#include +#include #include "./node.h" #include "./language.h" #include "./logger.h" #include "./util.h" #include "./conversions.h" +using std::vector; +using namespace Napi; + namespace node_tree_sitter { -using std::vector; -using namespace v8; using node_methods::UnmarshalNodeId; const char *query_error_names[] = { @@ -24,97 +24,47 @@ const char *query_error_names[] = { "TSQueryErrorStructure", }; -void Query::Init(Local exports, Local data_ext) { - AddonData* data = reinterpret_cast(data_ext->Value()); +void Query::Init(Napi::Env env, Napi::Object exports) { + auto data = env.GetInstanceData(); data->ts_query_cursor = ts_query_cursor_new(); - Local tpl = Nan::New(New, data_ext); - tpl->InstanceTemplate()->SetInternalFieldCount(1); - Local class_name = Nan::New("Query").ToLocalChecked(); - tpl->SetClassName(class_name); - - FunctionPair methods[] = { - {"_matches", Matches}, - {"_captures", Captures}, - {"_getPredicates", GetPredicates}, - }; - - for (size_t i = 0; i < length_of_array(methods); i++) { - Nan::SetPrototypeMethod(tpl, methods[i].name, methods[i].callback, data_ext); - } - - Local ctor = Nan::GetFunction(tpl).ToLocalChecked(); - - data->query_constructor_template.Reset(tpl); - data->query_constructor.Reset(ctor); - Nan::Set(exports, class_name, ctor); -} - -Query::Query(TSQuery *query) : query_(query) {} + Function ctor = DefineClass(env, "Query", { + InstanceMethod("_matches", &Query::Matches, napi_default_method), + InstanceMethod("_captures", &Query::Captures, napi_default_method), + InstanceMethod("_getPredicates", &Query::GetPredicates, napi_default_method), + }); -Query::~Query() { - ts_query_delete(query_); + data->query_constructor = Napi::Persistent(ctor); + exports["Query"] = ctor; } -Local Query::NewInstance(AddonData* data, TSQuery *query) { - if (query) { - Local self; - MaybeLocal maybe_self = Nan::NewInstance(Nan::New(data->query_constructor)); - if (maybe_self.ToLocal(&self)) { - (new Query(query))->Wrap(self); - return self; - } - } - return Nan::Null(); -} - -Query *Query::UnwrapQuery(AddonData* data, const Local &value) { - if (!value->IsObject()) return nullptr; - Local js_query = Local::Cast(value); - if (!Nan::New(data->query_constructor_template)->HasInstance(js_query)) return nullptr; - return ObjectWrap::Unwrap(js_query); -} - -void Query::New(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - if (!info.IsConstructCall()) { - Local self; - MaybeLocal maybe_self = Nan::New(data->query_constructor)->NewInstance(Nan::GetCurrentContext()); - if (maybe_self.ToLocal(&self)) { - info.GetReturnValue().Set(self); - } else { - info.GetReturnValue().Set(Nan::Null()); - } - return; - } +Query::Query(const Napi::CallbackInfo &info) : Napi::ObjectWrap(info) , query_(nullptr) { + Napi::Env env = info.Env(); const TSLanguage *language = language_methods::UnwrapLanguage(info[0]); const char *source; uint32_t source_len; uint32_t error_offset = 0; TSQueryError error_type = TSQueryErrorNone; - TSQuery *query; if (language == nullptr) { - Nan::ThrowError("Missing language argument"); - return; + throw Error::New(env, "Missing language argument"); } - if (info[1]->IsString()) { - auto string = Nan::To (info[1]).ToLocalChecked(); - Nan::Utf8String utf8_string(string); - source = *utf8_string; + if (info[1].IsString()) { + auto string = info[1].As(); + std::string utf8_string = string.Utf8Value(); + source = utf8_string.data(); source_len = utf8_string.length(); - query = ts_query_new(language, source, source_len, &error_offset, &error_type); - } - else if (node::Buffer::HasInstance(info[1])) { - source = node::Buffer::Data(info[1]); - source_len = node::Buffer::Length(info[1]); - query = ts_query_new(language, source, source_len, &error_offset, &error_type); + query_ = ts_query_new(language, source, source_len, &error_offset, &error_type); + } else if (info[1].IsBuffer()) { + Buffer buf = info[1].As>(); + source = buf.Data(); + source_len = buf.Length(); + query_ = ts_query_new(language, source, source_len, &error_offset, &error_type); } else { - Nan::ThrowError("Missing source argument"); - return; + throw Error::New(env, "Missing source argument"); } if (error_offset > 0) { @@ -123,42 +73,42 @@ void Query::New(const Nan::FunctionCallbackInfo &info) { message += error_name; message += " at position "; message += std::to_string(error_offset); - Nan::ThrowError(message.c_str()); - return; + throw Error::New(env, message.c_str()); } - auto self = info.This(); - - Query *query_wrapper = new Query(query); - query_wrapper->Wrap(self); + info.This().As().Get("_init").As().Call(info.This(), {}); +} - auto init = - Nan::To( - Nan::Get(self, Nan::New("_init").ToLocalChecked()).ToLocalChecked() - ).ToLocalChecked(); - Nan::Call(init, self, 0, nullptr); +Query::~Query() { + ts_query_delete(query_); +} - info.GetReturnValue().Set(self); +Query *Query::UnwrapQuery(const Napi::Value &value) { + auto data = value.Env().GetInstanceData(); + if (!value.IsObject()) return nullptr; + Napi::Object js_query = value.As(); + if (!js_query.InstanceOf(data->query_constructor.Value())) return nullptr; + return Query::Unwrap(js_query); } -void Query::GetPredicates(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - Query *query = Query::UnwrapQuery(data, info.This()); +Napi::Value Query::GetPredicates(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + Query *query = Query::UnwrapQuery(info.This()); auto ts_query = query->query_; auto pattern_len = ts_query_pattern_count(ts_query); - Local js_predicates = Nan::New(); + Array js_predicates = Array::New(env); for (size_t pattern_index = 0; pattern_index < pattern_len; pattern_index++) { uint32_t predicates_len; const TSQueryPredicateStep *predicates = ts_query_predicates_for_pattern( ts_query, pattern_index, &predicates_len); - Local js_pattern_predicates = Nan::New(); + Array js_pattern_predicates = Array::New(env); if (predicates_len > 0) { - Local js_predicate = Nan::New(); + Array js_predicate = Array::New(env); size_t a_index = 0; size_t p_index = 0; @@ -167,67 +117,76 @@ void Query::GetPredicates(const Nan::FunctionCallbackInfo &info) { uint32_t len; switch (predicate.type) { case TSQueryPredicateStepTypeCapture: - Nan::Set(js_predicate, p_index++, Nan::New(TSQueryPredicateStepTypeCapture)); - Nan::Set(js_predicate, p_index++, - Nan::New( - ts_query_capture_name_for_id(ts_query, predicate.value_id, &len) - ).ToLocalChecked()); + js_predicate[p_index++] = Number::New(env, TSQueryPredicateStepTypeCapture); + js_predicate[p_index++] = String::New(env, + ts_query_capture_name_for_id(ts_query, predicate.value_id, &len) + ); break; case TSQueryPredicateStepTypeString: - Nan::Set(js_predicate, p_index++, Nan::New(TSQueryPredicateStepTypeString)); - Nan::Set(js_predicate, p_index++, - Nan::New( - ts_query_string_value_for_id(ts_query, predicate.value_id, &len) - ).ToLocalChecked()); + js_predicate[p_index++] = Number::New(env, TSQueryPredicateStepTypeString); + js_predicate[p_index++] = String::New(env, + ts_query_string_value_for_id(ts_query, predicate.value_id, &len) + ); break; case TSQueryPredicateStepTypeDone: - Nan::Set(js_pattern_predicates, a_index++, js_predicate); - js_predicate = Nan::New(); + js_pattern_predicates[a_index++] = js_predicate; + js_predicate = Array::New(env); p_index = 0; break; } } } - Nan::Set(js_predicates, pattern_index, js_pattern_predicates); + js_predicates[pattern_index] = js_pattern_predicates; } - info.GetReturnValue().Set(js_predicates); + return js_predicates; } -void Query::Matches(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - Query *query = Query::UnwrapQuery(data, info.This()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - uint32_t start_row = Nan::To(info[1]).ToChecked(); - uint32_t start_column = Nan::To(info[2]).ToChecked() << 1; - uint32_t end_row = Nan::To(info[3]).ToChecked(); - uint32_t end_column = Nan::To(info[4]).ToChecked() << 1; +Napi::Value Query::Matches(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + auto data = env.GetInstanceData(); + Query *query = Query::UnwrapQuery(info.This()); + const Tree *tree = Tree::UnwrapTree(info[0]); + uint32_t start_row = 0; + if (info.Length() > 1 && info[1].IsNumber()) { + start_row = info[1].As().Uint32Value(); + } + uint32_t start_column = 0; + if (info.Length() > 2 && info[2].IsNumber()) { + start_column = info[2].As().Uint32Value() << 1; + } + uint32_t end_row = 0; + if (info.Length() > 3 && info[3].IsNumber()) { + end_row = info[3].As().Uint32Value(); + } + uint32_t end_column = 0; + if (info.Length() > 4 && info[4].IsNumber()) { + end_column = info[4].As().Uint32Value() << 1; + } if (query == nullptr) { - Nan::ThrowError("Missing argument query"); - return; + throw Error::New(env, "Missing argument query"); } if (tree == nullptr) { - Nan::ThrowError("Missing argument tree"); - return; + throw Error::New(env, "Missing argument tree"); } TSQuery *ts_query = query->query_; - TSNode rootNode = node_methods::UnmarshalNode(data, tree); + TSNode rootNode = node_methods::UnmarshalNode(env, tree); TSPoint start_point = {start_row, start_column}; TSPoint end_point = {end_row, end_column}; ts_query_cursor_set_point_range(data->ts_query_cursor, start_point, end_point); ts_query_cursor_exec(data->ts_query_cursor, ts_query, rootNode); - Local js_matches = Nan::New(); + Array js_matches = Array::New(env); unsigned index = 0; vector nodes; TSQueryMatch match; while (ts_query_cursor_next_match(data->ts_query_cursor, &match)) { - Nan::Set(js_matches, index++, Nan::New(match.pattern_index)); + js_matches[index++] = Number::New(env, match.pattern_index); for (uint16_t i = 0; i < match.capture_count; i++) { const TSQueryCapture &capture = match.captures[i]; @@ -239,46 +198,57 @@ void Query::Matches(const Nan::FunctionCallbackInfo &info) { TSNode node = capture.node; nodes.push_back(node); - Local js_capture = Nan::New(capture_name).ToLocalChecked(); - Nan::Set(js_matches, index++, js_capture); + String js_capture = String::New(env, capture_name);; + js_matches[index++] = js_capture; } } auto js_nodes = node_methods::GetMarshalNodes(info, tree, nodes.data(), nodes.size()); - auto result = Nan::New(); - Nan::Set(result, 0, js_matches); - Nan::Set(result, 1, js_nodes); - info.GetReturnValue().Set(result); + auto result = Array::New(env); + result[0u] = js_matches; + result[1] = js_nodes; + return result; } -void Query::Captures(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - Query *query = Query::UnwrapQuery(data, info.This()); - const Tree *tree = Tree::UnwrapTree(data, info[0]); - uint32_t start_row = Nan::To(info[1]).ToChecked(); - uint32_t start_column = Nan::To(info[2]).ToChecked() << 1; - uint32_t end_row = Nan::To(info[3]).ToChecked(); - uint32_t end_column = Nan::To(info[4]).ToChecked() << 1; +Napi::Value Query::Captures(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + auto data = env.GetInstanceData(); + Query *query = Query::UnwrapQuery(info.This()); + const Tree *tree = Tree::UnwrapTree(info[0]); + uint32_t start_row = 0; + if (info.Length() > 1 && info[1].IsNumber()) { + start_row = info[1].As().Uint32Value(); + } + uint32_t start_column = 0; + if (info.Length() > 2 && info[2].IsNumber()) { + start_column = info[2].As().Uint32Value() << 1; + } + uint32_t end_row = 0; + if (info.Length() > 3 && info[3].IsNumber()) { + end_row = info[3].As().Uint32Value(); + } + uint32_t end_column = 0; + if (info.Length() > 4 && info[4].IsNumber()) { + end_column = info[4].As().Uint32Value() << 1; + } if (query == nullptr) { - Nan::ThrowError("Missing argument query"); - return; + throw Error::New(env, "Missing argument query"); } if (tree == nullptr) { - Nan::ThrowError("Missing argument tree"); - return; + throw Error::New(env, "Missing argument tree"); } TSQuery *ts_query = query->query_; - TSNode rootNode = node_methods::UnmarshalNode(data, tree); + TSNode rootNode = node_methods::UnmarshalNode(env, tree); TSPoint start_point = {start_row, start_column}; TSPoint end_point = {end_row, end_column}; ts_query_cursor_set_point_range(data->ts_query_cursor, start_point, end_point); ts_query_cursor_exec(data->ts_query_cursor, ts_query, rootNode); - Local js_matches = Nan::New(); + Array js_matches = Array::New(env); unsigned index = 0; vector nodes; TSQueryMatch match; @@ -290,8 +260,8 @@ void Query::Captures(const Nan::FunctionCallbackInfo &info) { &capture_index )) { - Nan::Set(js_matches, index++, Nan::New(match.pattern_index)); - Nan::Set(js_matches, index++, Nan::New(capture_index)); + js_matches[index++] = Number::New(env, match.pattern_index); + js_matches[index++] = Number::New(env, capture_index); for (uint16_t i = 0; i < match.capture_count; i++) { const TSQueryCapture &capture = match.captures[i]; @@ -303,18 +273,17 @@ void Query::Captures(const Nan::FunctionCallbackInfo &info) { TSNode node = capture.node; nodes.push_back(node); - Local js_capture = Nan::New(capture_name).ToLocalChecked(); - Nan::Set(js_matches, index++, js_capture); + String js_capture = String::New(env, capture_name);; + js_matches[index++] = js_capture; } } auto js_nodes = node_methods::GetMarshalNodes(info, tree, nodes.data(), nodes.size()); - auto result = Nan::New(); - Nan::Set(result, 0, js_matches); - Nan::Set(result, 1, js_nodes); - info.GetReturnValue().Set(result); + auto result = Array::New(env); + result[0u] = js_matches; + result[1] = js_nodes; + return result; } - } // namespace node_tree_sitter diff --git a/src/query.h b/src/query.h index 5b48a931..921afdf8 100644 --- a/src/query.h +++ b/src/query.h @@ -1,8 +1,7 @@ #ifndef NODE_TREE_SITTER_QUERY_H_ #define NODE_TREE_SITTER_QUERY_H_ -#include -#include +#include #include #include #include @@ -10,22 +9,22 @@ namespace node_tree_sitter { -class Query : public Nan::ObjectWrap { +class Query : public Napi::ObjectWrap { public: - static void Init(v8::Local exports, v8::Local data_ext); - static v8::Local NewInstance(AddonData* data, TSQuery *); - static Query *UnwrapQuery(AddonData* data, const v8::Local &); + static void Init(Napi::Env env, Napi::Object exports); + static Query *UnwrapQuery(const Napi::Value &); + + explicit Query(const Napi::CallbackInfo &info); + ~Query(); TSQuery *query_; private: - explicit Query(TSQuery *); - ~Query(); - static void New(const Nan::FunctionCallbackInfo &); - static void Matches(const Nan::FunctionCallbackInfo &); - static void Captures(const Nan::FunctionCallbackInfo &); - static void GetPredicates(const Nan::FunctionCallbackInfo &); + Napi::Value New(const Napi::CallbackInfo &); + Napi::Value Matches(const Napi::CallbackInfo &); + Napi::Value Captures(const Napi::CallbackInfo &); + Napi::Value GetPredicates(const Napi::CallbackInfo &); }; } // namespace node_tree_sitter diff --git a/src/tree.cc b/src/tree.cc index 0ad0cd1f..967e7a3f 100644 --- a/src/tree.cc +++ b/src/tree.cc @@ -1,47 +1,36 @@ #include "./tree.h" #include "./addon_data.h" #include -#include -#include +#include #include "./node.h" #include "./logger.h" #include "./util.h" #include "./conversions.h" +using namespace Napi; + namespace node_tree_sitter { -using namespace v8; using node_methods::UnmarshalNodeId; -void Tree::Init(Local exports, Local data_ext) { - AddonData* data = reinterpret_cast(data_ext->Value()); - Local tpl = Nan::New(New, data_ext); - tpl->InstanceTemplate()->SetInternalFieldCount(1); - Local class_name = Nan::New("Tree").ToLocalChecked(); - tpl->SetClassName(class_name); - - FunctionPair methods[] = { - {"edit", Edit}, - {"rootNode", RootNode}, - {"printDotGraph", PrintDotGraph}, - {"getChangedRanges", GetChangedRanges}, - {"getEditedRange", GetEditedRange}, - {"_cacheNode", CacheNode}, - {"_cacheNodes", CacheNodes}, - }; - - for (size_t i = 0; i < length_of_array(methods); i++) { - Nan::SetPrototypeMethod(tpl, methods[i].name, methods[i].callback, data_ext); - } - - Local ctor = Nan::GetFunction(tpl).ToLocalChecked(); - - data->tree_constructor_template.Reset(tpl); - data->tree_constructor.Reset(ctor); - Nan::Set(exports, class_name, ctor); +void Tree::Init(Napi::Env env, Napi::Object exports) { + auto data = env.GetInstanceData(); + + Function ctor = DefineClass(env, "Tree", { + InstanceMethod("edit", &Tree::Edit, napi_default_method), + InstanceMethod("rootNode", &Tree::RootNode, napi_default_method), + InstanceMethod("printDotGraph", &Tree::PrintDotGraph, napi_default_method), + InstanceMethod("getChangedRanges", &Tree::GetChangedRanges, napi_default_method), + InstanceMethod("getEditedRange", &Tree::GetEditedRange, napi_default_method), + InstanceMethod("_cacheNode", &Tree::CacheNode, napi_default_method), + InstanceMethod("_cacheNodes", &Tree::CacheNodes, napi_default_method), + }); + + data->tree_constructor = Napi::Persistent(ctor); + exports["Tree"] = ctor; } -Tree::Tree(TSTree *tree) : tree_(tree) {} +Tree::Tree(const Napi::CallbackInfo &info) : Napi::ObjectWrap(info), tree_(nullptr) {} Tree::~Tree() { ts_tree_delete(tree_); @@ -50,44 +39,40 @@ Tree::~Tree() { } } -Local Tree::NewInstance(AddonData* data, TSTree *tree) { +Napi::Value Tree::NewInstance(Napi::Env env, TSTree *tree) { + auto data = env.GetInstanceData(); if (tree) { - Local self; - MaybeLocal maybe_self = Nan::NewInstance(Nan::New(data->tree_constructor)); - if (maybe_self.ToLocal(&self)) { - (new Tree(tree))->Wrap(self); - return self; - } + Object self = data->tree_constructor.New({}); + Tree::Unwrap(self)->tree_ = tree; + return self; } - return Nan::Null(); -} -const Tree *Tree::UnwrapTree(AddonData* data, const Local &value) { - if (!value->IsObject()) return nullptr; - Local js_tree = Local::Cast(value); - if (!Nan::New(data->tree_constructor_template)->HasInstance(js_tree)) return nullptr; - return ObjectWrap::Unwrap(js_tree); + return env.Null(); } -void Tree::New(const Nan::FunctionCallbackInfo &info) {} +const Tree *Tree::UnwrapTree(const Napi::Value &value) { + auto data = value.Env().GetInstanceData(); + if (!value.IsObject()) return nullptr; + Object js_tree = value.As(); + if (!js_tree.InstanceOf(data->tree_constructor.Value())) return nullptr; + return Tree::Unwrap(js_tree); +} -#define read_number_from_js(out, value, name) \ - maybe_number = Nan::To(value); \ - if (maybe_number.IsNothing()) { \ - Nan::ThrowTypeError(name " must be an integer"); \ - return; \ - } \ - *(out) = maybe_number.FromJust(); +#define read_number_from_js(out, value, name) \ + if (!value.IsNumber()) { \ + throw TypeError::New(env, name " must be an integer"); \ + return env.Undefined(); \ + } \ + *(out) = value.As().Uint32Value(); #define read_byte_count_from_js(out, value, name) \ read_number_from_js(out, value, name); \ (*out) *= 2 -void Tree::Edit(const Nan::FunctionCallbackInfo &info) { - Tree *tree = ObjectWrap::Unwrap(info.This()); - +Napi::Value Tree::Edit(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); TSInputEdit edit; - Nan::Maybe maybe_number = Nan::Nothing(); + // Nan::Maybe maybe_number = Nan::Nothing(); read_number_from_js(&edit.start_point.row, info[0], "startPosition.row"); read_byte_count_from_js(&edit.start_point.column, info[1], "startPosition.column"); read_number_from_js(&edit.old_end_point.row, info[2], "oldEndPosition.row"); @@ -98,61 +83,57 @@ void Tree::Edit(const Nan::FunctionCallbackInfo &info) { read_byte_count_from_js(&edit.old_end_byte, info[7], "oldEndIndex"); read_byte_count_from_js(&edit.new_end_byte, info[8], "newEndIndex"); - ts_tree_edit(tree->tree_, &edit); + ts_tree_edit(tree_, &edit); - for (auto &entry : tree->cached_nodes_) { - Local js_node = Nan::New(entry.second->node); + for (auto &entry : cached_nodes_) { + Object js_node = entry.second->node.Value(); TSNode node; node.id = entry.first; for (unsigned i = 0; i < 4; i++) { - Local node_field; - if (Nan::Get(js_node, i + 2).ToLocal(&node_field)) { - node.context[i] = Nan::To(node_field).FromMaybe(0); + Napi::Value node_field = js_node[i + 2]; + if (node_field.IsNumber()) { + node.context[i] = node_field.As().Uint32Value(); } } ts_node_edit(&node, &edit); for (unsigned i = 0; i < 4; i++) { - Nan::Set(js_node, i + 2, Nan::New(node.context[i])); + js_node[i + 2u] = Number::New(env, node.context[i]); } } - info.GetReturnValue().Set(info.This()); + return info.This(); } -void Tree::RootNode(const Nan::FunctionCallbackInfo &info) { - Tree *tree = ObjectWrap::Unwrap(info.This()); - node_methods::MarshalNode(info, tree, ts_tree_root_node(tree->tree_)); +Napi::Value Tree::RootNode(const Napi::CallbackInfo &info) { + return node_methods::MarshalNode(info, this, ts_tree_root_node(tree_)); } -void Tree::GetChangedRanges(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - const Tree *tree = ObjectWrap::Unwrap(info.This()); - const Tree *other_tree = UnwrapTree(data, info[0]); +Napi::Value Tree::GetChangedRanges(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + const Tree *other_tree = UnwrapTree(info[0]); if (!other_tree) { - Nan::ThrowTypeError("Argument must be a tree"); - return; + throw TypeError::New(env, "Argument must be a tree"); } uint32_t range_count; - TSRange *ranges = ts_tree_get_changed_ranges(tree->tree_, other_tree->tree_, &range_count); + TSRange *ranges = ts_tree_get_changed_ranges(tree_, other_tree->tree_, &range_count); - Local result = Nan::New(); + Array result = Array::New(env); for (size_t i = 0; i < range_count; i++) { - Nan::Set(result, i, RangeToJS(data, ranges[i])); + result[i] = RangeToJS(env, ranges[i]); } free(ranges); - info.GetReturnValue().Set(result); + return result; } -void Tree::GetEditedRange(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - Tree *tree = ObjectWrap::Unwrap(info.This()); - TSNode root = ts_tree_root_node(tree->tree_); - if (!ts_node_has_changes(root)) return; +Napi::Value Tree::GetEditedRange(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + TSNode root = ts_tree_root_node(tree_); + if (!ts_node_has_changes(root)) return env.Undefined(); TSRange result = { ts_node_start_point(root), ts_node_end_point(root), @@ -194,17 +175,15 @@ void Tree::GetEditedRange(const Nan::FunctionCallbackInfo &info) { } ts_tree_cursor_delete(&cursor); - info.GetReturnValue().Set(RangeToJS(data, result)); + return RangeToJS(env, result); } -void Tree::PrintDotGraph(const Nan::FunctionCallbackInfo &info) { - Tree *tree = ObjectWrap::Unwrap(info.This()); - ts_tree_print_dot_graph(tree->tree_, fileno(stderr)); - info.GetReturnValue().Set(info.This()); +Napi::Value Tree::PrintDotGraph(const Napi::CallbackInfo &info) { + ts_tree_print_dot_graph(tree_, fileno(stderr)); + return info.This(); } -static void FinalizeNode(const v8::WeakCallbackInfo &info) { - Tree::NodeCacheEntry *cache_entry = info.GetParameter(); +static void FinalizeNode(Napi::Env env, Tree::NodeCacheEntry *cache_entry) { assert(!cache_entry->node.IsEmpty()); cache_entry->node.Reset(); if (cache_entry->tree) { @@ -214,43 +193,56 @@ static void FinalizeNode(const v8::WeakCallbackInfo &info) delete cache_entry; } -static void CacheNodeForTree(Tree *tree, Isolate *isolate, Local js_node) { - Local js_node_field1, js_node_field2; - if (!Nan::Get(js_node, 0).ToLocal(&js_node_field1)) return; - if (!Nan::Get(js_node, 1).ToLocal(&js_node_field2)) return; +static void CacheNodeForTree(Tree *tree, Napi::Env env, Object js_node) { + Value js_node_field1 = js_node[0u]; + Value js_node_field2 = js_node[1u]; + if (!js_node_field1.IsNumber() || !js_node_field2.IsNumber()) { + return; + } uint32_t key_parts[2] = { - Nan::To(js_node_field1).FromMaybe(0), - Nan::To(js_node_field2).FromMaybe(0) + js_node_field1.As().Uint32Value(), + js_node_field2.As().Uint32Value(), }; const void *key = UnmarshalNodeId(key_parts); auto cache_entry = new Tree::NodeCacheEntry{tree, key, {}}; - cache_entry->node.Reset(isolate, js_node); - cache_entry->node.SetWeak(cache_entry, &FinalizeNode, Nan::WeakCallbackType::kParameter); + cache_entry->node.Reset(js_node, 0); + js_node.AddFinalizer(&FinalizeNode, cache_entry); assert(!tree->cached_nodes_.count(key)); tree->cached_nodes_[key] = cache_entry; } -void Tree::CacheNode(const Nan::FunctionCallbackInfo &info) { - Tree *tree = ObjectWrap::Unwrap(info.This()); - Isolate *isolate = info.GetIsolate(); - Local js_node = Local::Cast(info[0]); +Napi::Value Tree::CacheNode(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + if (!info[0].IsObject()) { + throw TypeError::New(env, "not an object"); + } - CacheNodeForTree(tree, isolate, js_node); + CacheNodeForTree(this, env, info[0].As()); + return env.Undefined(); } -void Tree::CacheNodes(const Nan::FunctionCallbackInfo &info) { - Tree *tree = ObjectWrap::Unwrap(info.This()); - Isolate *isolate = info.GetIsolate(); - Local js_nodes = Local::Cast(info[0]); - uint32_t length = js_nodes->Length(); +Napi::Value Tree::CacheNodes(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); - for (uint32_t i = 0; i < length; i++) { - auto js_node = Local::Cast(Nan::Get(js_nodes, i).ToLocalChecked()); - CacheNodeForTree(tree, isolate, js_node); + if (!info[0].IsArray()) { + throw TypeError::New(env, "not an array"); } + Array js_nodes = info[0].As(); + uint32_t length = js_nodes.Length(); + + for (uint32_t i = 0; i < length; ++i) { + Napi::Value js_node = js_nodes[i]; + if (!js_node.IsObject()) { + throw TypeError::New(env, "not an object"); + } + CacheNodeForTree(this, env, js_node.As()); + } + + return env.Undefined(); } } // namespace node_tree_sitter diff --git a/src/tree.h b/src/tree.h index 6a1340b2..44090fa8 100644 --- a/src/tree.h +++ b/src/tree.h @@ -1,8 +1,7 @@ #ifndef NODE_TREE_SITTER_TREE_H_ #define NODE_TREE_SITTER_TREE_H_ -#include -#include +#include #include #include #include @@ -10,33 +9,32 @@ namespace node_tree_sitter { -class Tree : public Nan::ObjectWrap { +class Tree : public Napi::ObjectWrap { public: - static void Init(v8::Local exports, v8::Local data_ext); - static v8::Local NewInstance(AddonData* data, TSTree *); - static const Tree *UnwrapTree(AddonData* data, const v8::Local &); + static void Init(Napi::Env env, Napi::Object exports); + static Napi::Value NewInstance(Napi::Env env, TSTree *); + static const Tree *UnwrapTree(const Napi::Value &); + + explicit Tree(const Napi::CallbackInfo &); + ~Tree(); struct NodeCacheEntry { Tree *tree; const void *key; - v8::Persistent node; + Napi::ObjectReference node; }; TSTree *tree_; std::unordered_map cached_nodes_; private: - explicit Tree(TSTree *); - ~Tree(); - - static void New(const Nan::FunctionCallbackInfo &); - static void Edit(const Nan::FunctionCallbackInfo &); - static void RootNode(const Nan::FunctionCallbackInfo &); - static void PrintDotGraph(const Nan::FunctionCallbackInfo &); - static void GetEditedRange(const Nan::FunctionCallbackInfo &); - static void GetChangedRanges(const Nan::FunctionCallbackInfo &); - static void CacheNode(const Nan::FunctionCallbackInfo &); - static void CacheNodes(const Nan::FunctionCallbackInfo &); + Napi::Value Edit(const Napi::CallbackInfo &); + Napi::Value RootNode(const Napi::CallbackInfo &); + Napi::Value PrintDotGraph(const Napi::CallbackInfo &); + Napi::Value GetEditedRange(const Napi::CallbackInfo &); + Napi::Value GetChangedRanges(const Napi::CallbackInfo &); + Napi::Value CacheNode(const Napi::CallbackInfo &); + Napi::Value CacheNodes(const Napi::CallbackInfo &); }; diff --git a/src/tree_cursor.cc b/src/tree_cursor.cc index 66a55f09..38bdc207 100644 --- a/src/tree_cursor.cc +++ b/src/tree_cursor.cc @@ -1,184 +1,141 @@ #include "./tree_cursor.h" -#include +#include #include -#include #include "./util.h" #include "./conversions.h" #include "./node.h" #include "./tree.h" +using namespace Napi; + namespace node_tree_sitter { -using namespace v8; - -void TreeCursor::Init(v8::Local exports, v8::Local data_ext) { - AddonData* data = reinterpret_cast(data_ext->Value()); - Local tpl = Nan::New(New, data_ext); - Local class_name = Nan::New("TreeCursor").ToLocalChecked(); - tpl->SetClassName(class_name); - tpl->InstanceTemplate()->SetInternalFieldCount(1); - - GetterPair getters[] = { - {"startIndex", StartIndex}, - {"endIndex", EndIndex}, - {"nodeType", NodeType}, - {"nodeIsNamed", NodeIsNamed}, - {"nodeIsMissing", NodeIsMissing}, - {"currentFieldName", CurrentFieldName}, - }; - - FunctionPair methods[] = { - {"startPosition", StartPosition}, - {"endPosition", EndPosition}, - {"gotoParent", GotoParent}, - {"gotoFirstChild", GotoFirstChild}, - {"gotoFirstChildForIndex", GotoFirstChildForIndex}, - {"gotoNextSibling", GotoNextSibling}, - {"currentNode", CurrentNode}, - {"reset", Reset}, - }; - - for (size_t i = 0; i < length_of_array(getters); i++) { - Nan::SetAccessor( - tpl->InstanceTemplate(), - Nan::New(getters[i].name).ToLocalChecked(), - getters[i].callback); - } +void TreeCursor::Init(Napi::Env env, Napi::Object exports) { + auto data = env.GetInstanceData(); - for (size_t i = 0; i < length_of_array(methods); i++) { - Nan::SetPrototypeMethod(tpl, methods[i].name, methods[i].callback, data_ext); - } + Function ctor = DefineClass(env, "TreeCursor", { + InstanceAccessor("startIndex", &TreeCursor::StartIndex, nullptr, napi_default_method), + InstanceAccessor("endIndex", &TreeCursor::EndIndex, nullptr, napi_default_method), + InstanceAccessor("nodeType", &TreeCursor::NodeType, nullptr, napi_default_method), + InstanceAccessor("nodeIsNamed", &TreeCursor::NodeIsNamed, nullptr, napi_default_method), + InstanceAccessor("nodeIsMissing", &TreeCursor::NodeIsMissing, nullptr, napi_default_method), + InstanceAccessor("currentFieldName", &TreeCursor::CurrentFieldName, nullptr, napi_default_method), - Local constructor_local = Nan::GetFunction(tpl).ToLocalChecked(); - Nan::Set(exports, class_name, constructor_local); - data->tree_cursor_constructor.Reset(Nan::Persistent(constructor_local)); + InstanceMethod("startPosition", &TreeCursor::StartPosition, napi_default_method), + InstanceMethod("endPosition", &TreeCursor::EndPosition, napi_default_method), + InstanceMethod("gotoParent", &TreeCursor::GotoParent, napi_default_method), + InstanceMethod("gotoFirstChild", &TreeCursor::GotoFirstChild, napi_default_method), + InstanceMethod("gotoFirstChildForIndex", &TreeCursor::GotoFirstChildForIndex, napi_default_method), + InstanceMethod("gotoNextSibling", &TreeCursor::GotoNextSibling, napi_default_method), + InstanceMethod("currentNode", &TreeCursor::CurrentNode, napi_default_method), + InstanceMethod("reset", &TreeCursor::Reset, napi_default_method), + }); + + exports["TreeCursor"] = ctor; + data->tree_cursor_constructor = Napi::Persistent(ctor); } -Local TreeCursor::NewInstance(AddonData* data, TSTreeCursor cursor) { - Local self; - MaybeLocal maybe_self = Nan::New(data->tree_cursor_constructor)->NewInstance(Nan::GetCurrentContext()); - if (maybe_self.ToLocal(&self)) { - (new TreeCursor(cursor))->Wrap(self); - return self; - } else { - return Nan::Null(); - } +Napi::Value TreeCursor::NewInstance(Napi::Env env, TSTreeCursor cursor) { + auto data = env.GetInstanceData(); + + Object self = data->tree_cursor_constructor.New({}); + TreeCursor::Unwrap(self)->cursor_ = cursor; + return self; } -TreeCursor::TreeCursor(TSTreeCursor cursor) : cursor_(cursor) {} +TreeCursor::TreeCursor(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info), cursor_() {} TreeCursor::~TreeCursor() { ts_tree_cursor_delete(&cursor_); } -void TreeCursor::New(const Nan::FunctionCallbackInfo &info) { - info.GetReturnValue().Set(Nan::Null()); -} - -void TreeCursor::GotoParent(const Nan::FunctionCallbackInfo &info) { - TreeCursor *cursor = Nan::ObjectWrap::Unwrap(info.This()); - bool result = ts_tree_cursor_goto_parent(&cursor->cursor_); - info.GetReturnValue().Set(Nan::New(result)); +Napi::Value TreeCursor::GotoParent(const Napi::CallbackInfo &info) { + bool result = ts_tree_cursor_goto_parent(&cursor_); + return Boolean::New(info.Env(), result); } -void TreeCursor::GotoFirstChild(const Nan::FunctionCallbackInfo &info) { - TreeCursor *cursor = Nan::ObjectWrap::Unwrap(info.This()); - bool result = ts_tree_cursor_goto_first_child(&cursor->cursor_); - info.GetReturnValue().Set(Nan::New(result)); +Napi::Value TreeCursor::GotoFirstChild(const Napi::CallbackInfo &info) { + bool result = ts_tree_cursor_goto_first_child(&cursor_); + return Boolean::New(info.Env(), result); } -void TreeCursor::GotoFirstChildForIndex(const Nan::FunctionCallbackInfo &info) { - TreeCursor *cursor = Nan::ObjectWrap::Unwrap(info.This()); - auto maybe_index = Nan::To(info[0]); - if (maybe_index.IsNothing()) { - Nan::ThrowTypeError("Argument must be an integer"); - return; +Napi::Value TreeCursor::GotoFirstChildForIndex(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + if (!info[0].IsNumber()) { + throw TypeError::New(env, "Argument must be an integer"); } - uint32_t goal_byte = maybe_index.FromJust() * 2; - int64_t child_index = ts_tree_cursor_goto_first_child_for_byte(&cursor->cursor_, goal_byte); + auto index = info[0].As(); + uint32_t goal_byte = index.Uint32Value() * 2; + int64_t child_index = ts_tree_cursor_goto_first_child_for_byte(&cursor_, goal_byte); if (child_index < 0) { - info.GetReturnValue().Set(Nan::Null()); + return env.Null(); } else { - info.GetReturnValue().Set(Nan::New(static_cast(child_index))); + return Number::New(env, child_index); } } -void TreeCursor::GotoNextSibling(const Nan::FunctionCallbackInfo &info) { - TreeCursor *cursor = Nan::ObjectWrap::Unwrap(info.This()); - bool result = ts_tree_cursor_goto_next_sibling(&cursor->cursor_); - info.GetReturnValue().Set(Nan::New(result)); +Napi::Value TreeCursor::GotoNextSibling(const Napi::CallbackInfo &info) { + bool result = ts_tree_cursor_goto_next_sibling(&cursor_); + return Boolean::New(info.Env(), result); } -void TreeCursor::StartPosition(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - TreeCursor *cursor = Nan::ObjectWrap::Unwrap(info.This()); - TSNode node = ts_tree_cursor_current_node(&cursor->cursor_); - TransferPoint(data, ts_node_start_point(node)); +Napi::Value TreeCursor::StartPosition(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + TSNode node = ts_tree_cursor_current_node(&cursor_); + TransferPoint(env, ts_node_start_point(node)); + return env.Undefined(); } -void TreeCursor::EndPosition(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - TreeCursor *cursor = Nan::ObjectWrap::Unwrap(info.This()); - TSNode node = ts_tree_cursor_current_node(&cursor->cursor_); - TransferPoint(data, ts_node_end_point(node)); +Napi::Value TreeCursor::EndPosition(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + TSNode node = ts_tree_cursor_current_node(&cursor_); + TransferPoint(env, ts_node_end_point(node)); + return env.Undefined(); } -void TreeCursor::CurrentNode(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - TreeCursor *cursor = Nan::ObjectWrap::Unwrap(info.This()); - Local key = Nan::New("tree").ToLocalChecked(); - const Tree *tree = Tree::UnwrapTree(data, Nan::Get(info.This(), key).ToLocalChecked()); - TSNode node = ts_tree_cursor_current_node(&cursor->cursor_); - node_methods::MarshalNode(info, tree, node); +Napi::Value TreeCursor::CurrentNode(const Napi::CallbackInfo &info) { + const Tree *tree = Tree::UnwrapTree(info.This().As()["tree"]); + TSNode node = ts_tree_cursor_current_node(&cursor_); + return node_methods::MarshalNode(info, tree, node); } -void TreeCursor::Reset(const Nan::FunctionCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - TreeCursor *cursor = Nan::ObjectWrap::Unwrap(info.This()); - Local key = Nan::New("tree").ToLocalChecked(); - const Tree *tree = Tree::UnwrapTree(data, Nan::Get(info.This(), key).ToLocalChecked()); - TSNode node = node_methods::UnmarshalNode(data, tree); - ts_tree_cursor_reset(&cursor->cursor_, node); +Napi::Value TreeCursor::Reset(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info.This().As()["tree"]); + TSNode node = node_methods::UnmarshalNode(env, tree); + ts_tree_cursor_reset(&cursor_, node); + return env.Undefined(); } -void TreeCursor::NodeType(v8::Local prop, const Nan::PropertyCallbackInfo &info) { - TreeCursor *cursor = Nan::ObjectWrap::Unwrap(info.This()); - TSNode node = ts_tree_cursor_current_node(&cursor->cursor_); - info.GetReturnValue().Set(Nan::New(ts_node_type(node)).ToLocalChecked()); +Napi::Value TreeCursor::NodeType(const Napi::CallbackInfo &info) { + TSNode node = ts_tree_cursor_current_node(&cursor_); + return String::New(info.Env(), ts_node_type(node));; } -void TreeCursor::NodeIsNamed(v8::Local prop, const Nan::PropertyCallbackInfo &info) { - TreeCursor *cursor = Nan::ObjectWrap::Unwrap(info.This()); - TSNode node = ts_tree_cursor_current_node(&cursor->cursor_); - - info.GetReturnValue().Set(Nan::New(ts_node_is_named(node))); +Napi::Value TreeCursor::NodeIsNamed(const Napi::CallbackInfo &info) { + TSNode node = ts_tree_cursor_current_node(&cursor_); + return Boolean::New(info.Env(), ts_node_is_named(node)); } -void TreeCursor::NodeIsMissing(v8::Local prop, const Nan::PropertyCallbackInfo &info) { - TreeCursor *cursor = Nan::ObjectWrap::Unwrap(info.This()); - TSNode node = ts_tree_cursor_current_node(&cursor->cursor_); - - info.GetReturnValue().Set(Nan::New(ts_node_is_missing(node))); +Napi::Value TreeCursor::NodeIsMissing(const Napi::CallbackInfo &info) { + TSNode node = ts_tree_cursor_current_node(&cursor_); + return Boolean::New(info.Env(), ts_node_is_missing(node)); } -void TreeCursor::CurrentFieldName(v8::Local prop, const Nan::PropertyCallbackInfo &info) { - TreeCursor *cursor = Nan::ObjectWrap::Unwrap(info.This()); - const char *field_name = ts_tree_cursor_current_field_name(&cursor->cursor_); +Napi::Value TreeCursor::CurrentFieldName(const Napi::CallbackInfo &info) { + const char *field_name = ts_tree_cursor_current_field_name(&cursor_); if (field_name) { - info.GetReturnValue().Set(Nan::New(field_name).ToLocalChecked()); + return String::New(info.Env(), field_name); } + return info.Env().Undefined(); } -void TreeCursor::StartIndex(v8::Local prop, const Nan::PropertyCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - TreeCursor *cursor = Nan::ObjectWrap::Unwrap(info.This()); - TSNode node = ts_tree_cursor_current_node(&cursor->cursor_); - info.GetReturnValue().Set(ByteCountToJS(data, ts_node_start_byte(node))); +Napi::Value TreeCursor::StartIndex(const Napi::CallbackInfo &info) { + TSNode node = ts_tree_cursor_current_node(&cursor_); + return ByteCountToJS(info.Env(), ts_node_start_byte(node)); } -void TreeCursor::EndIndex(v8::Local prop, const Nan::PropertyCallbackInfo &info) { - AddonData* data = reinterpret_cast(info.Data().As()->Value()); - TreeCursor *cursor = Nan::ObjectWrap::Unwrap(info.This()); - TSNode node = ts_tree_cursor_current_node(&cursor->cursor_); - info.GetReturnValue().Set(ByteCountToJS(data, ts_node_end_byte(node))); +Napi::Value TreeCursor::EndIndex(const Napi::CallbackInfo &info) { + TSNode node = ts_tree_cursor_current_node(&cursor_); + return ByteCountToJS(info.Env(), ts_node_end_byte(node)); } } diff --git a/src/tree_cursor.h b/src/tree_cursor.h index 1f70a151..54448da6 100644 --- a/src/tree_cursor.h +++ b/src/tree_cursor.h @@ -1,39 +1,37 @@ #ifndef NODE_TREE_SITTER_TREE_CURSOR_H_ #define NODE_TREE_SITTER_TREE_CURSOR_H_ -#include -#include +#include #include #include #include "./addon_data.h" namespace node_tree_sitter { -class TreeCursor : public Nan::ObjectWrap { +class TreeCursor : public Napi::ObjectWrap { public: - static void Init(v8::Local exports, v8::Local data_ext); - static v8::Local NewInstance(AddonData* data, TSTreeCursor); + static void Init(Napi::Env env, Napi::Object exports); + static Napi::Value NewInstance(Napi::Env Env, TSTreeCursor); - private: - explicit TreeCursor(TSTreeCursor); + explicit TreeCursor(const Napi::CallbackInfo &); ~TreeCursor(); - static void New(const Nan::FunctionCallbackInfo &); - static void GotoParent(const Nan::FunctionCallbackInfo &); - static void GotoFirstChild(const Nan::FunctionCallbackInfo &); - static void GotoFirstChildForIndex(const Nan::FunctionCallbackInfo &); - static void GotoNextSibling(const Nan::FunctionCallbackInfo &); - static void StartPosition(const Nan::FunctionCallbackInfo &); - static void EndPosition(const Nan::FunctionCallbackInfo &); - static void CurrentNode(const Nan::FunctionCallbackInfo &); - static void Reset(const Nan::FunctionCallbackInfo &); - - static void NodeType(v8::Local, const Nan::PropertyCallbackInfo &); - static void NodeIsNamed(v8::Local, const Nan::PropertyCallbackInfo &); - static void NodeIsMissing(v8::Local, const Nan::PropertyCallbackInfo &); - static void CurrentFieldName(v8::Local, const Nan::PropertyCallbackInfo &); - static void StartIndex(v8::Local, const Nan::PropertyCallbackInfo &); - static void EndIndex(v8::Local, const Nan::PropertyCallbackInfo &); + private: + Napi::Value GotoParent(const Napi::CallbackInfo &); + Napi::Value GotoFirstChild(const Napi::CallbackInfo &); + Napi::Value GotoFirstChildForIndex(const Napi::CallbackInfo &); + Napi::Value GotoNextSibling(const Napi::CallbackInfo &); + Napi::Value StartPosition(const Napi::CallbackInfo &); + Napi::Value EndPosition(const Napi::CallbackInfo &); + Napi::Value CurrentNode(const Napi::CallbackInfo &); + Napi::Value Reset(const Napi::CallbackInfo &); + + Napi::Value NodeType(const Napi::CallbackInfo &); + Napi::Value NodeIsNamed(const Napi::CallbackInfo &); + Napi::Value NodeIsMissing(const Napi::CallbackInfo &); + Napi::Value CurrentFieldName(const Napi::CallbackInfo &); + Napi::Value StartIndex(const Napi::CallbackInfo &); + Napi::Value EndIndex(const Napi::CallbackInfo &); TSTreeCursor cursor_; }; diff --git a/src/util.cc b/src/util.cc deleted file mode 100644 index cff335db..00000000 --- a/src/util.cc +++ /dev/null @@ -1,22 +0,0 @@ -#include -#include -#include "./util.h" - -namespace node_tree_sitter { - -bool instance_of(v8::Local value, v8::Local object) { - auto maybe_bool = value->InstanceOf(Nan::GetCurrentContext(), object); - if (maybe_bool.IsNothing()) - return false; - return maybe_bool.FromJust(); -} - -v8::Local GetGlobal(v8::Local& callback) { - #if (V8_MAJOR_VERSION > 9 || (V8_MAJOR_VERSION == 9 && V8_MINOR_VERSION > 4)) - return callback->GetCreationContext().ToLocalChecked()->Global(); - #else - return callback->CreationContext()->Global(); - #endif -} - -} // namespace node_tree_sitter diff --git a/src/util.h b/src/util.h index 42de5d13..1f54bb65 100644 --- a/src/util.h +++ b/src/util.h @@ -1,27 +1,17 @@ #ifndef NODE_TREE_SITTER_UTIL_H_ #define NODE_TREE_SITTER_UTIL_H_ -#include -#include +#include namespace node_tree_sitter { #define length_of_array(a) (sizeof(a) / sizeof(a[0])) -struct GetterPair { - const char *name; - Nan::GetterCallback callback; -}; - struct FunctionPair { const char *name; - Nan::FunctionCallback callback; + Napi::Function::Callback callback; }; -bool instance_of(v8::Local value, v8::Local object); - -v8::Local GetGlobal(v8::Local& callback); - } // namespace node_tree_sitter #endif // NODE_TREE_SITTER_UTIL_H_ From 440c29af1fdbeb4ff531eab9ba8260f770a07082 Mon Sep 17 00:00:00 2001 From: Amaan Qureshi Date: Wed, 28 Feb 2024 13:44:03 -0500 Subject: [PATCH 3/8] style: tidying and modernizing --- src/addon_data.h | 5 +- src/binding.cc | 7 +- src/conversions.cc | 18 +-- src/conversions.h | 3 +- src/language.cc | 34 +++-- src/language.h | 9 +- src/logger.cc | 15 +- src/logger.h | 2 +- src/node.cc | 348 +++++++++++++++++++++++++-------------------- src/node.h | 12 +- src/parser.cc | 89 +++++++----- src/parser.h | 2 +- src/query.cc | 38 ++--- src/query.h | 9 +- src/tree.cc | 70 +++++---- src/tree.h | 7 +- src/tree_cursor.cc | 20 +-- src/tree_cursor.h | 5 +- 18 files changed, 384 insertions(+), 309 deletions(-) diff --git a/src/addon_data.h b/src/addon_data.h index e79a543e..7ff5ba24 100644 --- a/src/addon_data.h +++ b/src/addon_data.h @@ -1,5 +1,4 @@ #include -#include #include #ifndef NODE_TREE_SITTER_ADDON_DATA_H_ @@ -9,7 +8,7 @@ namespace node_tree_sitter { class AddonData { public: - explicit AddonData(Napi::Env env) {} + explicit AddonData(Napi::Env _env) {} ~AddonData() { ts_query_cursor_delete(ts_query_cursor); @@ -39,6 +38,6 @@ class AddonData { Napi::FunctionReference tree_constructor; }; -} +} // namespace node_tree_sitter #endif // NODE_TREE_SITTER_ADDON_DATA_H_ diff --git a/src/binding.cc b/src/binding.cc index e0fa0051..7d194d68 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -1,19 +1,20 @@ -#include #include "./addon_data.h" +#include "./conversions.h" #include "./language.h" #include "./node.h" #include "./parser.h" #include "./query.h" #include "./tree.h" #include "./tree_cursor.h" -#include "./conversions.h" + +#include using namespace Napi; namespace node_tree_sitter { Napi::Object InitAll(Napi::Env env, Napi::Object exports) { - AddonData* data = new AddonData(env); + auto* data = new AddonData(env); env.SetInstanceData(data); InitConversions(env, exports); diff --git a/src/conversions.cc b/src/conversions.cc index 82ab7750..accf085d 100644 --- a/src/conversions.cc +++ b/src/conversions.cc @@ -1,9 +1,9 @@ #include "./conversions.h" +#include "./addon_data.h" + +#include #include #include -#include -#include "./node.h" -#include "./addon_data.h" using namespace Napi; @@ -12,7 +12,7 @@ namespace node_tree_sitter { static const unsigned BYTES_PER_CHARACTER = 2; void InitConversions(Napi::Env env, Napi::Object exports) { - auto data = env.GetInstanceData(); + auto *data = env.GetInstanceData(); auto js_point_transfer_buffer = Uint32Array::New(env, 2); data->point_transfer_buffer = js_point_transfer_buffer.Data(); @@ -21,7 +21,7 @@ void InitConversions(Napi::Env env, Napi::Object exports) { } void TransferPoint(Napi::Env env, const TSPoint &point) { - auto data = env.GetInstanceData(); + auto *data = env.GetInstanceData(); data->point_transfer_buffer[0] = point.row; data->point_transfer_buffer[1] = point.column / 2; } @@ -44,7 +44,7 @@ Napi::Maybe RangeFromJS(const Napi::Value& arg) { TSRange result; - Object js_range = arg.As(); + auto js_range = arg.As(); #define INIT(field, key, Convert) { \ auto value = js_range.Get(key); \ @@ -84,15 +84,15 @@ Napi::Maybe PointFromJS(const Napi::Value& arg) { TypeError::New(env, "Point must be a {row, column} object").ThrowAsJavaScriptException(); return Napi::Nothing(); } - Object js_point = arg.As(); + auto js_point = arg.As(); - Number js_row = js_point.Get("row").As(); + auto js_row = js_point.Get("row").As(); if (!js_row.IsNumber()) { TypeError::New(env, "Point must be a {row, column} object").ThrowAsJavaScriptException(); return Napi::Nothing(); } - Number js_column = js_point.Get("column").As(); + auto js_column = js_point.Get("column").As(); if (!js_column.IsNumber()) { TypeError::New(env, "Point must be a {row, column} object").ThrowAsJavaScriptException(); return Napi::Nothing(); diff --git a/src/conversions.h b/src/conversions.h index 22147257..2cd73c62 100644 --- a/src/conversions.h +++ b/src/conversions.h @@ -1,9 +1,10 @@ #ifndef NODE_TREE_SITTER_CONVERSIONS_H_ #define NODE_TREE_SITTER_CONVERSIONS_H_ +#include "./addon_data.h" + #include #include -#include "./addon_data.h" namespace node_tree_sitter { diff --git a/src/language.cc b/src/language.cc index 8ba6af72..0c4f3cc6 100644 --- a/src/language.cc +++ b/src/language.cc @@ -1,14 +1,12 @@ #include "./language.h" + #include -#include -#include #include +#include using namespace Napi; -using std::vector; -namespace node_tree_sitter { -namespace language_methods { +namespace node_tree_sitter::language_methods { const TSLanguage *UnwrapLanguage(Napi::Value value) { Napi::Env env = value.Env(); @@ -18,9 +16,9 @@ const TSLanguage *UnwrapLanguage(Napi::Value value) { } if (value.IsExternal()) { - External arg = value.As>(); + auto arg = value.As>(); const TSLanguage *language = arg.Data(); - if (language) { + if (language != nullptr) { uint16_t version = ts_language_version(language); if ( version < TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION || @@ -40,11 +38,15 @@ const TSLanguage *UnwrapLanguage(Napi::Value value) { throw TypeError::New(env, "Invalid language object"); } -static Napi::Value GetNodeTypeNamesById(const Napi::CallbackInfo &info) { +namespace { + +Napi::Value GetNodeTypeNamesById(const Napi::CallbackInfo &info) { Env env = info.Env(); const TSLanguage *language = UnwrapLanguage(info[0]); - if (!language) return env.Undefined(); + if (language == nullptr) { + return env.Undefined(); +} auto result = Array::New(env); uint32_t length = ts_language_symbol_count(language); @@ -61,17 +63,19 @@ static Napi::Value GetNodeTypeNamesById(const Napi::CallbackInfo &info) { return result; } -static Napi::Value GetNodeFieldNamesById(const Napi::CallbackInfo &info) { +Napi::Value GetNodeFieldNamesById(const Napi::CallbackInfo &info) { Env env = info.Env(); const TSLanguage *language = UnwrapLanguage(info[0]); - if (!language) return env.Undefined(); + if (language == nullptr) { + return env.Undefined(); +} auto result = Array::New(env); uint32_t length = ts_language_field_count(language); for (uint32_t i = 0; i < length + 1; i++) { const char *name = ts_language_field_name_for_id(language, i); - if (name) { + if (name != nullptr) { result[i] = String::New(env, name); } else { result[i] = env.Null(); @@ -81,10 +85,12 @@ static Napi::Value GetNodeFieldNamesById(const Napi::CallbackInfo &info) { return result; } +} // namespace + void Init(Napi::Env env, Napi::Object exports) { exports["getNodeTypeNamesById"] = Function::New(env, GetNodeTypeNamesById); exports["getNodeFieldNamesById"] = Function::New(env, GetNodeFieldNamesById); } -} // namespace language_methods -} // namespace node_tree_sitter +} // namespace node_tree_sitter::language_methods + diff --git a/src/language.h b/src/language.h index 4df74149..ca524c6c 100644 --- a/src/language.h +++ b/src/language.h @@ -4,16 +4,15 @@ #include #include #include -#include "./tree.h" -namespace node_tree_sitter { -namespace language_methods { + +namespace node_tree_sitter::language_methods { void Init(Napi::Env env, Napi::Object); const TSLanguage *UnwrapLanguage(Napi::Value); -} // namespace language_methods -} // namespace node_tree_sitter +} // namespace node_tree_sitter::language_methods + #endif // NODE_TREE_SITTER_LANGUAGE_H_ diff --git a/src/logger.cc b/src/logger.cc index e7416a74..21cdbceb 100644 --- a/src/logger.cc +++ b/src/logger.cc @@ -1,8 +1,8 @@ #include "./logger.h" -#include + #include +#include #include -#include "./util.h" using namespace Napi; using std::string; @@ -10,7 +10,7 @@ using std::string; namespace node_tree_sitter { void Logger::Log(void *payload, TSLogType type, const char *message_str) { - auto debugger = reinterpret_cast(payload); + auto *debugger = static_cast(payload); Env env = debugger->func.Env(); string message(message_str); @@ -23,10 +23,11 @@ void Logger::Log(void *payload, TSLogType type, const char *message_str) { while (param_sep_pos != string::npos) { size_t key_pos = param_sep_pos + param_sep.size(); - size_t value_sep_pos = message.find(":", key_pos); + size_t value_sep_pos = message.find(':', key_pos); - if (value_sep_pos == string::npos) + if (value_sep_pos == string::npos) { break; + } size_t val_pos = value_sep_pos + 1; param_sep = ", "; @@ -59,9 +60,9 @@ void Logger::Log(void *payload, TSLogType type, const char *message_str) { TSLogger Logger::Make(const Napi::Function &func) { TSLogger result; - Logger *logger = new Logger(); + auto *logger = new Logger(); logger->func = Napi::Persistent(func); - result.payload = (void *)logger; + result.payload = static_cast(logger); result.log = Log; return result; } diff --git a/src/logger.h b/src/logger.h index a6ed770e..a84082cf 100644 --- a/src/logger.h +++ b/src/logger.h @@ -13,6 +13,6 @@ class Logger { static void Log(void *, TSLogType, const char *); }; -} // namespace node_tree_sitter +} // namespace node_tree_sitter #endif // NODE_TREE_SITTER_LOGGER_H_ diff --git a/src/node.cc b/src/node.cc index 48181bd9..a447c435 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1,22 +1,25 @@ #include "./node.h" -#include -#include -#include -#include "./util.h" #include "./conversions.h" #include "./tree.h" #include "./tree_cursor.h" +#include "./util.h" + +#include +#include +#include using std::vector; using namespace Napi; -namespace node_tree_sitter { -namespace node_methods { -static const uint32_t FIELD_COUNT_PER_NODE = 6; +namespace node_tree_sitter::node_methods { + +const uint32_t FIELD_COUNT_PER_NODE = 6; + +namespace { -static inline void setup_transfer_buffer(Napi::Env env, uint32_t node_count) { - auto data = env.GetInstanceData(); +inline void setup_transfer_buffer(Napi::Env env, uint32_t node_count) { + auto *data = env.GetInstanceData(); uint32_t new_length = node_count * FIELD_COUNT_PER_NODE; if (new_length > data->transfer_buffer_length) { @@ -29,17 +32,23 @@ static inline void setup_transfer_buffer(Napi::Env env, uint32_t node_count) { } } -static inline bool operator<=(const TSPoint &left, const TSPoint &right) { - if (left.row < right.row) return true; - if (left.row > right.row) return false; +inline bool operator<=(const TSPoint &left, const TSPoint &right) { + if (left.row < right.row) { + return true; + } + if (left.row > right.row) { + return false; + } return left.column <= right.column; } -static Napi::Value MarshalNodes(const Napi::CallbackInfo &info, +Napi::Value MarshalNodes(const Napi::CallbackInfo &info, const Tree *tree, const TSNode *nodes, uint32_t node_count) { return GetMarshalNodes(info, tree, nodes, node_count); } +} // namespace + Napi::Value MarshalNode(const Napi::CallbackInfo &info, const Tree *tree, TSNode node) { return GetMarshalNode(info, tree, node); } @@ -47,7 +56,7 @@ Napi::Value MarshalNode(const Napi::CallbackInfo &info, const Tree *tree, TSNode Napi::Value GetMarshalNodes(const Napi::CallbackInfo &info, const Tree *tree, const TSNode *nodes, uint32_t node_count) { Env env = info.Env(); - auto data = env.GetInstanceData(); + auto *data = env.GetInstanceData(); auto result = Array::New(env, node_count); setup_transfer_buffer(env, node_count); uint32_t *p = data->transfer_buffer; @@ -61,7 +70,7 @@ Napi::Value GetMarshalNodes(const Napi::CallbackInfo &info, *(p++) = node.context[1]; *(p++) = node.context[2]; *(p++) = node.context[3]; - if (node.id) { + if (node.id != nullptr) { result[i] = Number::New(env, ts_node_symbol(node)); } else { result[i] = env.Null(); @@ -75,7 +84,7 @@ Napi::Value GetMarshalNodes(const Napi::CallbackInfo &info, Napi::Value GetMarshalNode(const Napi::CallbackInfo &info, const Tree *tree, TSNode node) { Env env = info.Env(); - AddonData* data = env.GetInstanceData(); + auto* data = env.GetInstanceData(); const auto &cache_entry = tree->cached_nodes_.find(node.id); if (cache_entry == tree->cached_nodes_.end()) { setup_transfer_buffer(env, 1); @@ -86,7 +95,7 @@ Napi::Value GetMarshalNode(const Napi::CallbackInfo &info, const Tree *tree, TSN *(p++) = node.context[1]; *(p++) = node.context[2]; *(p++) = node.context[3]; - if (node.id) { + if (node.id != nullptr) { return Number::New(env, ts_node_symbol(node)); } } else { @@ -95,17 +104,11 @@ Napi::Value GetMarshalNode(const Napi::CallbackInfo &info, const Tree *tree, TSN return env.Null(); } -static Napi::Value MarshalNullNode(Napi::Env env) { - auto data = env.GetInstanceData(); - memset(data->transfer_buffer, 0, FIELD_COUNT_PER_NODE * sizeof(data->transfer_buffer[0])); - return env.Undefined(); -} - TSNode UnmarshalNode(Napi::Env env, const Tree *tree) { - AddonData* data = env.GetInstanceData(); + auto* data = env.GetInstanceData(); TSNode result = {{0, 0, 0, 0}, nullptr, nullptr}; result.tree = tree->tree_; - if (!result.tree) { + if (result.tree == nullptr) { throw TypeError::New(env, "Argument must be a tree"); } @@ -117,11 +120,59 @@ TSNode UnmarshalNode(Napi::Env env, const Tree *tree) { return result; } -static Napi::Value ToString(const Napi::CallbackInfo &info) { +struct SymbolSet { + void add(TSSymbol symbol) { symbols += symbol; } + bool contains(TSSymbol symbol) const { return symbols.find(symbol) != symbols.npos; } + private: + std::basic_string symbols; +}; + +void symbol_set_from_js(SymbolSet *symbols, const Napi::Value &value, const TSLanguage *language) { + Env env = value.Env(); + + if (!value.IsArray()) { + throw TypeError::New(env, "Argument must be a string or array of strings"); + } + + unsigned symbol_count = ts_language_symbol_count(language); + + auto js_types = value.As(); + for (unsigned i = 0, n = js_types.Length(); i < n; i++) { + Value js_node_type_value = js_types[i]; + if (js_node_type_value.IsString()) { + auto js_node_type = js_node_type_value.As(); + std::string node_type = js_node_type.Utf8Value(); + + if (node_type == "ERROR") { + symbols->add(static_cast(-1)); + } else { + for (TSSymbol j = 0; j < static_cast(symbol_count); j++) { + if (node_type == ts_language_symbol_name(language, j)) { + symbols->add(j); + } + } + } + + continue; + } + + throw TypeError::New(env, "Argument must be a string or array of strings"); + } +} + +namespace { + +Napi::Value MarshalNullNode(Napi::Env env) { + auto *data = env.GetInstanceData(); + memset(data->transfer_buffer, 0, FIELD_COUNT_PER_NODE * sizeof(data->transfer_buffer[0])); + return env.Undefined(); +} + +Napi::Value ToString(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id) { + if (node.id != nullptr) { char *string = ts_node_string(node); String result = String::New(env, string); free(string); @@ -131,11 +182,11 @@ static Napi::Value ToString(const Napi::CallbackInfo &info) { return env.Undefined(); } -static Napi::Value IsMissing(const Napi::CallbackInfo &info) { +Napi::Value IsMissing(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id) { + if (node.id != nullptr) { bool result = ts_node_is_missing(node); return Boolean::New(env, result); } @@ -143,11 +194,11 @@ static Napi::Value IsMissing(const Napi::CallbackInfo &info) { return env.Undefined(); } -static Napi::Value HasChanges(const Napi::CallbackInfo &info) { +Napi::Value HasChanges(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id) { + if (node.id != nullptr) { bool result = ts_node_has_changes(node); return Boolean::New(env, result); } @@ -155,11 +206,11 @@ static Napi::Value HasChanges(const Napi::CallbackInfo &info) { return env.Undefined(); } -static Napi::Value HasError(const Napi::CallbackInfo &info) { +Napi::Value HasError(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id) { + if (node.id != nullptr) { bool result = ts_node_has_error(node); return Boolean::New(env, result); } @@ -167,11 +218,11 @@ static Napi::Value HasError(const Napi::CallbackInfo &info) { return env.Undefined(); } -static Napi::Value FirstNamedChildForIndex(const Napi::CallbackInfo &info) { +Napi::Value FirstNamedChildForIndex(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id) { + if (node.id != nullptr) { Napi::Maybe byte = ByteCountFromJS(info[1]); if (byte.IsJust()) { return MarshalNode(info, tree, ts_node_first_named_child_for_byte(node, byte.Unwrap())); @@ -180,12 +231,12 @@ static Napi::Value FirstNamedChildForIndex(const Napi::CallbackInfo &info) { return MarshalNullNode(env); } -static Napi::Value FirstChildForIndex(const Napi::CallbackInfo &info) { +Napi::Value FirstChildForIndex(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id && info.Length() > 1) { + if ((node.id != nullptr) && info.Length() > 1) { Napi::Maybe byte = ByteCountFromJS(info[1]); if (byte.IsJust()) { return MarshalNode(info, tree, ts_node_first_child_for_byte(node, byte.Unwrap())); @@ -194,12 +245,12 @@ static Napi::Value FirstChildForIndex(const Napi::CallbackInfo &info) { return MarshalNullNode(env); } -static Napi::Value NamedDescendantForIndex(const Napi::CallbackInfo &info) { +Napi::Value NamedDescendantForIndex(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id) { + if (node.id != nullptr) { Napi::Maybe maybe_min = ByteCountFromJS(info[1]); Napi::Maybe maybe_max = ByteCountFromJS(info[2]); if (maybe_min.IsJust() && maybe_max.IsJust()) { @@ -211,12 +262,12 @@ static Napi::Value NamedDescendantForIndex(const Napi::CallbackInfo &info) { return MarshalNullNode(env); } -static Napi::Value DescendantForIndex(const Napi::CallbackInfo &info) { +Napi::Value DescendantForIndex(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id) { + if (node.id != nullptr) { Napi::Maybe maybe_min = ByteCountFromJS(info[1]); Napi::Maybe maybe_max = ByteCountFromJS(info[2]); if (maybe_min.IsJust() && maybe_max.IsJust()) { @@ -228,12 +279,12 @@ static Napi::Value DescendantForIndex(const Napi::CallbackInfo &info) { return MarshalNullNode(env); } -static Napi::Value NamedDescendantForPosition(const Napi::CallbackInfo &info) { +Napi::Value NamedDescendantForPosition(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id) { + if (node.id != nullptr) { Napi::Maybe maybe_min = PointFromJS(info[1]); Napi::Maybe maybe_max = PointFromJS(info[2]); if (maybe_min.IsJust() && maybe_max.IsJust()) { @@ -245,12 +296,12 @@ static Napi::Value NamedDescendantForPosition(const Napi::CallbackInfo &info) { return MarshalNullNode(env); } -static Napi::Value DescendantForPosition(const Napi::CallbackInfo &info) { +Napi::Value DescendantForPosition(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id) { + if (node.id != nullptr) { Napi::Maybe maybe_min = PointFromJS(info[1]); Napi::Maybe maybe_max = PointFromJS(info[2]); if (maybe_min.IsJust() && maybe_max.IsJust()) { @@ -262,98 +313,98 @@ static Napi::Value DescendantForPosition(const Napi::CallbackInfo &info) { return MarshalNullNode(env); } -static Napi::Value Type(const Napi::CallbackInfo &info) { +Napi::Value Type(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id) { + if (node.id != nullptr) { return String::New(env, ts_node_type(node)); } return env.Undefined(); } -static Napi::Value TypeId(const Napi::CallbackInfo &info) { +Napi::Value TypeId(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id) { + if (node.id != nullptr) { return Number::New(env, ts_node_symbol(node)); } return env.Undefined(); } -static Napi::Value IsNamed(const Napi::CallbackInfo &info) { +Napi::Value IsNamed(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id) { + if (node.id != nullptr) { return Boolean::New(env, ts_node_is_named(node)); } return env.Undefined(); } -static Napi::Value StartIndex(const Napi::CallbackInfo &info) { +Napi::Value StartIndex(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id) { - int32_t result = ts_node_start_byte(node) / 2; + if (node.id != nullptr) { + auto result = static_cast(ts_node_start_byte(node) / 2); return Number::New(env, result); } return env.Undefined(); } -static Napi::Value EndIndex(const Napi::CallbackInfo &info) { +Napi::Value EndIndex(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id) { - int32_t result = ts_node_end_byte(node) / 2; + if (node.id != nullptr) { + auto result = static_cast(ts_node_end_byte(node) / 2); return Number::New(env, result); } return env.Undefined(); } -static Napi::Value StartPosition(const Napi::CallbackInfo &info) { +Napi::Value StartPosition(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id) { + if (node.id != nullptr) { TransferPoint(env, ts_node_start_point(node)); } return env.Undefined(); } -static Napi::Value EndPosition(const Napi::CallbackInfo &info) { +Napi::Value EndPosition(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id) { + if (node.id != nullptr) { TransferPoint(env, ts_node_end_point(node)); } return env.Undefined(); } -static Napi::Value Child(const Napi::CallbackInfo &info) { +Napi::Value Child(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id) { + if (node.id != nullptr) { if (!info[1].IsNumber()) { throw TypeError::New(env, "Second argument must be an integer"); } @@ -363,12 +414,12 @@ static Napi::Value Child(const Napi::CallbackInfo &info) { return MarshalNullNode(env); } -static Napi::Value NamedChild(const Napi::CallbackInfo &info) { +Napi::Value NamedChild(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id) { + if (node.id != nullptr) { if (!info[1].IsNumber()) { throw TypeError::New(env, "Second argument must be an integer"); } @@ -378,55 +429,55 @@ static Napi::Value NamedChild(const Napi::CallbackInfo &info) { return MarshalNullNode(env); } -static Napi::Value ChildCount(const Napi::CallbackInfo &info) { +Napi::Value ChildCount(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id) { + if (node.id != nullptr) { return Number::New(env, ts_node_child_count(node)); } return env.Undefined(); } -static Napi::Value NamedChildCount(const Napi::CallbackInfo &info) { +Napi::Value NamedChildCount(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id) { + if (node.id != nullptr) { return Number::New(env, ts_node_named_child_count(node)); } return env.Undefined(); } -static Napi::Value FirstChild(const Napi::CallbackInfo &info) { +Napi::Value FirstChild(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id) { + if (node.id != nullptr) { return MarshalNode(info, tree, ts_node_child(node, 0)); } return MarshalNullNode(env); } -static Napi::Value FirstNamedChild(const Napi::CallbackInfo &info) { +Napi::Value FirstNamedChild(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id) { + if (node.id != nullptr) { return MarshalNode(info, tree, ts_node_named_child(node, 0)); } return MarshalNullNode(env); } -static Napi::Value LastChild(const Napi::CallbackInfo &info) { +Napi::Value LastChild(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id) { + if (node.id != nullptr) { uint32_t child_count = ts_node_child_count(node); if (child_count > 0) { return MarshalNode(info, tree, ts_node_child(node, child_count - 1)); @@ -435,11 +486,11 @@ static Napi::Value LastChild(const Napi::CallbackInfo &info) { return MarshalNullNode(env); } -static Napi::Value LastNamedChild(const Napi::CallbackInfo &info) { +Napi::Value LastNamedChild(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id) { + if (node.id != nullptr) { uint32_t child_count = ts_node_named_child_count(node); if (child_count > 0) { return MarshalNode(info, tree, ts_node_named_child(node, child_count - 1)); @@ -448,101 +499,64 @@ static Napi::Value LastNamedChild(const Napi::CallbackInfo &info) { return MarshalNullNode(env); } -static Napi::Value Parent(const Napi::CallbackInfo &info) { +Napi::Value Parent(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id) { + if (node.id != nullptr) { return MarshalNode(info, tree, ts_node_parent(node)); } return MarshalNullNode(env); } -static Napi::Value NextSibling(const Napi::CallbackInfo &info) { +Napi::Value NextSibling(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id) { + if (node.id != nullptr) { return MarshalNode(info, tree, ts_node_next_sibling(node)); } return MarshalNullNode(env); } -static Napi::Value NextNamedSibling(const Napi::CallbackInfo &info) { +Napi::Value NextNamedSibling(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id) { + if (node.id != nullptr) { return MarshalNode(info, tree, ts_node_next_named_sibling(node)); } return MarshalNullNode(env); } -static Napi::Value PreviousSibling(const Napi::CallbackInfo &info) { +Napi::Value PreviousSibling(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id) { + if (node.id != nullptr) { return MarshalNode(info, tree, ts_node_prev_sibling(node)); } return MarshalNullNode(env); } -static Napi::Value PreviousNamedSibling(const Napi::CallbackInfo &info) { +Napi::Value PreviousNamedSibling(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id) { + if (node.id != nullptr) { return MarshalNode(info, tree, ts_node_prev_named_sibling(node)); } return MarshalNullNode(env); } -struct SymbolSet { - std::basic_string symbols; - void add(TSSymbol symbol) { symbols += symbol; } - bool contains(TSSymbol symbol) { return symbols.find(symbol) != symbols.npos; } -}; - -void symbol_set_from_js(SymbolSet *symbols, const Napi::Value &value, const TSLanguage *language) { - Env env = value.Env(); - - if (!value.IsArray()) { - throw TypeError::New(env, "Argument must be a string or array of strings"); - } - - unsigned symbol_count = ts_language_symbol_count(language); - - auto js_types = value.As(); - for (unsigned i = 0, n = js_types.Length(); i < n; i++) { - Value js_node_type_value = js_types[i]; - if (js_node_type_value.IsString()) { - String js_node_type = js_node_type_value.As(); - std::string node_type = js_node_type.Utf8Value(); - - if (node_type == "ERROR") { - symbols->add(static_cast(-1)); - } else { - for (TSSymbol j = 0; j < symbol_count; j++) { - if (node_type == ts_language_symbol_name(language, j)) { - symbols->add(j); - } - } - } - - continue; - } - - throw TypeError::New(env, "Argument must be a string or array of strings"); - } -} - -static Napi::Value Children(const Napi::CallbackInfo &info) { +Napi::Value Children(const Napi::CallbackInfo &info) { Env env = info.Env(); - AddonData* data = env.GetInstanceData(); + auto* data = env.GetInstanceData(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (!node.id) return env.Undefined(); + if (node.id == nullptr) { + return env.Undefined(); + } vector result; ts_tree_cursor_reset(&data->scratch_cursor, node); @@ -556,12 +570,14 @@ static Napi::Value Children(const Napi::CallbackInfo &info) { return MarshalNodes(info, tree, result.data(), result.size()); } -static Napi::Value NamedChildren(const Napi::CallbackInfo &info) { +Napi::Value NamedChildren(const Napi::CallbackInfo &info) { Env env = info.Env(); - AddonData* data = env.GetInstanceData(); + auto* data = env.GetInstanceData(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (!node.id) return env.Undefined(); + if (node.id == nullptr) { + return env.Undefined(); + } vector result; ts_tree_cursor_reset(&data->scratch_cursor, node); @@ -577,12 +593,14 @@ static Napi::Value NamedChildren(const Napi::CallbackInfo &info) { return MarshalNodes(info, tree, result.data(), result.size()); } -static Napi::Value DescendantsOfType(const Napi::CallbackInfo &info) { +Napi::Value DescendantsOfType(const Napi::CallbackInfo &info) { Env env = info.Env(); - AddonData* data = env.GetInstanceData(); + auto* data = env.GetInstanceData(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (!node.id) return env.Undefined(); + if (node.id == nullptr) { + return env.Undefined(); + } SymbolSet symbols; symbol_set_from_js(&symbols, info[1], ts_tree_language(node.tree)); @@ -592,13 +610,17 @@ static Napi::Value DescendantsOfType(const Napi::CallbackInfo &info) { if (info.Length() > 2 && info[2].IsObject()) { auto maybe_start_point = PointFromJS(info[2]); - if (maybe_start_point.IsNothing()) return env.Undefined(); + if (maybe_start_point.IsNothing()) { + return env.Undefined(); + } start_point = maybe_start_point.Unwrap(); } if (info.Length() > 3 && info[3].IsObject()) { auto maybe_end_point = PointFromJS(info[3]); - if (maybe_end_point.IsNothing()) return env.Undefined(); + if (maybe_end_point.IsNothing()) { + return env.Undefined(); + } end_point = maybe_end_point.Unwrap(); } @@ -613,13 +635,17 @@ static Napi::Value DescendantsOfType(const Napi::CallbackInfo &info) { if (ts_tree_cursor_goto_next_sibling(&data->scratch_cursor)) { already_visited_children = false; } else { - if (!ts_tree_cursor_goto_parent(&data->scratch_cursor)) break; + if (!ts_tree_cursor_goto_parent(&data->scratch_cursor)) { + break; + } already_visited_children = true; } continue; } - if (end_point <= ts_node_start_point(descendant)) break; + if (end_point <= ts_node_start_point(descendant)) { + break; + } if (symbols.contains(ts_node_symbol(descendant))) { found.push_back(descendant); @@ -630,14 +656,18 @@ static Napi::Value DescendantsOfType(const Napi::CallbackInfo &info) { } else if (ts_tree_cursor_goto_next_sibling(&data->scratch_cursor)) { already_visited_children = false; } else { - if (!ts_tree_cursor_goto_parent(&data->scratch_cursor)) break; + if (!ts_tree_cursor_goto_parent(&data->scratch_cursor)) { + break; + } already_visited_children = true; } } else { if (ts_tree_cursor_goto_next_sibling(&data->scratch_cursor)) { already_visited_children = false; } else { - if (!ts_tree_cursor_goto_parent(&data->scratch_cursor)) break; + if (!ts_tree_cursor_goto_parent(&data->scratch_cursor)) { + break; + } } } } @@ -645,12 +675,14 @@ static Napi::Value DescendantsOfType(const Napi::CallbackInfo &info) { return MarshalNodes(info, tree, found.data(), found.size()); } -static Napi::Value ChildNodesForFieldId(const Napi::CallbackInfo &info) { +Napi::Value ChildNodesForFieldId(const Napi::CallbackInfo &info) { Env env = info.Env(); - AddonData* data = env.GetInstanceData(); + auto* data = env.GetInstanceData(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (!node.id) return env.Undefined(); + if (node.id == nullptr) { + return env.Undefined(); + } if (!info[1].IsNumber()) { throw TypeError::New(env, "Second argument must be an integer"); @@ -672,12 +704,12 @@ static Napi::Value ChildNodesForFieldId(const Napi::CallbackInfo &info) { return MarshalNodes(info, tree, result.data(), result.size()); } -static Napi::Value ChildNodeForFieldId(const Napi::CallbackInfo &info) { +Napi::Value ChildNodeForFieldId(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id) { + if (node.id != nullptr) { if (!info[1].IsNumber()) { throw TypeError::New(env, "Second argument must be an integer"); } @@ -688,18 +720,22 @@ static Napi::Value ChildNodeForFieldId(const Napi::CallbackInfo &info) { return MarshalNullNode(env); } -static Napi::Value Closest(const Napi::CallbackInfo &info) { +Napi::Value Closest(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (!node.id) return env.Undefined(); + if (node.id == nullptr) { + return env.Undefined(); + } SymbolSet symbols; symbol_set_from_js(&symbols, info[1], ts_tree_language(node.tree)); for (;;) { TSNode parent = ts_node_parent(node); - if (!parent.id) break; + if (parent.id == nullptr) { + break; + } if (symbols.contains(ts_node_symbol(parent))) { return MarshalNode(info, tree, parent); } @@ -709,7 +745,7 @@ static Napi::Value Closest(const Napi::CallbackInfo &info) { return MarshalNullNode(env); } -static Napi::Value Walk(const Napi::CallbackInfo &info) { +Napi::Value Walk(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); @@ -717,8 +753,10 @@ static Napi::Value Walk(const Napi::CallbackInfo &info) { return TreeCursor::NewInstance(env, cursor); } +} // namespace + void Init(Napi::Env env, Napi::Object exports) { - auto data = env.GetInstanceData(); + auto *data = env.GetInstanceData(); Object result = Object::New(env); @@ -762,8 +800,8 @@ void Init(Napi::Env env, Napi::Object exports) { {"childNodesForFieldId", ChildNodesForFieldId}, }; - for (size_t i = 0; i < length_of_array(methods); i++) { - result[methods[i].name] = Napi::Function::New(env, methods[i].callback); + for (auto & method : methods) { + result[method.name] = Napi::Function::New(env, method.callback); } data->module_exports = Napi::Persistent(exports); @@ -772,5 +810,5 @@ void Init(Napi::Env env, Napi::Object exports) { exports["NodeMethods"] = result; } -} // namespace node_methods -} // namespace node_tree_sitter +} // namespace node_tree_sitter::node_methods + diff --git a/src/node.h b/src/node.h index f4415ff2..451de14f 100644 --- a/src/node.h +++ b/src/node.h @@ -1,14 +1,12 @@ #ifndef NODE_TREE_SITTER_NODE_H_ #define NODE_TREE_SITTER_NODE_H_ -#include -#include #include "./tree.h" -using namespace Napi; +#include +#include -namespace node_tree_sitter { -namespace node_methods { +namespace node_tree_sitter::node_methods { void Init(Napi::Env, Napi::Object); Napi::Value MarshalNode(const Napi::CallbackInfo &info, const Tree *, TSNode); @@ -27,7 +25,7 @@ static inline void MarshalNodeId(const void *id, uint32_t *buffer) { memcpy(buffer, &id, sizeof(id)); } -} // namespace node_methods -} // namespace node_tree_sitter +} // namespace node_tree_sitter::node_methods + #endif // NODE_TREE_SITTER_NODE_H_ diff --git a/src/parser.cc b/src/parser.cc index beabb08c..b712d2ba 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -1,25 +1,20 @@ #include "./parser.h" -#include -#include -#include -#include #include "./conversions.h" #include "./language.h" #include "./logger.h" #include "./tree.h" -#include "./util.h" -#include + +#include +#include using namespace Napi; using std::vector; -using std::pair; namespace node_tree_sitter { class CallbackInput { public: - CallbackInput(Function callback, Napi::Value js_buffer_size) - : byte_offset(0) { + CallbackInput(Function callback, Napi::Value js_buffer_size) { this->callback.Reset(callback, 1); if (js_buffer_size.IsNumber()) { buffer.resize(js_buffer_size.As().Uint32Value()); @@ -30,7 +25,7 @@ class CallbackInput { TSInput Input() { TSInput result; - result.payload = (void *)this; + result.payload = static_cast(this); result.encoding = TSInputEncodingUTF16; result.read = Read; return result; @@ -39,12 +34,12 @@ class CallbackInput { private: static String slice(String s, uint32_t offset) { Env env = s.Env(); - auto data = env.GetInstanceData(); + auto *data = env.GetInstanceData(); return data->string_slice.Call(s, {Number::New(s.Env(), offset)}).As(); } static const char * Read(void *payload, uint32_t byte, TSPoint position, uint32_t *bytes_read) { - CallbackInput *reader = (CallbackInput *)payload; + auto *reader = static_cast(payload); Napi::Env env = reader->callback.Env(); if (byte != reader->byte_offset) { @@ -62,8 +57,12 @@ class CallbackInput { ByteCountToJS(env, byte), PointToJS(env, position), }); - if (env.IsExceptionPending()) return nullptr; - if (!result_value.IsString()) return nullptr; + if (env.IsExceptionPending()) { + return nullptr; + } + if (!result_value.IsString()) { + return nullptr; + } result = result_value.As(); } @@ -73,11 +72,15 @@ class CallbackInput { status = napi_get_value_string_utf16( env, result, nullptr, 0, &length ); - if (status != napi_ok) return nullptr; + if (status != napi_ok) { + return nullptr; + } status = napi_get_value_string_utf16( - env, result, (char16_t *)&reader->buffer[0], reader->buffer.size(), &utf16_units_read + env, result, reinterpret_cast(reader->buffer.data()), reader->buffer.size(), &utf16_units_read ); - if (status != napi_ok) return nullptr; + if (status != napi_ok) { + return nullptr; + } *bytes_read = 2 * utf16_units_read; reader->byte_offset += *bytes_read; @@ -88,17 +91,17 @@ class CallbackInput { reader->partial_string.Reset(); } - return (const char *)reader->buffer.data(); + return reinterpret_cast(reader->buffer.data()); } FunctionReference callback; std::vector buffer; - size_t byte_offset; + size_t byte_offset {}; Reference partial_string; }; void Parser::Init(Napi::Env env, Napi::Object exports) { - auto data = env.GetInstanceData(); + auto *data = env.GetInstanceData(); Function ctor = DefineClass(env, "Parser", { InstanceMethod("getLogger", &Parser::GetLogger, napi_default_method), @@ -121,16 +124,22 @@ Parser::Parser(const Napi::CallbackInfo &info) : Napi::ObjectWrap(info), Parser::~Parser() { ts_parser_delete(parser_); } -static bool handle_included_ranges(Napi::Env env, TSParser *parser, Napi::Value arg) { +namespace { + +bool handle_included_ranges(Napi::Env env, TSParser *parser, Napi::Value arg) { uint32_t last_included_range_end = 0; if (arg.IsArray()) { auto js_included_ranges = arg.As(); vector included_ranges; for (unsigned i = 0; i < js_included_ranges.Length(); i++) { Value range_value = js_included_ranges[i]; - if (!range_value.IsObject()) return false; + if (!range_value.IsObject()) { + return false; + } auto maybe_range = RangeFromJS(range_value); - if (!maybe_range.IsJust()) return false; + if (!maybe_range.IsJust()) { + return false; + } auto range = maybe_range.Unwrap(); if (range.start_byte < last_included_range_end) { throw RangeError::New(env, "Overlapping ranges"); @@ -146,9 +155,11 @@ static bool handle_included_ranges(Napi::Env env, TSParser *parser, Napi::Value return true; } +} // namespace + Napi::Value Parser::SetLanguage(const Napi::CallbackInfo &info) { const TSLanguage *language = language_methods::UnwrapLanguage(info[0]); - if (language) { + if (language != nullptr) { ts_parser_set_language(parser_, language); return info.This(); } @@ -162,23 +173,27 @@ Napi::Value Parser::Parse(const CallbackInfo &info) { throw TypeError::New(env, "Input must be a function"); } - Function callback = info[0].As(); + auto callback = info[0].As(); Object js_old_tree; const TSTree *old_tree = nullptr; if (info.Length() > 1 && !info[1].IsNull() && !info[1].IsUndefined() && info[1].IsObject()) { js_old_tree = info[1].As(); const Tree *tree = Tree::UnwrapTree(js_old_tree); - if (!tree) { + if (tree == nullptr) { throw TypeError::New(env, "Second argument must be a tree"); } old_tree = tree->tree_; } Napi::Value buffer_size = env.Null(); - if (info.Length() > 2) buffer_size = info[2]; + if (info.Length() > 2) { + buffer_size = info[2]; + } - if (!handle_included_ranges(env, parser_, info[3])) return env.Undefined(); + if (!handle_included_ranges(env, parser_, info[3])) { + return env.Undefined(); + } CallbackInput callback_input(callback, buffer_size); TSTree *tree = ts_parser_parse(parser_, old_tree, callback_input.Input()); @@ -188,23 +203,27 @@ Napi::Value Parser::Parse(const CallbackInfo &info) { Napi::Value Parser::GetLogger(const Napi::CallbackInfo &info) { TSLogger current_logger = ts_parser_logger(parser_); - if (current_logger.payload && current_logger.log == Logger::Log) { - Logger *logger = (Logger *)current_logger.payload; + if ((current_logger.payload != nullptr) && current_logger.log == Logger::Log) { + auto *logger = static_cast(current_logger.payload); return logger->func.Value(); - } else { - return info.Env().Null(); } + return info.Env().Null(); + } Napi::Value Parser::SetLogger(const Napi::CallbackInfo &info) { TSLogger current_logger = ts_parser_logger(parser_); if (info[0].IsFunction()) { - if (current_logger.payload) delete (Logger *)current_logger.payload; + if (current_logger.payload != nullptr) { + delete static_cast(current_logger.payload); + } ts_parser_set_logger(parser_, Logger::Make(info[0].As())); } else if (info[0].IsEmpty() || info[0].IsUndefined() || info[0].IsNull() || (info[0].IsBoolean() && !info[0].As())) { - if (current_logger.payload) delete (Logger *)current_logger.payload; - ts_parser_set_logger(parser_, { 0, 0 }); + if (current_logger.payload != nullptr) { + delete static_cast(current_logger.payload); + } + ts_parser_set_logger(parser_, { nullptr, nullptr }); } else { throw TypeError::New(info.Env(), "Logger callback must either be a function or a falsy value"); } diff --git a/src/parser.h b/src/parser.h index 0756529c..69f4076f 100644 --- a/src/parser.h +++ b/src/parser.h @@ -11,7 +11,7 @@ class Parser : public Napi::ObjectWrap { public: static void Init(Napi::Env env, Napi::Object exports); explicit Parser(const Napi::CallbackInfo &info); - ~Parser(); + ~Parser() override; private: TSParser *parser_; diff --git a/src/query.cc b/src/query.cc index 05aecc5f..59705f47 100644 --- a/src/query.cc +++ b/src/query.cc @@ -1,20 +1,16 @@ #include "./query.h" +#include "./language.h" +#include "./node.h" + +#include #include #include -#include -#include "./node.h" -#include "./language.h" -#include "./logger.h" -#include "./util.h" -#include "./conversions.h" using std::vector; using namespace Napi; namespace node_tree_sitter { -using node_methods::UnmarshalNodeId; - const char *query_error_names[] = { "TSQueryErrorNone", "TSQueryErrorSyntax", @@ -25,7 +21,7 @@ const char *query_error_names[] = { }; void Query::Init(Napi::Env env, Napi::Object exports) { - auto data = env.GetInstanceData(); + auto *data = env.GetInstanceData(); data->ts_query_cursor = ts_query_cursor_new(); Function ctor = DefineClass(env, "Query", { @@ -58,7 +54,7 @@ Query::Query(const Napi::CallbackInfo &info) : Napi::ObjectWrap(info) , q source_len = utf8_string.length(); query_ = ts_query_new(language, source, source_len, &error_offset, &error_type); } else if (info[1].IsBuffer()) { - Buffer buf = info[1].As>(); + auto buf = info[1].As>(); source = buf.Data(); source_len = buf.Length(); query_ = ts_query_new(language, source, source_len, &error_offset, &error_type); @@ -84,17 +80,21 @@ Query::~Query() { } Query *Query::UnwrapQuery(const Napi::Value &value) { - auto data = value.Env().GetInstanceData(); - if (!value.IsObject()) return nullptr; - Napi::Object js_query = value.As(); - if (!js_query.InstanceOf(data->query_constructor.Value())) return nullptr; + auto *data = value.Env().GetInstanceData(); + if (!value.IsObject()) { + return nullptr; + } + auto js_query = value.As(); + if (!js_query.InstanceOf(data->query_constructor.Value())) { + return nullptr; + } return Query::Unwrap(js_query); } Napi::Value Query::GetPredicates(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); Query *query = Query::UnwrapQuery(info.This()); - auto ts_query = query->query_; + auto *ts_query = query->query_; auto pattern_len = ts_query_pattern_count(ts_query); @@ -145,7 +145,7 @@ Napi::Value Query::GetPredicates(const Napi::CallbackInfo &info) { Napi::Value Query::Matches(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); - auto data = env.GetInstanceData(); + auto *data = env.GetInstanceData(); Query *query = Query::UnwrapQuery(info.This()); const Tree *tree = Tree::UnwrapTree(info[0]); uint32_t start_row = 0; @@ -206,14 +206,14 @@ Napi::Value Query::Matches(const Napi::CallbackInfo &info) { auto js_nodes = node_methods::GetMarshalNodes(info, tree, nodes.data(), nodes.size()); auto result = Array::New(env); - result[0u] = js_matches; + result[0U] = js_matches; result[1] = js_nodes; return result; } Napi::Value Query::Captures(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); - auto data = env.GetInstanceData(); + auto *data = env.GetInstanceData(); Query *query = Query::UnwrapQuery(info.This()); const Tree *tree = Tree::UnwrapTree(info[0]); uint32_t start_row = 0; @@ -281,7 +281,7 @@ Napi::Value Query::Captures(const Napi::CallbackInfo &info) { auto js_nodes = node_methods::GetMarshalNodes(info, tree, nodes.data(), nodes.size()); auto result = Array::New(env); - result[0u] = js_matches; + result[0U] = js_matches; result[1] = js_nodes; return result; } diff --git a/src/query.h b/src/query.h index 921afdf8..64ad20f2 100644 --- a/src/query.h +++ b/src/query.h @@ -1,11 +1,11 @@ #ifndef NODE_TREE_SITTER_QUERY_H_ #define NODE_TREE_SITTER_QUERY_H_ +#include "./addon_data.h" + #include #include -#include #include -#include "./addon_data.h" namespace node_tree_sitter { @@ -15,11 +15,10 @@ class Query : public Napi::ObjectWrap { static Query *UnwrapQuery(const Napi::Value &); explicit Query(const Napi::CallbackInfo &info); - ~Query(); - - TSQuery *query_; + ~Query() override; private: + TSQuery *query_; Napi::Value New(const Napi::CallbackInfo &); Napi::Value Matches(const Napi::CallbackInfo &); diff --git a/src/tree.cc b/src/tree.cc index 967e7a3f..7be15dcb 100644 --- a/src/tree.cc +++ b/src/tree.cc @@ -1,11 +1,9 @@ #include "./tree.h" #include "./addon_data.h" -#include -#include -#include "./node.h" -#include "./logger.h" -#include "./util.h" #include "./conversions.h" +#include "./node.h" + +#include using namespace Napi; @@ -14,7 +12,7 @@ namespace node_tree_sitter { using node_methods::UnmarshalNodeId; void Tree::Init(Napi::Env env, Napi::Object exports) { - auto data = env.GetInstanceData(); + auto *data = env.GetInstanceData(); Function ctor = DefineClass(env, "Tree", { InstanceMethod("edit", &Tree::Edit, napi_default_method), @@ -40,8 +38,8 @@ Tree::~Tree() { } Napi::Value Tree::NewInstance(Napi::Env env, TSTree *tree) { - auto data = env.GetInstanceData(); - if (tree) { + auto *data = env.GetInstanceData(); + if (tree != nullptr) { Object self = data->tree_constructor.New({}); Tree::Unwrap(self)->tree_ = tree; return self; @@ -51,28 +49,31 @@ Napi::Value Tree::NewInstance(Napi::Env env, TSTree *tree) { } const Tree *Tree::UnwrapTree(const Napi::Value &value) { - auto data = value.Env().GetInstanceData(); - if (!value.IsObject()) return nullptr; - Object js_tree = value.As(); - if (!js_tree.InstanceOf(data->tree_constructor.Value())) return nullptr; + auto *data = value.Env().GetInstanceData(); + if (!value.IsObject()) { + return nullptr; + } + auto js_tree = value.As(); + if (!js_tree.InstanceOf(data->tree_constructor.Value())) { + return nullptr; + } return Tree::Unwrap(js_tree); } #define read_number_from_js(out, value, name) \ - if (!value.IsNumber()) { \ + if (!(value).IsNumber()) { \ throw TypeError::New(env, name " must be an integer"); \ return env.Undefined(); \ } \ - *(out) = value.As().Uint32Value(); + *(out) = (value).As().Uint32Value(); #define read_byte_count_from_js(out, value, name) \ read_number_from_js(out, value, name); \ - (*out) *= 2 + (*(out)) *= 2 Napi::Value Tree::Edit(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); TSInputEdit edit; - // Nan::Maybe maybe_number = Nan::Nothing(); read_number_from_js(&edit.start_point.row, info[0], "startPosition.row"); read_byte_count_from_js(&edit.start_point.column, info[1], "startPosition.column"); read_number_from_js(&edit.old_end_point.row, info[2], "oldEndPosition.row"); @@ -99,7 +100,7 @@ Napi::Value Tree::Edit(const Napi::CallbackInfo &info) { ts_node_edit(&node, &edit); for (unsigned i = 0; i < 4; i++) { - js_node[i + 2u] = Number::New(env, node.context[i]); + js_node[i + 2U] = Number::New(env, node.context[i]); } } @@ -113,7 +114,7 @@ Napi::Value Tree::RootNode(const Napi::CallbackInfo &info) { Napi::Value Tree::GetChangedRanges(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); const Tree *other_tree = UnwrapTree(info[0]); - if (!other_tree) { + if (other_tree == nullptr) { throw TypeError::New(env, "Argument must be a tree"); } @@ -133,7 +134,9 @@ Napi::Value Tree::GetChangedRanges(const Napi::CallbackInfo &info) { Napi::Value Tree::GetEditedRange(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); TSNode root = ts_tree_root_node(tree_); - if (!ts_node_has_changes(root)) return env.Undefined(); + if (!ts_node_has_changes(root)) { + return env.Undefined(); + } TSRange result = { ts_node_start_point(root), ts_node_end_point(root), @@ -144,14 +147,17 @@ Napi::Value Tree::GetEditedRange(const Napi::CallbackInfo &info) { TSTreeCursor cursor = ts_tree_cursor_new(root); while (true) { - if (!ts_tree_cursor_goto_first_child(&cursor)) break; + if (!ts_tree_cursor_goto_first_child(&cursor)) { + break; + } while (true) { TSNode node = ts_tree_cursor_current_node(&cursor); if (ts_node_has_changes(node)) { result.start_byte = ts_node_start_byte(node); result.start_point = ts_node_start_point(node); break; - } else if (!ts_tree_cursor_goto_next_sibling(&cursor)) { + } + if (!ts_tree_cursor_goto_next_sibling(&cursor)) { break; } } @@ -160,7 +166,9 @@ Napi::Value Tree::GetEditedRange(const Napi::CallbackInfo &info) { while (ts_tree_cursor_goto_parent(&cursor)) {} while (true) { - if (!ts_tree_cursor_goto_first_child(&cursor)) break; + if (!ts_tree_cursor_goto_first_child(&cursor)) { + break; + } while (true) { TSNode node = ts_tree_cursor_current_node(&cursor); if (ts_node_has_changes(node)) { @@ -183,19 +191,21 @@ Napi::Value Tree::PrintDotGraph(const Napi::CallbackInfo &info) { return info.This(); } -static void FinalizeNode(Napi::Env env, Tree::NodeCacheEntry *cache_entry) { +namespace { + +void FinalizeNode(Napi::Env _env, Tree::NodeCacheEntry *cache_entry) { assert(!cache_entry->node.IsEmpty()); cache_entry->node.Reset(); - if (cache_entry->tree) { + if (cache_entry->tree != nullptr) { assert(cache_entry->tree->cached_nodes_.count(cache_entry->key)); cache_entry->tree->cached_nodes_.erase(cache_entry->key); } delete cache_entry; } -static void CacheNodeForTree(Tree *tree, Napi::Env env, Object js_node) { - Value js_node_field1 = js_node[0u]; - Value js_node_field2 = js_node[1u]; +void CacheNodeForTree(Tree *tree, Napi::Env _env, Object js_node) { + Value js_node_field1 = js_node[0U]; + Value js_node_field2 = js_node[1U]; if (!js_node_field1.IsNumber() || !js_node_field2.IsNumber()) { return; } @@ -205,7 +215,7 @@ static void CacheNodeForTree(Tree *tree, Napi::Env env, Object js_node) { }; const void *key = UnmarshalNodeId(key_parts); - auto cache_entry = new Tree::NodeCacheEntry{tree, key, {}}; + auto *cache_entry = new Tree::NodeCacheEntry{tree, key, {}}; cache_entry->node.Reset(js_node, 0); js_node.AddFinalizer(&FinalizeNode, cache_entry); @@ -214,6 +224,8 @@ static void CacheNodeForTree(Tree *tree, Napi::Env env, Object js_node) { tree->cached_nodes_[key] = cache_entry; } +} // namespace + Napi::Value Tree::CacheNode(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); @@ -231,7 +243,7 @@ Napi::Value Tree::CacheNodes(const Napi::CallbackInfo &info) { if (!info[0].IsArray()) { throw TypeError::New(env, "not an array"); } - Array js_nodes = info[0].As(); + auto js_nodes = info[0].As(); uint32_t length = js_nodes.Length(); for (uint32_t i = 0; i < length; ++i) { diff --git a/src/tree.h b/src/tree.h index 44090fa8..412f260e 100644 --- a/src/tree.h +++ b/src/tree.h @@ -1,11 +1,12 @@ #ifndef NODE_TREE_SITTER_TREE_H_ #define NODE_TREE_SITTER_TREE_H_ +#include "./addon_data.h" + #include #include -#include #include -#include "./addon_data.h" +#include namespace node_tree_sitter { @@ -16,7 +17,7 @@ class Tree : public Napi::ObjectWrap { static const Tree *UnwrapTree(const Napi::Value &); explicit Tree(const Napi::CallbackInfo &); - ~Tree(); + ~Tree() override; struct NodeCacheEntry { Tree *tree; diff --git a/src/tree_cursor.cc b/src/tree_cursor.cc index 38bdc207..072f3a57 100644 --- a/src/tree_cursor.cc +++ b/src/tree_cursor.cc @@ -1,17 +1,17 @@ -#include "./tree_cursor.h" -#include -#include -#include "./util.h" #include "./conversions.h" #include "./node.h" #include "./tree.h" +#include "./tree_cursor.h" + +#include +#include using namespace Napi; namespace node_tree_sitter { void TreeCursor::Init(Napi::Env env, Napi::Object exports) { - auto data = env.GetInstanceData(); + auto *data = env.GetInstanceData(); Function ctor = DefineClass(env, "TreeCursor", { InstanceAccessor("startIndex", &TreeCursor::StartIndex, nullptr, napi_default_method), @@ -36,7 +36,7 @@ void TreeCursor::Init(Napi::Env env, Napi::Object exports) { } Napi::Value TreeCursor::NewInstance(Napi::Env env, TSTreeCursor cursor) { - auto data = env.GetInstanceData(); + auto *data = env.GetInstanceData(); Object self = data->tree_cursor_constructor.New({}); TreeCursor::Unwrap(self)->cursor_ = cursor; @@ -67,9 +67,9 @@ Napi::Value TreeCursor::GotoFirstChildForIndex(const Napi::CallbackInfo &info) { int64_t child_index = ts_tree_cursor_goto_first_child_for_byte(&cursor_, goal_byte); if (child_index < 0) { return env.Null(); - } else { - return Number::New(env, child_index); } + return Number::New(env, static_cast(child_index)); + } Napi::Value TreeCursor::GotoNextSibling(const Napi::CallbackInfo &info) { @@ -122,7 +122,7 @@ Napi::Value TreeCursor::NodeIsMissing(const Napi::CallbackInfo &info) { Napi::Value TreeCursor::CurrentFieldName(const Napi::CallbackInfo &info) { const char *field_name = ts_tree_cursor_current_field_name(&cursor_); - if (field_name) { + if (field_name != nullptr) { return String::New(info.Env(), field_name); } return info.Env().Undefined(); @@ -138,4 +138,4 @@ Napi::Value TreeCursor::EndIndex(const Napi::CallbackInfo &info) { return ByteCountToJS(info.Env(), ts_node_end_byte(node)); } -} +} // namespace node_tree_sitter diff --git a/src/tree_cursor.h b/src/tree_cursor.h index 54448da6..ea5b7752 100644 --- a/src/tree_cursor.h +++ b/src/tree_cursor.h @@ -1,10 +1,11 @@ #ifndef NODE_TREE_SITTER_TREE_CURSOR_H_ #define NODE_TREE_SITTER_TREE_CURSOR_H_ +#include "./addon_data.h" + #include #include #include -#include "./addon_data.h" namespace node_tree_sitter { @@ -14,7 +15,7 @@ class TreeCursor : public Napi::ObjectWrap { static Napi::Value NewInstance(Napi::Env Env, TSTreeCursor); explicit TreeCursor(const Napi::CallbackInfo &); - ~TreeCursor(); + ~TreeCursor() override; private: Napi::Value GotoParent(const Napi::CallbackInfo &); From d245a63e0d456a7d369a4247f452e71d026431b8 Mon Sep 17 00:00:00 2001 From: Segev Finer Date: Thu, 29 Feb 2024 00:02:22 +0200 Subject: [PATCH 4/8] feat: use type tags --- package.json | 2 +- src/language.cc | 16 +++++++++++++--- src/query.cc | 16 ++++++++++++++-- src/tree.cc | 22 +++++++++++++++++++--- 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 70aacefd..0ad54220 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "mocha": "^8.4.0", "node-gyp": "^10.0.1", "prebuild": "^13.0.0", - "tree-sitter-javascript": "github:segevfiner/tree-sitter-javascript#napi-2" + "tree-sitter-javascript": "github:segevfiner/tree-sitter-javascript#napi-type-tag" }, "scripts": { "install": "prebuild-install -r napi || node-gyp rebuild", diff --git a/src/language.cc b/src/language.cc index 0c4f3cc6..2c6c7453 100644 --- a/src/language.cc +++ b/src/language.cc @@ -8,6 +8,17 @@ using namespace Napi; namespace node_tree_sitter::language_methods { +/* + tstag() { + b2sum -l64 <(printf tree-sitter) <(printf "$1") | \ + awk '{printf "0x" toupper($1) (NR == 1 ? ", " : "\n")}' + } + tstag language # => 0x8AF2E5212AD58ABF, 0xD5006CAD83ABBA16 +*/ +const napi_type_tag LANGUAGE_TYPE_TAG = { + 0x8AF2E5212AD58ABF, 0xD5006CAD83ABBA16 +}; + const TSLanguage *UnwrapLanguage(Napi::Value value) { Napi::Env env = value.Env(); @@ -15,8 +26,8 @@ const TSLanguage *UnwrapLanguage(Napi::Value value) { value = value.As()["language"]; } - if (value.IsExternal()) { - auto arg = value.As>(); + auto arg = value.As>(); + if (arg.IsExternal() && arg.CheckTypeTag(&LANGUAGE_TYPE_TAG)) { const TSLanguage *language = arg.Data(); if (language != nullptr) { uint16_t version = ts_language_version(language); @@ -93,4 +104,3 @@ void Init(Napi::Env env, Napi::Object exports) { } } // namespace node_tree_sitter::language_methods - diff --git a/src/query.cc b/src/query.cc index 59705f47..bbc1f574 100644 --- a/src/query.cc +++ b/src/query.cc @@ -11,6 +11,17 @@ using namespace Napi; namespace node_tree_sitter { +/* + tstag() { + b2sum -l64 <(printf tree-sitter) <(printf "$1") | \ + awk '{printf "0x" toupper($1) (NR == 1 ? ", " : "\n")}' + } + tstag query # => 0x8AF2E5212AD58ABF, 0x7B1FAB666CBD6803 +*/ +const napi_type_tag QUERY_TYPE_TAG = { + 0x8AF2E5212AD58ABF, 0x7B1FAB666CBD6803 +}; + const char *query_error_names[] = { "TSQueryErrorNone", "TSQueryErrorSyntax", @@ -37,6 +48,8 @@ void Query::Init(Napi::Env env, Napi::Object exports) { Query::Query(const Napi::CallbackInfo &info) : Napi::ObjectWrap(info) , query_(nullptr) { Napi::Env env = info.Env(); + Value().TypeTag(&QUERY_TYPE_TAG); + const TSLanguage *language = language_methods::UnwrapLanguage(info[0]); const char *source; uint32_t source_len; @@ -80,12 +93,11 @@ Query::~Query() { } Query *Query::UnwrapQuery(const Napi::Value &value) { - auto *data = value.Env().GetInstanceData(); if (!value.IsObject()) { return nullptr; } auto js_query = value.As(); - if (!js_query.InstanceOf(data->query_constructor.Value())) { + if (!js_query.CheckTypeTag(&QUERY_TYPE_TAG)) { return nullptr; } return Query::Unwrap(js_query); diff --git a/src/tree.cc b/src/tree.cc index 7be15dcb..c8d847a0 100644 --- a/src/tree.cc +++ b/src/tree.cc @@ -9,6 +9,17 @@ using namespace Napi; namespace node_tree_sitter { +/* + tstag() { + b2sum -l64 <(printf tree-sitter) <(printf "$1") | \ + awk '{printf "0x" toupper($1) (NR == 1 ? ", " : "\n")}' + } + tstag tree # => 0x8AF2E5212AD58ABF, 0x7FA28BFC1966AC2D +*/ +const napi_type_tag TREE_TYPE_TAG = { + 0x8AF2E5212AD58ABF, 0x7FA28BFC1966AC2D +}; + using node_methods::UnmarshalNodeId; void Tree::Init(Napi::Env env, Napi::Object exports) { @@ -28,7 +39,9 @@ void Tree::Init(Napi::Env env, Napi::Object exports) { exports["Tree"] = ctor; } -Tree::Tree(const Napi::CallbackInfo &info) : Napi::ObjectWrap(info), tree_(nullptr) {} +Tree::Tree(const Napi::CallbackInfo &info) : Napi::ObjectWrap(info), tree_(nullptr) { + Value().TypeTag(&TREE_TYPE_TAG); +} Tree::~Tree() { ts_tree_delete(tree_); @@ -49,12 +62,11 @@ Napi::Value Tree::NewInstance(Napi::Env env, TSTree *tree) { } const Tree *Tree::UnwrapTree(const Napi::Value &value) { - auto *data = value.Env().GetInstanceData(); if (!value.IsObject()) { return nullptr; } auto js_tree = value.As(); - if (!js_tree.InstanceOf(data->tree_constructor.Value())) { + if (!js_tree.CheckTypeTag(&TREE_TYPE_TAG)) { return nullptr; } return Tree::Unwrap(js_tree); @@ -258,3 +270,7 @@ Napi::Value Tree::CacheNodes(const Napi::CallbackInfo &info) { } } // namespace node_tree_sitter + + + + From e0669fb14f3705ed14a0316ef4a01d37db11672e Mon Sep 17 00:00:00 2001 From: Amaan Qureshi Date: Fri, 1 Mar 2024 10:45:10 -0500 Subject: [PATCH 5/8] feat: keep API in-line with upstream --- binding.gyp | 1 + index.js | 98 +++- package.json | 14 +- src/addon_data.h | 10 +- src/binding.cc | 4 +- src/conversions.cc | 4 +- src/conversions.h | 6 +- src/language.cc | 10 +- src/language.h | 7 +- src/logger.cc | 4 +- src/logger.h | 7 +- src/lookaheaditerator.cc | 135 ++++++ src/lookaheaditerator.h | 34 ++ src/node.cc | 455 ++++++++++++++---- src/node.h | 7 +- src/parser.cc | 76 ++- src/parser.h | 19 +- src/query.cc | 77 ++- src/query.h | 18 +- src/tree.cc | 35 +- src/tree.h | 31 +- src/tree_cursor.cc | 102 +++- src/tree_cursor.h | 26 +- src/util.h | 17 - test/lookahead_iterator_test.js | 38 ++ test/node_test.js | 512 +++++++++++++++++++- test/parser_test.js | 506 ++++++++++++++++++- test/query_test.js | 829 +++++++++++++++++++++++++++++++- test/tree_test.js | 302 ++++++++++-- tree-sitter.d.ts | 82 +++- 30 files changed, 3165 insertions(+), 301 deletions(-) create mode 100644 src/lookaheaditerator.cc create mode 100644 src/lookaheaditerator.h delete mode 100644 src/util.h create mode 100644 test/lookahead_iterator_test.js diff --git a/binding.gyp b/binding.gyp index 314b7b70..4fa2270d 100644 --- a/binding.gyp +++ b/binding.gyp @@ -8,6 +8,7 @@ "src/conversions.cc", "src/language.cc", "src/logger.cc", + "src/lookaheaditerator.cc", "src/node.cc", "src/parser.cc", "src/query.cc", diff --git a/index.js b/index.js index a4e2efda..83511997 100644 --- a/index.js +++ b/index.js @@ -10,13 +10,13 @@ try { } const util = require('util') -const {Query, Parser, NodeMethods, Tree, TreeCursor} = binding; +const {Query, Parser, NodeMethods, Tree, TreeCursor, LookaheadIterator} = binding; /* * Tree */ -const {rootNode, edit} = Tree.prototype; +const {rootNode, rootNodeWithOffset, edit} = Tree.prototype; Object.defineProperty(Tree.prototype, 'rootNode', { get() { @@ -36,9 +36,13 @@ Object.defineProperty(Tree.prototype, 'rootNode', { }, // Jest worker pool may attempt to override property due to race condition, // we don't want to error on this - configurable: true + configurable: true }); +Tree.prototype.rootNodeWithOffset = function(offset_bytes, offset_extent) { + return unmarshalNode(rootNodeWithOffset.call(this, offset_bytes, offset_extent.row, offset_extent.column), this); +} + Tree.prototype.edit = function(arg) { if (this instanceof Tree && edit) { edit.call( @@ -75,9 +79,9 @@ class SyntaxNode { '}' } - get type() { + get id() { marshalNode(this); - return NodeMethods.type(this.tree); + return NodeMethods.id(this.tree); } get typeId() { @@ -85,6 +89,21 @@ class SyntaxNode { return NodeMethods.typeId(this.tree); } + get grammarId() { + marshalNode(this); + return NodeMethods.grammarId(this.tree); + } + + get type() { + marshalNode(this); + return NodeMethods.type(this.tree); + } + + get grammarName() { + marshalNode(this); + return NodeMethods.grammarName(this.tree); + } + get isNamed() { marshalNode(this); return NodeMethods.isNamed(this.tree); @@ -181,6 +200,21 @@ class SyntaxNode { return unmarshalNode(NodeMethods.previousNamedSibling(this.tree), this.tree); } + get parseState() { + marshalNode(this); + return NodeMethods.parseState(this.tree); + } + + get nextParseState() { + marshalNode(this); + return NodeMethods.nextParseState(this.tree); + } + + get descendantCount() { + marshalNode(this); + return NodeMethods.descendantCount(this.tree); + } + hasChanges() { marshalNode(this); return NodeMethods.hasChanges(this.tree); @@ -196,6 +230,16 @@ class SyntaxNode { return NodeMethods.isMissing(this.tree); } + isExtra() { + marshalNode(this); + return NodeMethods.isExtra(this.tree); + } + + isError() { + marshalNode(this); + return NodeMethods.isError(this.tree); + } + toString() { marshalNode(this); return NodeMethods.toString(this.tree); @@ -211,6 +255,31 @@ class SyntaxNode { return unmarshalNode(NodeMethods.namedChild(this.tree, index), this.tree); } + childForFieldName(fieldName) { + marshalNode(this); + return unmarshalNode(NodeMethods.childForFieldName(this.tree, fieldName), this.tree); + } + + childForFieldId(fieldId) { + marshalNode(this); + return unmarshalNode(NodeMethods.childForFieldId(this.tree, fieldId), this.tree); + } + + fieldNameForChild(childIndex) { + marshalNode(this); + return NodeMethods.fieldNameForChild(this.tree, childIndex); + } + + childrenForFieldName(fieldName, cursor) { + marshalNode(this); + return unmarshalNodes(NodeMethods.childrenForFieldName(this.tree, fieldName, cursor), this.tree); + } + + childrenForFieldId(fieldId) { + marshalNode(this); + return unmarshalNodes(NodeMethods.childrenForFieldId(this.tree, fieldId), this.tree); + } + firstChildForIndex(index) { marshalNode(this); return unmarshalNode(NodeMethods.firstChildForIndex(this.tree, index), this.tree); @@ -302,7 +371,8 @@ Parser.prototype.parse = function(input, oldTree, {bufferSize, includedRanges}={ input, oldTree, bufferSize, - includedRanges) + includedRanges, + ) : undefined; if (tree) { @@ -556,11 +626,22 @@ Query.prototype._init = function() { this.refutedProperties = Object.freeze(refutedProperties); } -Query.prototype.matches = function(rootNode, startPosition = ZERO_POINT, endPosition = ZERO_POINT) { +Query.prototype.matches = function( + rootNode, + { + startPosition = ZERO_POINT, + endPosition = ZERO_POINT, + startIndex = 0, + endIndex = 0, + matchLimit = 0xFFFFFFFF, + maxStartDepth = 0xFFFFFFFF + } = {} +) { marshalNode(rootNode); const [returnedMatches, returnedNodes] = _matches.call(this, rootNode.tree, startPosition.row, startPosition.column, - endPosition.row, endPosition.column + endPosition.row, endPosition.column, + startIndex, endIndex, matchLimit, maxStartDepth ); const nodes = unmarshalNodes(returnedNodes, rootNode.tree); const results = []; @@ -798,3 +879,4 @@ module.exports.Query = Query; module.exports.Tree = Tree; module.exports.SyntaxNode = SyntaxNode; module.exports.TreeCursor = TreeCursor; +module.exports.LookaheadIterator = LookaheadIterator; diff --git a/package.json b/package.json index 0ad54220..1f045c59 100644 --- a/package.json +++ b/package.json @@ -21,16 +21,24 @@ "devDependencies": { "@types/node": "^20.11.16", "chai": "^4.3.10", - "jest": "^24.0.0", "mocha": "^8.4.0", "node-gyp": "^10.0.1", "prebuild": "^13.0.0", - "tree-sitter-javascript": "github:segevfiner/tree-sitter-javascript#napi-type-tag" + "tmp": "^0.2.1", + "tree-sitter-c": "github:amaanq/tree-sitter-c#napi", + "tree-sitter-embedded-template": "github:tree-sitter/tree-sitter-embedded-template#napi", + "tree-sitter-html": "github:amaanq/tree-sitter-html#napi", + "tree-sitter-java": "github:amaanq/tree-sitter-java#napi", + "tree-sitter-javascript": "github:amaanq/tree-sitter-javascript#napi", + "tree-sitter-json": "github:tree-sitter/tree-sitter-json#napi", + "tree-sitter-python": "github:amaanq/tree-sitter-python#napi", + "tree-sitter-rust": "github:amaanq/tree-sitter-rust#napi", + "tree-sitter-ruby": "github:tree-sitter/tree-sitter-ruby#napi" }, "scripts": { "install": "prebuild-install -r napi || node-gyp rebuild", "build": "node-gyp build", - "test": "mocha && jest" + "test": "mocha" }, "binary": { "napi_versions": [ diff --git a/src/addon_data.h b/src/addon_data.h index 7ff5ba24..36ecb29a 100644 --- a/src/addon_data.h +++ b/src/addon_data.h @@ -1,12 +1,13 @@ +#include "tree_sitter/api.h" + #include -#include #ifndef NODE_TREE_SITTER_ADDON_DATA_H_ #define NODE_TREE_SITTER_ADDON_DATA_H_ namespace node_tree_sitter { -class AddonData { +class AddonData final { public: explicit AddonData(Napi::Env _env) {} @@ -36,8 +37,11 @@ class AddonData { // tree Napi::FunctionReference tree_constructor; + + // lookaheaditerator + Napi::FunctionReference lookahead_iterator_constructor; }; } // namespace node_tree_sitter -#endif // NODE_TREE_SITTER_ADDON_DATA_H_ +#endif // NODE_TREE_SITTER_ADDON_DATA_H_ diff --git a/src/binding.cc b/src/binding.cc index 7d194d68..0ccee9b8 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -1,6 +1,7 @@ #include "./addon_data.h" #include "./conversions.h" #include "./language.h" +#include "./lookaheaditerator.h" #include "./node.h" #include "./parser.h" #include "./query.h" @@ -20,6 +21,7 @@ Napi::Object InitAll(Napi::Env env, Napi::Object exports) { InitConversions(env, exports); node_methods::Init(env, exports); language_methods::Init(env, exports); + LookaheadIterator::Init(env, exports); Parser::Init(env, exports); Query::Init(env, exports); Tree::Init(env, exports); @@ -30,4 +32,4 @@ Napi::Object InitAll(Napi::Env env, Napi::Object exports) { NODE_API_MODULE(tree_sitter_runtime_binding, InitAll) -} // namespace node_tree_sitter +} // namespace node_tree_sitter diff --git a/src/conversions.cc b/src/conversions.cc index accf085d..a6782f81 100644 --- a/src/conversions.cc +++ b/src/conversions.cc @@ -1,9 +1,9 @@ #include "./conversions.h" #include "./addon_data.h" +#include "tree_sitter/api.h" #include #include -#include using namespace Napi; @@ -137,4 +137,4 @@ Napi::Maybe ByteCountFromJS(const Napi::Value &arg) { return Napi::Just(result.Uint32Value() * BYTES_PER_CHARACTER); } -} // namespace node_tree_sitter +} // namespace node_tree_sitter diff --git a/src/conversions.h b/src/conversions.h index 2cd73c62..8cbe8b75 100644 --- a/src/conversions.h +++ b/src/conversions.h @@ -2,9 +2,9 @@ #define NODE_TREE_SITTER_CONVERSIONS_H_ #include "./addon_data.h" +#include "tree_sitter/api.h" #include -#include namespace node_tree_sitter { @@ -17,6 +17,6 @@ Napi::Maybe PointFromJS(const Napi::Value &); Napi::Maybe ByteCountFromJS(const Napi::Value &); Napi::Maybe RangeFromJS(const Napi::Value&); -} // namespace node_tree_sitter +} // namespace node_tree_sitter -#endif // NODE_TREE_SITTER_CONVERSIONS_H_ +#endif // NODE_TREE_SITTER_CONVERSIONS_H_ diff --git a/src/language.cc b/src/language.cc index 2c6c7453..3dc4d807 100644 --- a/src/language.cc +++ b/src/language.cc @@ -1,8 +1,8 @@ #include "./language.h" +#include "tree_sitter/api.h" #include #include -#include using namespace Napi; @@ -56,8 +56,8 @@ Napi::Value GetNodeTypeNamesById(const Napi::CallbackInfo &info) { const TSLanguage *language = UnwrapLanguage(info[0]); if (language == nullptr) { - return env.Undefined(); -} + return env.Undefined(); + } auto result = Array::New(env); uint32_t length = ts_language_symbol_count(language); @@ -79,8 +79,8 @@ Napi::Value GetNodeFieldNamesById(const Napi::CallbackInfo &info) { const TSLanguage *language = UnwrapLanguage(info[0]); if (language == nullptr) { - return env.Undefined(); -} + return env.Undefined(); + } auto result = Array::New(env); uint32_t length = ts_language_field_count(language); diff --git a/src/language.h b/src/language.h index ca524c6c..9aacb813 100644 --- a/src/language.h +++ b/src/language.h @@ -1,10 +1,10 @@ #ifndef NODE_TREE_SITTER_LANGUAGE_H_ #define NODE_TREE_SITTER_LANGUAGE_H_ +#include "tree_sitter/api.h" + #include #include -#include - namespace node_tree_sitter::language_methods { @@ -14,5 +14,4 @@ const TSLanguage *UnwrapLanguage(Napi::Value); } // namespace node_tree_sitter::language_methods - -#endif // NODE_TREE_SITTER_LANGUAGE_H_ +#endif // NODE_TREE_SITTER_LANGUAGE_H_ diff --git a/src/logger.cc b/src/logger.cc index 21cdbceb..b765b5ce 100644 --- a/src/logger.cc +++ b/src/logger.cc @@ -1,8 +1,8 @@ #include "./logger.h" +#include "tree_sitter/api.h" #include #include -#include using namespace Napi; using std::string; @@ -67,4 +67,4 @@ TSLogger Logger::Make(const Napi::Function &func) { return result; } -} // namespace node_tree_sitter +} // namespace node_tree_sitter diff --git a/src/logger.h b/src/logger.h index a84082cf..13bfbf06 100644 --- a/src/logger.h +++ b/src/logger.h @@ -1,12 +1,13 @@ #ifndef NODE_TREE_SITTER_LOGGER_H_ #define NODE_TREE_SITTER_LOGGER_H_ +#include "tree_sitter/api.h" + #include -#include namespace node_tree_sitter { -class Logger { +class Logger final { public: static TSLogger Make(const Napi::Function &); Napi::FunctionReference func; @@ -15,4 +16,4 @@ class Logger { } // namespace node_tree_sitter -#endif // NODE_TREE_SITTER_LOGGER_H_ +#endif // NODE_TREE_SITTER_LOGGER_H_ diff --git a/src/lookaheaditerator.cc b/src/lookaheaditerator.cc new file mode 100644 index 00000000..902c0273 --- /dev/null +++ b/src/lookaheaditerator.cc @@ -0,0 +1,135 @@ +#include "./conversions.h" +#include "./language.h" +#include "./lookaheaditerator.h" +#include "tree_sitter/api.h" + +#include + +using namespace Napi; + +namespace node_tree_sitter { + +void LookaheadIterator::Init(Napi::Env env, Napi::Object exports) { + auto *data = env.GetInstanceData(); + + Function ctor = DefineClass(env, "LookaheadIterator", { + InstanceAccessor("currentSymbol", &LookaheadIterator::CurrentSymbol, nullptr, napi_default_method), + InstanceAccessor("currentSymbolName", &LookaheadIterator::CurrentSymbolName, nullptr, napi_default_method), + + InstanceMethod("reset", &LookaheadIterator::Reset, napi_default_method), + InstanceMethod("resetState", &LookaheadIterator::ResetState, napi_default_method), + InstanceMethod("next", &LookaheadIterator::Next, napi_default_method), + InstanceMethod("iterNames", &LookaheadIterator::IterNames, napi_default_method), + }); + + exports["LookaheadIterator"] = ctor; + data->lookahead_iterator_constructor = Napi::Persistent(ctor); +} + +LookaheadIterator::LookaheadIterator(const Napi::CallbackInfo &info) : Napi::ObjectWrap(info), iterator_(nullptr) { + Napi::Env env = info.Env(); + const TSLanguage *language = language_methods::UnwrapLanguage(info[0]); + + if (language == nullptr) { + Napi::Error::New(env, "Missing language argument").ThrowAsJavaScriptException(); + return; + } + if (!info[1].IsNumber()) { + Napi::Error::New(env, "Missing state argument").ThrowAsJavaScriptException(); + return; + } + + TSStateId state = info[1].As().Uint32Value(); + iterator_ = ts_lookahead_iterator_new(language, state); + if (iterator_ == nullptr) { + Napi::Error::New(env, "Invalid state argument").ThrowAsJavaScriptException(); + return; + } +} + + +LookaheadIterator::~LookaheadIterator() { + ts_lookahead_iterator_delete(iterator_); +} + +Napi::Value LookaheadIterator::NewInstance(Napi::Env env, TSLookaheadIterator *iterator) { + auto *data = env.GetInstanceData(); + if (iterator != nullptr) { + Object self = data->lookahead_iterator_constructor.New({}); + LookaheadIterator::Unwrap(self)->iterator_ = iterator; + return self; + } + + return env.Null(); +} + +LookaheadIterator *LookaheadIterator::UnwrapLookaheadIterator(const Napi::Value &value) { + auto *data = value.Env().GetInstanceData(); + if (!value.IsObject()) { + return nullptr; + } + auto js_iterator = value.As(); + if (!js_iterator.InstanceOf(data->lookahead_iterator_constructor.Value())) { + return nullptr; + } + return LookaheadIterator::Unwrap(js_iterator); +} + +Napi::Value LookaheadIterator::CurrentSymbolName(const Napi::CallbackInfo &info) { + LookaheadIterator *iterator = UnwrapLookaheadIterator(info.This()); + return Napi::String::New(info.Env(), ts_lookahead_iterator_current_symbol_name(iterator->iterator_)); +} + +Napi::Value LookaheadIterator::CurrentSymbol(const Napi::CallbackInfo &info) { + LookaheadIterator *iterator = UnwrapLookaheadIterator(info.This()); + return Napi::Number::New(info.Env(), ts_lookahead_iterator_current_symbol(iterator->iterator_)); +} + +Napi::Value LookaheadIterator::Reset(const Napi::CallbackInfo &info) { + LookaheadIterator *iterator = UnwrapLookaheadIterator(info.This()); + const TSLanguage *language = language_methods::UnwrapLanguage(info[0]); + + if (language == nullptr) { + Napi::Error::New(info.Env(), "Invalid language argument").ThrowAsJavaScriptException(); + return info.Env().Undefined(); + } + if (!info[1].IsNumber()) { + Napi::Error::New(info.Env(), "Missing state argument").ThrowAsJavaScriptException(); + return info.Env().Undefined(); + } + + TSStateId state = info[1].As().Uint32Value(); + return Napi::Boolean::New(info.Env(), ts_lookahead_iterator_reset(iterator->iterator_, language, state)); +} + +Napi::Value LookaheadIterator::ResetState(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + LookaheadIterator *iterator = UnwrapLookaheadIterator(info.This()); + + if (!info[0].IsNumber()) { + Napi::Error::New(env, "Missing state argument").ThrowAsJavaScriptException(); + return env.Undefined(); + } + + TSStateId state = info[0].As().Uint32Value(); + return Napi::Boolean::New(env, ts_lookahead_iterator_reset_state(iterator->iterator_, state)); +} + +Napi::Value LookaheadIterator::Next(const Napi::CallbackInfo &info) { + LookaheadIterator *iterator = UnwrapLookaheadIterator(info.This()); + return Napi::Boolean::New(info.Env(), ts_lookahead_iterator_next(iterator->iterator_)); +} + +Napi::Value LookaheadIterator::IterNames(const Napi::CallbackInfo &info) { + LookaheadIterator *iterator = UnwrapLookaheadIterator(info.This()); + auto result = Napi::Array::New(info.Env()); + + while (ts_lookahead_iterator_next(iterator->iterator_)) { + const char *name = ts_lookahead_iterator_current_symbol_name(iterator->iterator_); + result.Set(result.Length(), Napi::String::New(info.Env(), name)); + } + + return result; +} + +} // namespace node_tree_sitter diff --git a/src/lookaheaditerator.h b/src/lookaheaditerator.h new file mode 100644 index 00000000..b996a9aa --- /dev/null +++ b/src/lookaheaditerator.h @@ -0,0 +1,34 @@ +#ifndef NODE_TREE_SITTER_LOOKAHEAD_ITERATOR_H_ +#define NODE_TREE_SITTER_LOOKAHEAD_ITERATOR_H_ + +#include "tree_sitter/api.h" + +#include + +namespace node_tree_sitter { + +class LookaheadIterator final : public Napi::ObjectWrap { + public: + static void Init(Napi::Env env, Napi::Object exports); + static Napi::Value NewInstance(Napi::Env env, TSLookaheadIterator *); + static LookaheadIterator *UnwrapLookaheadIterator(const Napi::Value &); + + explicit LookaheadIterator(const Napi::CallbackInfo &); + ~LookaheadIterator() final; + + private: + TSLookaheadIterator *iterator_; + + Napi::Value New(const Napi::CallbackInfo &); + Napi::Value Reset(const Napi::CallbackInfo &); + Napi::Value ResetState(const Napi::CallbackInfo &); + Napi::Value Next(const Napi::CallbackInfo &); + Napi::Value IterNames(const Napi::CallbackInfo &); + + Napi::Value CurrentSymbol(const Napi::CallbackInfo &); + Napi::Value CurrentSymbolName(const Napi::CallbackInfo &); +}; + +} // namespace node_tree_sitter + +#endif // NODE_TREE_SITTER_LOOKAHEAD_ITERATOR_H_ diff --git a/src/node.cc b/src/node.cc index a447c435..2d20116c 100644 --- a/src/node.cc +++ b/src/node.cc @@ -2,16 +2,14 @@ #include "./conversions.h" #include "./tree.h" #include "./tree_cursor.h" -#include "./util.h" +#include "tree_sitter/api.h" #include -#include #include using std::vector; using namespace Napi; - namespace node_tree_sitter::node_methods { const uint32_t FIELD_COUNT_PER_NODE = 6; @@ -122,7 +120,7 @@ TSNode UnmarshalNode(Napi::Env env, const Tree *tree) { struct SymbolSet { void add(TSSymbol symbol) { symbols += symbol; } - bool contains(TSSymbol symbol) const { return symbols.find(symbol) != symbols.npos; } + [[nodiscard]] bool contains(TSSymbol symbol) const { return symbols.find(symbol) != symbols.npos; } private: std::basic_string symbols; }; @@ -182,42 +180,6 @@ Napi::Value ToString(const Napi::CallbackInfo &info) { return env.Undefined(); } -Napi::Value IsMissing(const Napi::CallbackInfo &info) { - Env env = info.Env(); - const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(env, tree); - if (node.id != nullptr) { - bool result = ts_node_is_missing(node); - return Boolean::New(env, result); - } - - return env.Undefined(); -} - -Napi::Value HasChanges(const Napi::CallbackInfo &info) { - Env env = info.Env(); - const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(env, tree); - if (node.id != nullptr) { - bool result = ts_node_has_changes(node); - return Boolean::New(env, result); - } - - return env.Undefined(); -} - -Napi::Value HasError(const Napi::CallbackInfo &info) { - Env env = info.Env(); - const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(env, tree); - if (node.id != nullptr) { - bool result = ts_node_has_error(node); - return Boolean::New(env, result); - } - - return env.Undefined(); -} - Napi::Value FirstNamedChildForIndex(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); @@ -313,13 +275,13 @@ Napi::Value DescendantForPosition(const Napi::CallbackInfo &info) { return MarshalNullNode(env); } -Napi::Value Type(const Napi::CallbackInfo &info) { +Napi::Value Id(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); if (node.id != nullptr) { - return String::New(env, ts_node_type(node)); + return Number::New(env, static_cast(reinterpret_cast(node.id))); } return env.Undefined(); @@ -337,6 +299,42 @@ Napi::Value TypeId(const Napi::CallbackInfo &info) { return env.Undefined(); } +Napi::Value GrammarId(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); + + if (node.id != nullptr) { + return Number::New(env, ts_node_grammar_symbol(node)); + } + + return env.Undefined(); +} + +Napi::Value Type(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); + + if (node.id != nullptr) { + return String::New(env, ts_node_type(node)); + } + + return env.Undefined(); +} + +Napi::Value GrammarName(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); + + if (node.id != nullptr) { + return String::New(env, ts_node_grammar_type(node)); + } + + return env.Undefined(); +} + Napi::Value IsNamed(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); @@ -349,6 +347,86 @@ Napi::Value IsNamed(const Napi::CallbackInfo &info) { return env.Undefined(); } +Napi::Value IsExtra(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); + + if (node.id != nullptr) { + return Boolean::New(env, ts_node_is_extra(node)); + } + + return env.Undefined(); +} + +Napi::Value HasChanges(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); + if (node.id != nullptr) { + bool result = ts_node_has_changes(node); + return Boolean::New(env, result); + } + + return env.Undefined(); +} + +Napi::Value HasError(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); + if (node.id != nullptr) { + bool result = ts_node_has_error(node); + return Boolean::New(env, result); + } + + return env.Undefined(); +} + +Napi::Value IsError(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); + if (node.id != nullptr) { + bool result = ts_node_is_error(node); + return Boolean::New(env, result); + } + + return env.Undefined(); +} + +Napi::Value ParseState(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); + if (node.id != nullptr) { + return Number::New(env, ts_node_parse_state(node)); + } + return env.Undefined(); +} + +Napi::Value NextParseState(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); + if (node.id != nullptr) { + return Number::New(env, ts_node_next_parse_state(node)); + } + return env.Undefined(); +} + +Napi::Value IsMissing(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); + if (node.id != nullptr) { + bool result = ts_node_is_missing(node); + return Boolean::New(env, result); + } + + return env.Undefined(); +} + Napi::Value StartIndex(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); @@ -414,6 +492,18 @@ Napi::Value Child(const Napi::CallbackInfo &info) { return MarshalNullNode(env); } +Napi::Value ChildCount(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); + + if (node.id != nullptr) { + return Number::New(env, ts_node_child_count(node)); + } + + return env.Undefined(); +} + Napi::Value NamedChild(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); @@ -429,30 +519,202 @@ Napi::Value NamedChild(const Napi::CallbackInfo &info) { return MarshalNullNode(env); } -Napi::Value ChildCount(const Napi::CallbackInfo &info) { +Napi::Value NamedChildCount(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); if (node.id != nullptr) { - return Number::New(env, ts_node_child_count(node)); + return Number::New(env, ts_node_named_child_count(node)); } return env.Undefined(); } -Napi::Value NamedChildCount(const Napi::CallbackInfo &info) { +Napi::Value ChildForFieldName(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); if (node.id != nullptr) { - return Number::New(env, ts_node_named_child_count(node)); + if (!info[1].IsString()) { + throw TypeError::New(env, "Second argument must be a string"); + } + std::string field_name = info[1].As().Utf8Value(); + return MarshalNode(info, tree, ts_node_child_by_field_name(node, field_name.c_str(), field_name.length())); + } + + return MarshalNullNode(env); +} + +Napi::Value ChildForFieldId(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); + if (node.id != nullptr) { + if (!info[1].IsNumber()) { + throw TypeError::New(env, "Second argument must be an integer"); + } + uint32_t field_id = info[1].As().Uint32Value(); + return MarshalNode(info, tree, ts_node_child_by_field_id(node, field_id)); } + return MarshalNullNode(env); +} +Napi::Value FieldNameForChild(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); + if (node.id != nullptr) { + if (!info[1].IsNumber()) { + throw TypeError::New(env, "Second argument must be an integer"); + } + uint32_t child_id = info[1].As().Uint32Value(); + const char *field_name = ts_node_field_name_for_child(node, child_id); + if (field_name != nullptr) { + return String::New(env, field_name); + } + } return env.Undefined(); } +Napi::Value Children(const Napi::CallbackInfo &info) { + Env env = info.Env(); + auto* data = env.GetInstanceData(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); + if (node.id == nullptr) { + return env.Undefined(); + } + + vector result; + ts_tree_cursor_reset(&data->scratch_cursor, node); + if (ts_tree_cursor_goto_first_child(&data->scratch_cursor)) { + do { + TSNode child = ts_tree_cursor_current_node(&data->scratch_cursor); + result.push_back(child); + } while (ts_tree_cursor_goto_next_sibling(&data->scratch_cursor)); + } + + return MarshalNodes(info, tree, result.data(), result.size()); +} + +Napi::Value NamedChildren(const Napi::CallbackInfo &info) { + Env env = info.Env(); + auto* data = env.GetInstanceData(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); + if (node.id == nullptr) { + return env.Undefined(); + } + + vector result; + ts_tree_cursor_reset(&data->scratch_cursor, node); + if (ts_tree_cursor_goto_first_child(&data->scratch_cursor)) { + do { + TSNode child = ts_tree_cursor_current_node(&data->scratch_cursor); + if (ts_node_is_named(child)) { + result.push_back(child); + } + } while (ts_tree_cursor_goto_next_sibling(&data->scratch_cursor)); + } + + return MarshalNodes(info, tree, result.data(), result.size()); +} + +Napi::Value ChildrenForFieldName(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); + if (node.id == nullptr) { + return env.Undefined(); + } + + if (!info[1].IsString()) { + throw TypeError::New(env, "First argument must be a string"); + } + std::string field_name = info[1].As().Utf8Value(); + + if (!info[2].IsObject()) { + throw TypeError::New(env, "Second argument must be a cursor"); + } + TSTreeCursor *cursor = &TreeCursor::Unwrap(info[2].As())->cursor_; + + const TSLanguage *language = ts_tree_language(node.tree); + TSFieldId field_id = ts_language_field_id_for_name(language, field_name.c_str(), field_name.length()); + + bool done = field_id == 0; + if (!done) { + ts_tree_cursor_reset(cursor, node); + ts_tree_cursor_goto_first_child(cursor); + } + + vector result; + while (!done) { + while (ts_tree_cursor_current_field_id(cursor) != field_id) { + if (!ts_tree_cursor_goto_next_sibling(cursor)) { + done = true; + break; + } + } + if (done) { + break; + } + TSNode result_node = ts_tree_cursor_current_node(cursor); + if (!ts_tree_cursor_goto_next_sibling(cursor)) { + done = true; + } + result.push_back(result_node); + } + + return MarshalNodes(info, tree, result.data(), result.size()); +} + +Napi::Value ChildrenForFieldId(const Napi::CallbackInfo &info) { + Env env = info.Env(); + const Tree *tree = Tree::UnwrapTree(info[0]); + TSNode node = UnmarshalNode(env, tree); + if (node.id == nullptr) { + return env.Undefined(); + } + + if (!info[1].IsNumber()) { + throw TypeError::New(env, "First argument must be an integer"); + } + uint32_t field_id = info[1].As().Uint32Value(); + + if (!info[2].IsObject()) { + throw TypeError::New(env, "Second argument must be a cursor"); + } + TSTreeCursor *cursor = &TreeCursor::Unwrap(info[2].As())->cursor_; + + bool done = field_id == 0; + if (!done) { + ts_tree_cursor_reset(cursor, node); + ts_tree_cursor_goto_first_child(cursor); + } + + vector result; + while (!done) { + while (ts_tree_cursor_current_field_id(cursor) != field_id) { + if (!ts_tree_cursor_goto_next_sibling(cursor)) { + done = true; + break; + } + } + if (done) { + break; + } + TSNode result_node = ts_tree_cursor_current_node(cursor); + if (!ts_tree_cursor_goto_next_sibling(cursor)) { + done = true; + } + result.push_back(result_node); + } + + return MarshalNodes(info, tree, result.data(), result.size()); +} + Napi::Value FirstChild(const Napi::CallbackInfo &info) { Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); @@ -549,48 +811,14 @@ Napi::Value PreviousNamedSibling(const Napi::CallbackInfo &info) { return MarshalNullNode(env); } -Napi::Value Children(const Napi::CallbackInfo &info) { - Env env = info.Env(); - auto* data = env.GetInstanceData(); - const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(env, tree); - if (node.id == nullptr) { - return env.Undefined(); - } - - vector result; - ts_tree_cursor_reset(&data->scratch_cursor, node); - if (ts_tree_cursor_goto_first_child(&data->scratch_cursor)) { - do { - TSNode child = ts_tree_cursor_current_node(&data->scratch_cursor); - result.push_back(child); - } while (ts_tree_cursor_goto_next_sibling(&data->scratch_cursor)); - } - - return MarshalNodes(info, tree, result.data(), result.size()); -} - -Napi::Value NamedChildren(const Napi::CallbackInfo &info) { +Napi::Value DescendantCount(const Napi::CallbackInfo &info) { Env env = info.Env(); - auto* data = env.GetInstanceData(); const Tree *tree = Tree::UnwrapTree(info[0]); TSNode node = UnmarshalNode(env, tree); - if (node.id == nullptr) { - return env.Undefined(); - } - - vector result; - ts_tree_cursor_reset(&data->scratch_cursor, node); - if (ts_tree_cursor_goto_first_child(&data->scratch_cursor)) { - do { - TSNode child = ts_tree_cursor_current_node(&data->scratch_cursor); - if (ts_node_is_named(child)) { - result.push_back(child); - } - } while (ts_tree_cursor_goto_next_sibling(&data->scratch_cursor)); + if (node.id != nullptr) { + return Number::New(env, ts_node_descendant_count(node)); } - - return MarshalNodes(info, tree, result.data(), result.size()); + return env.Undefined(); } Napi::Value DescendantsOfType(const Napi::CallbackInfo &info) { @@ -760,41 +988,59 @@ void Init(Napi::Env env, Napi::Object exports) { Object result = Object::New(env); + struct FunctionPair { + const char *name; + Napi::Function::Callback callback; + }; + FunctionPair methods[] = { - {"startIndex", StartIndex}, - {"endIndex", EndIndex}, - {"type", Type}, + {"id", Id}, {"typeId", TypeId}, + {"grammarId", GrammarId}, + {"type", Type}, + {"grammarName", GrammarName}, {"isNamed", IsNamed}, - {"parent", Parent}, + {"isExtra", IsExtra}, + {"hasChanges", HasChanges}, + {"hasError", HasError}, + {"isError", IsError}, + {"parseState", ParseState}, + {"nextParseState", NextParseState}, + {"isMissing", IsMissing}, + {"startIndex", StartIndex}, + {"endIndex", EndIndex}, + {"startPosition", StartPosition}, + {"endPosition", EndPosition}, {"child", Child}, + {"childCount", ChildCount}, {"namedChild", NamedChild}, + {"namedChildCount", NamedChildCount}, + {"childForFieldName", ChildForFieldName}, + {"childForFieldId", ChildForFieldId}, + {"fieldNameForChild", FieldNameForChild}, {"children", Children}, {"namedChildren", NamedChildren}, - {"childCount", ChildCount}, - {"namedChildCount", NamedChildCount}, - {"firstChild", FirstChild}, - {"lastChild", LastChild}, - {"firstNamedChild", FirstNamedChild}, - {"lastNamedChild", LastNamedChild}, + {"childrenForFieldName", ChildrenForFieldName}, + {"childrenForFieldId", ChildrenForFieldId}, + {"parent", Parent}, {"nextSibling", NextSibling}, - {"nextNamedSibling", NextNamedSibling}, {"previousSibling", PreviousSibling}, + {"nextNamedSibling", NextNamedSibling}, {"previousNamedSibling", PreviousNamedSibling}, - {"startPosition", StartPosition}, - {"endPosition", EndPosition}, - {"isMissing", IsMissing}, - {"toString", ToString}, - {"firstChildForIndex", FirstChildForIndex}, - {"firstNamedChildForIndex", FirstNamedChildForIndex}, + {"descendantCount", DescendantCount}, {"descendantForIndex", DescendantForIndex}, {"namedDescendantForIndex", NamedDescendantForIndex}, {"descendantForPosition", DescendantForPosition}, {"namedDescendantForPosition", NamedDescendantForPosition}, - {"hasChanges", HasChanges}, - {"hasError", HasError}, - {"descendantsOfType", DescendantsOfType}, + {"toString", ToString}, {"walk", Walk}, + {"firstChild", FirstChild}, + {"lastChild", LastChild}, + {"firstNamedChild", FirstNamedChild}, + {"lastNamedChild", LastNamedChild}, + {"firstChildForIndex", FirstChildForIndex}, + {"firstNamedChildForIndex", FirstNamedChildForIndex}, + {"descendantsOfType", DescendantsOfType}, {"closest", Closest}, {"childNodeForFieldId", ChildNodeForFieldId}, {"childNodesForFieldId", ChildNodesForFieldId}, @@ -811,4 +1057,3 @@ void Init(Napi::Env env, Napi::Object exports) { } } // namespace node_tree_sitter::node_methods - diff --git a/src/node.h b/src/node.h index 451de14f..baef9f47 100644 --- a/src/node.h +++ b/src/node.h @@ -2,13 +2,13 @@ #define NODE_TREE_SITTER_NODE_H_ #include "./tree.h" +#include "tree_sitter/api.h" #include -#include namespace node_tree_sitter::node_methods { -void Init(Napi::Env, Napi::Object); +void Init(Napi::Env env, Napi::Object exports); Napi::Value MarshalNode(const Napi::CallbackInfo &info, const Tree *, TSNode); Napi::Value GetMarshalNode(const Napi::CallbackInfo &info, const Tree *tree, TSNode node); Napi::Value GetMarshalNodes(const Napi::CallbackInfo &info, const Tree *tree, const TSNode *nodes, uint32_t node_count); @@ -27,5 +27,4 @@ static inline void MarshalNodeId(const void *id, uint32_t *buffer) { } // namespace node_tree_sitter::node_methods - -#endif // NODE_TREE_SITTER_NODE_H_ +#endif // NODE_TREE_SITTER_NODE_H_ diff --git a/src/parser.cc b/src/parser.cc index b712d2ba..8a6a6850 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -4,6 +4,7 @@ #include "./logger.h" #include "./tree.h" +#include #include #include @@ -12,14 +13,14 @@ using std::vector; namespace node_tree_sitter { -class CallbackInput { +class CallbackInput final { public: CallbackInput(Function callback, Napi::Value js_buffer_size) { this->callback.Reset(callback, 1); if (js_buffer_size.IsNumber()) { buffer.resize(js_buffer_size.As().Uint32Value()); } else { - buffer.resize(32 * 1024); + buffer.resize(static_cast(32 * 1024)); } } @@ -104,11 +105,15 @@ void Parser::Init(Napi::Env env, Napi::Object exports) { auto *data = env.GetInstanceData(); Function ctor = DefineClass(env, "Parser", { + InstanceMethod("setLanguage", &Parser::SetLanguage, napi_default_method), + InstanceMethod("parse", &Parser::Parse, napi_default_method), + InstanceMethod("getIncludedRanges", &Parser::IncludedRanges, napi_default_method), + InstanceMethod("getTimeoutMicros", &Parser::TimeoutMicros, napi_default_method), + InstanceMethod("setTimeoutMicros", &Parser::SetTimeoutMicros, napi_default_method), InstanceMethod("getLogger", &Parser::GetLogger, napi_default_method), InstanceMethod("setLogger", &Parser::SetLogger, napi_default_method), - InstanceMethod("setLanguage", &Parser::SetLanguage, napi_default_method), InstanceMethod("printDotGraphs", &Parser::PrintDotGraphs, napi_default_method), - InstanceMethod("parse", &Parser::Parse, napi_default_method), + InstanceMethod("reset", &Parser::Reset, napi_default_method), }); data->parser_constructor = Napi::Persistent(ctor); @@ -122,7 +127,11 @@ void Parser::Init(Napi::Env env, Napi::Object exports) { Parser::Parser(const Napi::CallbackInfo &info) : Napi::ObjectWrap(info), parser_(ts_parser_new()) {} -Parser::~Parser() { ts_parser_delete(parser_); } +Parser::~Parser() { + ts_parser_print_dot_graphs(parser_, -1); + ts_parser_set_logger(parser_, { nullptr, nullptr }); + ts_parser_delete(parser_); +} namespace { @@ -201,6 +210,34 @@ Napi::Value Parser::Parse(const CallbackInfo &info) { return result; } +Napi::Value Parser::IncludedRanges(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + uint32_t count; + const TSRange *ranges = ts_parser_included_ranges(parser_, &count); + + Napi::Array result = Napi::Array::New(env, count); + for (uint32_t i = 0; i < count; i++) { + result[i] = RangeToJS(env, ranges[i]); + } + + return result; +} + +Napi::Value Parser::TimeoutMicros(const Napi::CallbackInfo &info) { + uint64_t timeout_micros = ts_parser_timeout_micros(parser_); + return Number::New(info.Env(), static_cast(timeout_micros)); +} + +Napi::Value Parser::SetTimeoutMicros(const Napi::CallbackInfo &info) { + uint64_t timeout_micros; + if (!info[0].IsNumber()) { + throw TypeError::New(info.Env(), "First argument must be a number"); + } + timeout_micros = info[0].As().Uint32Value(); + ts_parser_set_timeout_micros(parser_, timeout_micros); + return info.This(); +} + Napi::Value Parser::GetLogger(const Napi::CallbackInfo &info) { TSLogger current_logger = ts_parser_logger(parser_); if ((current_logger.payload != nullptr) && current_logger.log == Logger::Log) { @@ -208,7 +245,6 @@ Napi::Value Parser::GetLogger(const Napi::CallbackInfo &info) { return logger->func.Value(); } return info.Env().Null(); - } Napi::Value Parser::SetLogger(const Napi::CallbackInfo &info) { @@ -232,13 +268,31 @@ Napi::Value Parser::SetLogger(const Napi::CallbackInfo &info) { } Napi::Value Parser::PrintDotGraphs(const Napi::CallbackInfo &info) { - if (info[0].IsBoolean() && info[0].As()) { - ts_parser_print_dot_graphs(parser_, 2); - } else { - ts_parser_print_dot_graphs(parser_, -1); + bool should_print = true; + int32_t fd = fileno(stderr); + + if (info.Length() > 0) { + if (!info[0].IsBoolean()) { + throw TypeError::New(info.Env(), "First argument must be a boolean"); + } + should_print = info[0].As(); } + if (info.Length() > 1) { + if (!info[1].IsNumber()) { + throw TypeError::New(info.Env(), "Second argument must be a number"); + } + fd = info[1].As().Int32Value(); + } + + ts_parser_print_dot_graphs(parser_, should_print ? fd : -1); + + return info.This(); +} + +Napi::Value Parser::Reset(const Napi::CallbackInfo & info) { + ts_parser_reset(parser_); return info.This(); } -} // namespace node_tree_sitter +} // namespace node_tree_sitter diff --git a/src/parser.h b/src/parser.h index 69f4076f..4b480ba1 100644 --- a/src/parser.h +++ b/src/parser.h @@ -1,28 +1,35 @@ #ifndef NODE_TREE_SITTER_PARSER_H_ #define NODE_TREE_SITTER_PARSER_H_ +#include "tree_sitter/api.h" + #include #include -#include namespace node_tree_sitter { -class Parser : public Napi::ObjectWrap { +class Parser final : public Napi::ObjectWrap { public: static void Init(Napi::Env env, Napi::Object exports); + explicit Parser(const Napi::CallbackInfo &info); - ~Parser() override; + ~Parser() final; private: TSParser *parser_; Napi::Value SetLanguage(const Napi::CallbackInfo &); + Napi::Value Parse(const Napi::CallbackInfo &); + Napi::Value IncludedRanges(const Napi::CallbackInfo &info); + Napi::Value SetIncludedRanges(const Napi::CallbackInfo &info); + Napi::Value TimeoutMicros(const Napi::CallbackInfo &info); + Napi::Value SetTimeoutMicros(const Napi::CallbackInfo &info); Napi::Value GetLogger(const Napi::CallbackInfo &); Napi::Value SetLogger(const Napi::CallbackInfo &); - Napi::Value Parse(const Napi::CallbackInfo &); Napi::Value PrintDotGraphs(const Napi::CallbackInfo &); + Napi::Value Reset(const Napi::CallbackInfo &info); }; -} // namespace node_tree_sitter +} // namespace node_tree_sitter -#endif // NODE_TREE_SITTER_PARSER_H_ +#endif // NODE_TREE_SITTER_PARSER_H_ diff --git a/src/query.cc b/src/query.cc index bbc1f574..d67a5a77 100644 --- a/src/query.cc +++ b/src/query.cc @@ -36,9 +36,18 @@ void Query::Init(Napi::Env env, Napi::Object exports) { data->ts_query_cursor = ts_query_cursor_new(); Function ctor = DefineClass(env, "Query", { + InstanceAccessor("matchLimit", &Query::MatchLimit, nullptr, napi_default_method), + InstanceMethod("_matches", &Query::Matches, napi_default_method), InstanceMethod("_captures", &Query::Captures, napi_default_method), InstanceMethod("_getPredicates", &Query::GetPredicates, napi_default_method), + InstanceMethod("disableCapture", &Query::DisableCapture, napi_default_method), + InstanceMethod("disablePattern", &Query::DisablePattern, napi_default_method), + InstanceMethod("isPatternGuaranteedAtStep", &Query::IsPatternGuaranteedAtStep, napi_default_method), + InstanceMethod("isPatternRooted", &Query::IsPatternRooted, napi_default_method), + InstanceMethod("isPatternNonLocal", &Query::IsPatternNonLocal, napi_default_method), + InstanceMethod("startIndexForPattern", &Query::StartIndexForPattern, napi_default_method), + InstanceMethod("didExceedMatchLimit", &Query::DidExceedMatchLimit, napi_default_method), }); data->query_constructor = Napi::Persistent(ctor); @@ -160,22 +169,34 @@ Napi::Value Query::Matches(const Napi::CallbackInfo &info) { auto *data = env.GetInstanceData(); Query *query = Query::UnwrapQuery(info.This()); const Tree *tree = Tree::UnwrapTree(info[0]); - uint32_t start_row = 0; + + uint32_t start_row = 0, start_column = 0, end_row = 0, end_column = 0, start_index = 0, end_index = 0, + match_limit = UINT32_MAX, max_start_depth = UINT32_MAX; + if (info.Length() > 1 && info[1].IsNumber()) { start_row = info[1].As().Uint32Value(); } - uint32_t start_column = 0; if (info.Length() > 2 && info[2].IsNumber()) { start_column = info[2].As().Uint32Value() << 1; } - uint32_t end_row = 0; if (info.Length() > 3 && info[3].IsNumber()) { end_row = info[3].As().Uint32Value(); } - uint32_t end_column = 0; if (info.Length() > 4 && info[4].IsNumber()) { end_column = info[4].As().Uint32Value() << 1; } + if (info.Length() > 5 && info[5].IsNumber()) { + start_index = info[5].As().Uint32Value(); + } + if (info.Length() > 6 && info[6].IsNumber()) { + end_index = info[6].As().Uint32Value() << 1; + } + if (info.Length() > 7 && info[7].IsNumber()) { + match_limit = info[7].As().Uint32Value(); + } + if (info.Length() > 8 && info[8].IsNumber()) { + max_start_depth = info[8].As().Uint32Value(); + } if (query == nullptr) { throw Error::New(env, "Missing argument query"); @@ -190,6 +211,9 @@ Napi::Value Query::Matches(const Napi::CallbackInfo &info) { TSPoint start_point = {start_row, start_column}; TSPoint end_point = {end_row, end_column}; ts_query_cursor_set_point_range(data->ts_query_cursor, start_point, end_point); + ts_query_cursor_set_byte_range(data->ts_query_cursor, start_index, end_index); + ts_query_cursor_set_match_limit(data->ts_query_cursor, match_limit); + ts_query_cursor_set_max_start_depth(data->ts_query_cursor, max_start_depth); ts_query_cursor_exec(data->ts_query_cursor, ts_query, rootNode); Array js_matches = Array::New(env); @@ -298,4 +322,47 @@ Napi::Value Query::Captures(const Napi::CallbackInfo &info) { return result; } -} // namespace node_tree_sitter +Napi::Value Query::DisableCapture(const Napi::CallbackInfo &info) { + std::string string = info[0].As().Utf8Value(); + const char *capture_name = string.c_str(); + ts_query_disable_capture(query_, capture_name, string.length()); + return info.Env().Undefined(); +} + +Napi::Value Query::DisablePattern(const Napi::CallbackInfo &info) { + uint32_t pattern_index = info[0].As().Uint32Value(); + ts_query_disable_pattern(query_, pattern_index); + return info.Env().Undefined(); +} + +Napi::Value Query::IsPatternGuaranteedAtStep(const Napi::CallbackInfo &info) { + uint32_t byte_offset = info[0].As().Uint32Value(); + return Boolean::New(info.Env(), ts_query_is_pattern_guaranteed_at_step(query_, byte_offset)); +} + +Napi::Value Query::IsPatternRooted(const Napi::CallbackInfo &info) { + uint32_t pattern_index = info[0].As().Uint32Value(); + return Boolean::New(info.Env(), ts_query_is_pattern_rooted(query_, pattern_index)); +} + +Napi::Value Query::IsPatternNonLocal(const Napi::CallbackInfo &info) { + uint32_t pattern_index = info[0].As().Uint32Value(); + return Boolean::New(info.Env(), ts_query_is_pattern_non_local(query_, pattern_index)); +} + +Napi::Value Query::StartIndexForPattern(const Napi::CallbackInfo &info) { + uint32_t pattern_index = info[0].As().Uint32Value(); + return Number::New(info.Env(), ts_query_start_byte_for_pattern(query_, pattern_index)); +} + +Napi::Value Query::DidExceedMatchLimit(const Napi::CallbackInfo &info) { + auto *data = info.Env().GetInstanceData(); + return Boolean::New(info.Env(), ts_query_cursor_did_exceed_match_limit(data->ts_query_cursor)); +} + +Napi::Value Query::MatchLimit(const Napi::CallbackInfo &info) { + auto *data = info.Env().GetInstanceData(); + return Number::New(info.Env(), ts_query_cursor_match_limit(data->ts_query_cursor)); +} + +} // namespace node_tree_sitter diff --git a/src/query.h b/src/query.h index 64ad20f2..89d7dc31 100644 --- a/src/query.h +++ b/src/query.h @@ -2,20 +2,20 @@ #define NODE_TREE_SITTER_QUERY_H_ #include "./addon_data.h" +#include "tree_sitter/api.h" #include #include -#include namespace node_tree_sitter { -class Query : public Napi::ObjectWrap { +class Query final : public Napi::ObjectWrap { public: static void Init(Napi::Env env, Napi::Object exports); static Query *UnwrapQuery(const Napi::Value &); explicit Query(const Napi::CallbackInfo &info); - ~Query() override; + ~Query() final; private: TSQuery *query_; @@ -24,8 +24,16 @@ class Query : public Napi::ObjectWrap { Napi::Value Matches(const Napi::CallbackInfo &); Napi::Value Captures(const Napi::CallbackInfo &); Napi::Value GetPredicates(const Napi::CallbackInfo &); + Napi::Value DisableCapture(const Napi::CallbackInfo &); + Napi::Value DisablePattern(const Napi::CallbackInfo &); + Napi::Value IsPatternGuaranteedAtStep(const Napi::CallbackInfo &); + Napi::Value IsPatternRooted(const Napi::CallbackInfo &); + Napi::Value IsPatternNonLocal(const Napi::CallbackInfo &); + Napi::Value StartIndexForPattern(const Napi::CallbackInfo &); + Napi::Value DidExceedMatchLimit(const Napi::CallbackInfo &); + Napi::Value MatchLimit(const Napi::CallbackInfo &); }; -} // namespace node_tree_sitter +} // namespace node_tree_sitter -#endif // NODE_TREE_SITTER_QUERY_H_ +#endif // NODE_TREE_SITTER_QUERY_H_ diff --git a/src/tree.cc b/src/tree.cc index c8d847a0..edf217c9 100644 --- a/src/tree.cc +++ b/src/tree.cc @@ -28,8 +28,10 @@ void Tree::Init(Napi::Env env, Napi::Object exports) { Function ctor = DefineClass(env, "Tree", { InstanceMethod("edit", &Tree::Edit, napi_default_method), InstanceMethod("rootNode", &Tree::RootNode, napi_default_method), + InstanceMethod("rootNodeWithOffset", &Tree::RootNodeWithOffset, napi_default_method), InstanceMethod("printDotGraph", &Tree::PrintDotGraph, napi_default_method), InstanceMethod("getChangedRanges", &Tree::GetChangedRanges, napi_default_method), + InstanceMethod("getIncludedRanges", &Tree::GetIncludedRanges, napi_default_method), InstanceMethod("getEditedRange", &Tree::GetEditedRange, napi_default_method), InstanceMethod("_cacheNode", &Tree::CacheNode, napi_default_method), InstanceMethod("_cacheNodes", &Tree::CacheNodes, napi_default_method), @@ -123,6 +125,18 @@ Napi::Value Tree::RootNode(const Napi::CallbackInfo &info) { return node_methods::MarshalNode(info, this, ts_tree_root_node(tree_)); } +Napi::Value Tree::RootNodeWithOffset(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + uint32_t offset_bytes = 0; + TSPoint offset_extent = {0, UINT32_MAX}; + + read_byte_count_from_js(&offset_bytes, info[0], "offsetBytes"); + read_number_from_js(&offset_extent.row, info[1], "offsetExtent.row"); + read_byte_count_from_js(&offset_extent.column, info[2], "offsetExtent.column"); + + return node_methods::MarshalNode(info, this, ts_tree_root_node_with_offset(tree_, offset_bytes, offset_extent)); +} + Napi::Value Tree::GetChangedRanges(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); const Tree *other_tree = UnwrapTree(info[0]); @@ -143,6 +157,21 @@ Napi::Value Tree::GetChangedRanges(const Napi::CallbackInfo &info) { return result; } +Napi::Value Tree::GetIncludedRanges(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + uint32_t range_count; + TSRange *ranges = ts_tree_included_ranges(tree_, &range_count); + + Array result = Array::New(env); + for (size_t i = 0; i < range_count; i++) { + result[i] = RangeToJS(env, ranges[i]); + } + + free(ranges); + + return result; +} + Napi::Value Tree::GetEditedRange(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); TSNode root = ts_tree_root_node(tree_); @@ -269,8 +298,4 @@ Napi::Value Tree::CacheNodes(const Napi::CallbackInfo &info) { return env.Undefined(); } -} // namespace node_tree_sitter - - - - +} // namespace node_tree_sitter diff --git a/src/tree.h b/src/tree.h index 412f260e..e086de77 100644 --- a/src/tree.h +++ b/src/tree.h @@ -2,22 +2,22 @@ #define NODE_TREE_SITTER_TREE_H_ #include "./addon_data.h" +#include "tree_sitter/api.h" #include #include -#include #include namespace node_tree_sitter { -class Tree : public Napi::ObjectWrap { +class Tree final : public Napi::ObjectWrap { public: static void Init(Napi::Env env, Napi::Object exports); - static Napi::Value NewInstance(Napi::Env env, TSTree *); - static const Tree *UnwrapTree(const Napi::Value &); + static Napi::Value NewInstance(Napi::Env env, TSTree *tree); + static const Tree *UnwrapTree(const Napi::Value &value); explicit Tree(const Napi::CallbackInfo &); - ~Tree() override; + ~Tree() final; struct NodeCacheEntry { Tree *tree; @@ -29,16 +29,17 @@ class Tree : public Napi::ObjectWrap { std::unordered_map cached_nodes_; private: - Napi::Value Edit(const Napi::CallbackInfo &); - Napi::Value RootNode(const Napi::CallbackInfo &); - Napi::Value PrintDotGraph(const Napi::CallbackInfo &); - Napi::Value GetEditedRange(const Napi::CallbackInfo &); - Napi::Value GetChangedRanges(const Napi::CallbackInfo &); - Napi::Value CacheNode(const Napi::CallbackInfo &); - Napi::Value CacheNodes(const Napi::CallbackInfo &); - + Napi::Value Edit(const Napi::CallbackInfo &info); + Napi::Value RootNode(const Napi::CallbackInfo &info); + Napi::Value RootNodeWithOffset(const Napi::CallbackInfo &info); + Napi::Value PrintDotGraph(const Napi::CallbackInfo &info); + Napi::Value GetEditedRange(const Napi::CallbackInfo &info); + Napi::Value GetChangedRanges(const Napi::CallbackInfo &info); + Napi::Value GetIncludedRanges(const Napi::CallbackInfo &info); + Napi::Value CacheNode(const Napi::CallbackInfo &info); + Napi::Value CacheNodes(const Napi::CallbackInfo &info); }; -} // namespace node_tree_sitter +} // namespace node_tree_sitter -#endif // NODE_TREE_SITTER_TREE_H_ +#endif // NODE_TREE_SITTER_TREE_H_ diff --git a/src/tree_cursor.cc b/src/tree_cursor.cc index 072f3a57..de286391 100644 --- a/src/tree_cursor.cc +++ b/src/tree_cursor.cc @@ -2,9 +2,9 @@ #include "./node.h" #include "./tree.h" #include "./tree_cursor.h" +#include "tree_sitter/api.h" #include -#include using namespace Napi; @@ -19,16 +19,24 @@ void TreeCursor::Init(Napi::Env env, Napi::Object exports) { InstanceAccessor("nodeType", &TreeCursor::NodeType, nullptr, napi_default_method), InstanceAccessor("nodeIsNamed", &TreeCursor::NodeIsNamed, nullptr, napi_default_method), InstanceAccessor("nodeIsMissing", &TreeCursor::NodeIsMissing, nullptr, napi_default_method), + InstanceAccessor("currentFieldId", &TreeCursor::CurrentFieldId, nullptr, napi_default_method), InstanceAccessor("currentFieldName", &TreeCursor::CurrentFieldName, nullptr, napi_default_method), + InstanceAccessor("currentDepth", &TreeCursor::CurrentDepth, nullptr, napi_default_method), + InstanceAccessor("currentDescendantIndex", &TreeCursor::CurrentDescendantIndex, nullptr, napi_default_method), - InstanceMethod("startPosition", &TreeCursor::StartPosition, napi_default_method), - InstanceMethod("endPosition", &TreeCursor::EndPosition, napi_default_method), - InstanceMethod("gotoParent", &TreeCursor::GotoParent, napi_default_method), InstanceMethod("gotoFirstChild", &TreeCursor::GotoFirstChild, napi_default_method), - InstanceMethod("gotoFirstChildForIndex", &TreeCursor::GotoFirstChildForIndex, napi_default_method), + InstanceMethod("gotoLastChild", &TreeCursor::GotoLastChild, napi_default_method), + InstanceMethod("gotoParent", &TreeCursor::GotoParent, napi_default_method), InstanceMethod("gotoNextSibling", &TreeCursor::GotoNextSibling, napi_default_method), + InstanceMethod("gotoPreviousSibling", &TreeCursor::GotoPreviousSibling, napi_default_method), + InstanceMethod("gotoDescendant", &TreeCursor::GotoDescendant, napi_default_method), + InstanceMethod("gotoFirstChildForIndex", &TreeCursor::GotoFirstChildForIndex, napi_default_method), + InstanceMethod("gotoFirstChildForPosition", &TreeCursor::GotoFirstChildForPosition, napi_default_method), + InstanceMethod("startPosition", &TreeCursor::StartPosition, napi_default_method), + InstanceMethod("endPosition", &TreeCursor::EndPosition, napi_default_method), InstanceMethod("currentNode", &TreeCursor::CurrentNode, napi_default_method), InstanceMethod("reset", &TreeCursor::Reset, napi_default_method), + InstanceMethod("resetTo", &TreeCursor::ResetTo, napi_default_method), }); exports["TreeCursor"] = ctor; @@ -47,20 +55,45 @@ TreeCursor::TreeCursor(const Napi::CallbackInfo& info) : Napi::ObjectWrap().Uint32Value(); + ts_tree_cursor_goto_descendant(&cursor_, goal_descendant_index); + return env.Undefined(); +} + Napi::Value TreeCursor::GotoFirstChildForIndex(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); if (!info[0].IsNumber()) { - throw TypeError::New(env, "Argument must be an integer"); + throw TypeError::New(env, "First argument must be an integer"); } auto index = info[0].As(); uint32_t goal_byte = index.Uint32Value() * 2; @@ -72,23 +105,29 @@ Napi::Value TreeCursor::GotoFirstChildForIndex(const Napi::CallbackInfo &info) { } -Napi::Value TreeCursor::GotoNextSibling(const Napi::CallbackInfo &info) { - bool result = ts_tree_cursor_goto_next_sibling(&cursor_); - return Boolean::New(info.Env(), result); +Napi::Value TreeCursor::GotoFirstChildForPosition(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + Napi::Maybe goal_point = PointFromJS(info[0].As()); + if (goal_point.IsNothing()) { + throw TypeError::New(env, "First argument must be a Point object"); + } + int64_t child_index = ts_tree_cursor_goto_first_child_for_point(&cursor_, goal_point.Unwrap()); + if (child_index < 0) { + return env.Null(); + } + return Number::New(env, static_cast(child_index)); } Napi::Value TreeCursor::StartPosition(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); TSNode node = ts_tree_cursor_current_node(&cursor_); - TransferPoint(env, ts_node_start_point(node)); - return env.Undefined(); + TransferPoint(info.Env(), ts_node_start_point(node)); + return info.Env().Undefined(); } Napi::Value TreeCursor::EndPosition(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); TSNode node = ts_tree_cursor_current_node(&cursor_); - TransferPoint(env, ts_node_end_point(node)); - return env.Undefined(); + TransferPoint(info.Env(), ts_node_end_point(node)); + return info.Env().Undefined(); } Napi::Value TreeCursor::CurrentNode(const Napi::CallbackInfo &info) { @@ -98,11 +137,16 @@ Napi::Value TreeCursor::CurrentNode(const Napi::CallbackInfo &info) { } Napi::Value TreeCursor::Reset(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info.This().As()["tree"]); - TSNode node = node_methods::UnmarshalNode(env, tree); + TSNode node = node_methods::UnmarshalNode(info.Env(), tree); ts_tree_cursor_reset(&cursor_, node); - return env.Undefined(); + return info.Env().Undefined(); +} + +Napi::Value TreeCursor::ResetTo(const Napi::CallbackInfo &info) { + TSTreeCursor other_cursor = TreeCursor::Unwrap(info[0].As())->cursor_; + ts_tree_cursor_reset_to(&cursor_, &other_cursor); + return info.Env().Undefined(); } Napi::Value TreeCursor::NodeType(const Napi::CallbackInfo &info) { @@ -120,6 +164,14 @@ Napi::Value TreeCursor::NodeIsMissing(const Napi::CallbackInfo &info) { return Boolean::New(info.Env(), ts_node_is_missing(node)); } +Napi::Value TreeCursor::CurrentFieldId(const Napi::CallbackInfo &info) { + TSFieldId field_id = ts_tree_cursor_current_field_id(&cursor_); + if (field_id != 0) { + return Number::New(info.Env(), static_cast(field_id)); + } + return info.Env().Undefined(); +} + Napi::Value TreeCursor::CurrentFieldName(const Napi::CallbackInfo &info) { const char *field_name = ts_tree_cursor_current_field_name(&cursor_); if (field_name != nullptr) { @@ -128,6 +180,14 @@ Napi::Value TreeCursor::CurrentFieldName(const Napi::CallbackInfo &info) { return info.Env().Undefined(); } +Napi::Value TreeCursor::CurrentDepth(const Napi::CallbackInfo &info) { + return Number::New(info.Env(), static_cast(ts_tree_cursor_current_depth(&cursor_))); +} + +Napi::Value TreeCursor::CurrentDescendantIndex(const Napi::CallbackInfo &info) { + return Number::New(info.Env(), static_cast(ts_tree_cursor_current_descendant_index(&cursor_))); +} + Napi::Value TreeCursor::StartIndex(const Napi::CallbackInfo &info) { TSNode node = ts_tree_cursor_current_node(&cursor_); return ByteCountToJS(info.Env(), ts_node_start_byte(node)); @@ -138,4 +198,4 @@ Napi::Value TreeCursor::EndIndex(const Napi::CallbackInfo &info) { return ByteCountToJS(info.Env(), ts_node_end_byte(node)); } -} // namespace node_tree_sitter +} // namespace node_tree_sitter diff --git a/src/tree_cursor.h b/src/tree_cursor.h index ea5b7752..e20a20ab 100644 --- a/src/tree_cursor.h +++ b/src/tree_cursor.h @@ -2,41 +2,49 @@ #define NODE_TREE_SITTER_TREE_CURSOR_H_ #include "./addon_data.h" +#include "tree_sitter/api.h" #include #include -#include namespace node_tree_sitter { -class TreeCursor : public Napi::ObjectWrap { +class TreeCursor final : public Napi::ObjectWrap { public: static void Init(Napi::Env env, Napi::Object exports); static Napi::Value NewInstance(Napi::Env Env, TSTreeCursor); explicit TreeCursor(const Napi::CallbackInfo &); - ~TreeCursor() override; + ~TreeCursor() final; + + TSTreeCursor cursor_; private: - Napi::Value GotoParent(const Napi::CallbackInfo &); Napi::Value GotoFirstChild(const Napi::CallbackInfo &); - Napi::Value GotoFirstChildForIndex(const Napi::CallbackInfo &); + Napi::Value GotoLastChild(const Napi::CallbackInfo &); + Napi::Value GotoParent(const Napi::CallbackInfo &); Napi::Value GotoNextSibling(const Napi::CallbackInfo &); + Napi::Value GotoPreviousSibling(const Napi::CallbackInfo &); + Napi::Value GotoDescendant(const Napi::CallbackInfo &); + Napi::Value GotoFirstChildForIndex(const Napi::CallbackInfo &); + Napi::Value GotoFirstChildForPosition(const Napi::CallbackInfo &); Napi::Value StartPosition(const Napi::CallbackInfo &); Napi::Value EndPosition(const Napi::CallbackInfo &); Napi::Value CurrentNode(const Napi::CallbackInfo &); Napi::Value Reset(const Napi::CallbackInfo &); + Napi::Value ResetTo(const Napi::CallbackInfo &); Napi::Value NodeType(const Napi::CallbackInfo &); Napi::Value NodeIsNamed(const Napi::CallbackInfo &); Napi::Value NodeIsMissing(const Napi::CallbackInfo &); + Napi::Value CurrentFieldId(const Napi::CallbackInfo &); Napi::Value CurrentFieldName(const Napi::CallbackInfo &); + Napi::Value CurrentDepth(const Napi::CallbackInfo &); + Napi::Value CurrentDescendantIndex(const Napi::CallbackInfo &); Napi::Value StartIndex(const Napi::CallbackInfo &); Napi::Value EndIndex(const Napi::CallbackInfo &); - - TSTreeCursor cursor_; }; -} // namespace node_tree_sitter +} // namespace node_tree_sitter -#endif // NODE_TREE_SITTER_TREE_CURSOR_H_ +#endif // NODE_TREE_SITTER_TREE_CURSOR_H_ diff --git a/src/util.h b/src/util.h deleted file mode 100644 index 1f54bb65..00000000 --- a/src/util.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef NODE_TREE_SITTER_UTIL_H_ -#define NODE_TREE_SITTER_UTIL_H_ - -#include - -namespace node_tree_sitter { - -#define length_of_array(a) (sizeof(a) / sizeof(a[0])) - -struct FunctionPair { - const char *name; - Napi::Function::Callback callback; -}; - -} // namespace node_tree_sitter - -#endif // NODE_TREE_SITTER_UTIL_H_ diff --git a/test/lookahead_iterator_test.js b/test/lookahead_iterator_test.js new file mode 100644 index 00000000..62428196 --- /dev/null +++ b/test/lookahead_iterator_test.js @@ -0,0 +1,38 @@ +const Parser = require(".."); +const Rust = require("tree-sitter-rust"); +const { assert } = require("chai"); +const { LookaheadIterator } = Parser; + +describe("LookaheadIterator", () => { + + const parser = new Parser(); + parser.setLanguage(Rust); + + describe("test lookahead iterator", () => { + it("should iterate correctly", () => { + const tree = parser.parse("struct Stuff {}"); + + const cursor = tree.walk(); + + assert(cursor.gotoFirstChild()); // struct + assert(cursor.gotoFirstChild()); // struct keyword + + const nextState = cursor.currentNode.nextParseState; + assert.notEqual(nextState, 0); + assert(cursor.gotoNextSibling()); // type_identifier + assert.equal(nextState, cursor.currentNode.parseState); + assert.equal(cursor.currentNode.grammarName, "identifier"); + assert.notEqual(cursor.currentNode.grammarId, cursor.currentNode.typeId); + + const expectedSymbols = ["//", "/*", "identifier", "line_comment", "block_comment"] + const lookahead = new LookaheadIterator(Rust, nextState); + assert.deepEqual(lookahead.iterNames(), expectedSymbols); + + assert(lookahead.resetState(nextState)); + assert.deepEqual(lookahead.iterNames(), expectedSymbols); + + assert(lookahead.reset(Rust, nextState)); + assert.deepEqual(lookahead.iterNames(), expectedSymbols); + }); + }); +}); diff --git a/test/node_test.js b/test/node_test.js index b81c47b8..408fd70c 100644 --- a/test/node_test.js +++ b/test/node_test.js @@ -1,7 +1,41 @@ const Parser = require(".."); +const C = require('tree-sitter-c'); +const EmbeddedTemplate = require('tree-sitter-embedded-template'); const JavaScript = require('tree-sitter-javascript'); +const JSON = require('tree-sitter-json'); +const Python = require('tree-sitter-python'); const { assert } = require("chai"); +const JSON_EXAMPLE = ` + +[ + 123, + false, + { + "x": null + } +] +` + +function getAllNodes(tree) { + const result = []; + let visitedChildren = false; + let 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; @@ -51,6 +85,171 @@ describe("Node", () => { }) }); + describe(".child", () => { + it("returns the child at the given index", () => { + parser.setLanguage(JSON); + const tree = parser.parse(JSON_EXAMPLE); + const arrayNode = tree.rootNode.child(0); + + assert.equal(arrayNode.type, "array"); + assert.equal(arrayNode.namedChildCount, 3); + assert.equal(arrayNode.startIndex, JSON_EXAMPLE.indexOf("[")); + assert.equal(arrayNode.endIndex, JSON_EXAMPLE.indexOf("]") + 1); + assert.deepEqual(arrayNode.startPosition, { row: 2, column: 0 }); + assert.deepEqual(arrayNode.endPosition, { row: 8, column: 1 }); + assert.equal(arrayNode.childCount, 7); + + const leftBracketNode = arrayNode.child(0); + const numberNode = arrayNode.child(1); + const commaNode1 = arrayNode.child(2); + const falseNode = arrayNode.child(3); + const commaNode2 = arrayNode.child(4); + const objectNode = arrayNode.child(5); + const rightBracketNode = arrayNode.child(6); + + assert.equal(leftBracketNode.type, "["); + assert.equal(numberNode.type, "number"); + assert.equal(commaNode1.type, ","); + assert.equal(falseNode.type, "false"); + assert.equal(commaNode2.type, ","); + assert.equal(objectNode.type, "object"); + assert.equal(rightBracketNode.type, "]"); + + assert(!leftBracketNode.isNamed); + assert(numberNode.isNamed); + assert(!commaNode1.isNamed); + assert(falseNode.isNamed); + assert(!commaNode2.isNamed); + assert(objectNode.isNamed); + assert(!rightBracketNode.isNamed); + + assert.equal(numberNode.startIndex, JSON_EXAMPLE.indexOf("123")); + assert.equal(numberNode.endIndex, JSON_EXAMPLE.indexOf("123") + 3); + assert.deepEqual(numberNode.startPosition, { row: 3, column: 2 }); + assert.deepEqual(numberNode.endPosition, { row: 3, column: 5 }); + + assert.equal(falseNode.startIndex, JSON_EXAMPLE.indexOf("false")); + assert.equal(falseNode.endIndex, JSON_EXAMPLE.indexOf("false") + 5); + assert.deepEqual(falseNode.startPosition, { row: 4, column: 2 }); + assert.deepEqual(falseNode.endPosition, { row: 4, column: 7 }); + + assert.equal(objectNode.startIndex, JSON_EXAMPLE.indexOf("{")); + assert.equal(objectNode.endIndex, JSON_EXAMPLE.indexOf("}") + 1); + assert.deepEqual(objectNode.startPosition, { row: 5, column: 2 }); + assert.deepEqual(objectNode.endPosition, { row: 7, column: 3 }); + + assert.equal(objectNode.childCount, 3); + const leftBraceNode = objectNode.child(0); + const pairNode = objectNode.child(1); + const rightBraceNode = objectNode.child(2); + + assert.equal(leftBraceNode.type, "{"); + assert.equal(pairNode.type, "pair"); + assert.equal(rightBraceNode.type, "}"); + + assert(!leftBraceNode.isNamed); + assert(pairNode.isNamed); + assert(!rightBraceNode.isNamed); + + assert.equal(pairNode.startIndex, JSON_EXAMPLE.indexOf('"x"')); + assert.equal(pairNode.endIndex, JSON_EXAMPLE.indexOf("null") + 4); + assert.deepEqual(pairNode.startPosition, { row: 6, column: 4 }); + assert.deepEqual(pairNode.endPosition, { row: 6, column: 13 }); + + assert.equal(pairNode.childCount, 3); + const stringNode = pairNode.child(0); + const colonNode = pairNode.child(1); + const nullNode = pairNode.child(2); + + assert.equal(stringNode.type, "string"); + assert.equal(colonNode.type, ":"); + assert.equal(nullNode.type, "null"); + + assert(stringNode.isNamed); + assert(!colonNode.isNamed); + assert(nullNode.isNamed); + + assert.equal(stringNode.startIndex, JSON_EXAMPLE.indexOf('"x"')); + assert.equal(stringNode.endIndex, JSON_EXAMPLE.indexOf('"x"') + 3); + assert.deepEqual(stringNode.startPosition, { row: 6, column: 4 }); + assert.deepEqual(stringNode.endPosition, { row: 6, column: 7 }); + + assert.equal(nullNode.startIndex, JSON_EXAMPLE.indexOf("null")); + assert.equal(nullNode.endIndex, JSON_EXAMPLE.indexOf("null") + 4); + assert.deepEqual(nullNode.startPosition, { row: 6, column: 9 }); + assert.deepEqual(nullNode.endPosition, { row: 6, column: 13 }); + + assert.equal(stringNode.parent, pairNode); + assert.equal(nullNode.parent, pairNode); + assert.equal(pairNode.parent, objectNode); + assert.equal(numberNode.parent, arrayNode); + assert.equal(falseNode.parent, arrayNode); + assert.equal(objectNode.parent, arrayNode); + assert.equal(arrayNode.parent, tree.rootNode); + assert.equal(tree.rootNode.parent, null); + }); + }); + + describe(".namedChild", () => { + it("returns the named child at the given index", () => { + parser.setLanguage(JSON); + const tree = parser.parse(JSON_EXAMPLE); + const arrayNode = tree.rootNode.namedChild(0); + + const numberNode = arrayNode.namedChild(0); + const falseNode = arrayNode.namedChild(1); + const objectNode = arrayNode.namedChild(2); + + assert.equal(numberNode.type, "number"); + assert.equal(numberNode.startIndex, JSON_EXAMPLE.indexOf("123")); + assert.equal(numberNode.endIndex, JSON_EXAMPLE.indexOf("123") + 3); + assert.deepEqual(numberNode.startPosition, { row: 3, column: 2 }); + assert.deepEqual(numberNode.endPosition, { row: 3, column: 5 }); + + assert.equal(falseNode.type, "false"); + assert.equal(falseNode.startIndex, JSON_EXAMPLE.indexOf("false")); + assert.equal(falseNode.endIndex, JSON_EXAMPLE.indexOf("false") + 5); + assert.deepEqual(falseNode.startPosition, { row: 4, column: 2 }); + assert.deepEqual(falseNode.endPosition, { row: 4, column: 7 }); + + assert.equal(objectNode.type, "object"); + assert.equal(objectNode.startIndex, JSON_EXAMPLE.indexOf("{")); + assert.deepEqual(objectNode.startPosition, { row: 5, column: 2 }); + assert.deepEqual(objectNode.endPosition, { row: 7, column: 3 }); + + assert.equal(objectNode.namedChildCount, 1); + + const pairNode = objectNode.namedChild(0); + assert.equal(pairNode.type, "pair"); + assert.equal(pairNode.startIndex, JSON_EXAMPLE.indexOf('"x"')); + assert.equal(pairNode.endIndex, JSON_EXAMPLE.indexOf("null") + 4); + assert.deepEqual(pairNode.startPosition, { row: 6, column: 4 }); + assert.deepEqual(pairNode.endPosition, { row: 6, column: 13 }); + + const stringNode = pairNode.namedChild(0); + const nullNode = pairNode.namedChild(1); + + assert.equal(stringNode.startIndex, JSON_EXAMPLE.indexOf('"x"')); + assert.equal(stringNode.endIndex, JSON_EXAMPLE.indexOf('"x"') + 3); + assert.deepEqual(stringNode.startPosition, { row: 6, column: 4 }); + assert.deepEqual(stringNode.endPosition, { row: 6, column: 7 }); + + assert.equal(nullNode.startIndex, JSON_EXAMPLE.indexOf("null")); + assert.equal(nullNode.endIndex, JSON_EXAMPLE.indexOf("null") + 4); + assert.deepEqual(nullNode.startPosition, { row: 6, column: 9 }); + assert.deepEqual(nullNode.endPosition, { row: 6, column: 13 }); + + assert.equal(stringNode.parent, pairNode); + assert.equal(nullNode.parent, pairNode); + assert.equal(pairNode.parent, objectNode); + assert.equal(numberNode.parent, arrayNode); + assert.equal(falseNode.parent, arrayNode); + assert.equal(objectNode.parent, arrayNode); + assert.equal(arrayNode.parent, tree.rootNode); + assert.equal(tree.rootNode.parent, null); + }); + }); + describe(".children", () => { it("returns an array of child nodes", () => { const tree = parser.parse("x10 + 1000"); @@ -63,6 +262,74 @@ 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()` + + const tree = parser.parse(source); + const node = tree.rootNode.firstChild; + assert.equal(node.type, 'if_statement'); + const cursor = tree.walk(); + const alternatives = node.childrenForFieldName('alternative', cursor); + const alternative_texts = alternatives.map(n => { + const condition = n.childForFieldName('condition'); + return source.slice(condition.startIndex, condition.endIndex); + }); + assert.deepEqual(alternative_texts, ['two', 'three', 'four']); + }); + }); + + describe(".childForFieldName", () => { + it("checks the parent node", () => { + const tree = parser.parse("foo(a().b[0].c.d.e())"); + const callNode = tree.rootNode.firstNamedChild.firstNamedChild; + assert.equal(callNode.type, "call_expression"); + + // Regression test - when a field points to a hidden node (in this case, `_expression`) + // the hidden node should not be added to the node parent cache. + assert.equal( + callNode.childForFieldName("function").parent, + callNode + ); + }); + + it("checks that extra hidden children are skipped", () => { + parser.setLanguage(Python); + const tree = parser.parse("while a:\n pass"); + const whileNode = tree.rootNode.firstChild; + assert.equal(whileNode.type, "while_statement"); + assert.equal(whileNode.childForFieldName("body"), whileNode.child(3)); + }); + }); + + describe(".fieldNameForChild", () => { + it("returns the field name for the given child node", () => { + parser.setLanguage(C); + const tree = parser.parse("int w = x + y;"); + const translation_unit_node = tree.rootNode; + const declaration_node = translation_unit_node.firstNamedChild; + + const binary_expression_node = declaration_node + .childForFieldName("declarator") + .childForFieldName("value"); + + assert.equal(binary_expression_node.fieldNameForChild(0), "left"); + assert.equal(binary_expression_node.fieldNameForChild(1), "operator"); + assert.equal(binary_expression_node.fieldNameForChild(2), "right"); + // Negative test - Not a valid child index + assert.equal(binary_expression_node.fieldNameForChild(3), null); + }); + }); + describe(".namedChildren", () => { it("returns an array of named child nodes", () => { const tree = parser.parse("x10 + 1000"); @@ -258,25 +525,25 @@ describe("Node", () => { it('finds all of the descendants of the given type in the given range', () => { const 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}) + 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}) + 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}) + 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}) + descendants = outerSum.descendantsOfType('number', { row: 0, column: 0 }, { row: 0, column: 30 }) assert.deepEqual( descendants.map(node => node.startIndex), [4, 12, 20] @@ -284,8 +551,8 @@ describe("Node", () => { descendants = outerSum.descendantsOfType( ['identifier', 'number'], - {row: 0, column: 0}, - {row: 0, column: 30} + { row: 0, column: 0 }, + { row: 0, column: 30 } ) assert.deepEqual( descendants.map(node => node.startIndex), @@ -298,7 +565,7 @@ describe("Node", () => { [4, 12, 20] ); - descendants = outerSum.firstChild.descendantsOfType('number', {row: 0, column: 0}, {row: 0, column: 30}) + descendants = outerSum.firstChild.descendantsOfType('number', { row: 0, column: 0 }, { row: 0, column: 30 }) assert.deepEqual( descendants.map(node => node.startIndex), [4, 12] @@ -327,7 +594,7 @@ describe("Node", () => { const 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/) + assert.throws(() => number.closest({ a: 1 }), /Argument must be a string or array of strings/) }); }); @@ -389,6 +656,35 @@ describe("Node", () => { }); }); + describe(".isExtra()", () => { + it("returns true if the node is an extra node like comments", () => { + const tree = parser.parse("foo(/* hi */);"); + const node = tree.rootNode; + const comment_node = node.descendantForIndex(7, 7); + + assert.equal(node.type, "program"); + assert.equal(comment_node.type, "comment"); + assert(!node.isExtra()); + assert(comment_node.isExtra()); + }); + }); + + describe(".toString()", () => { + it("returns a string representation of the node", () => { + const tree = parser.parse("if (a) b"); + const rootNode = tree.rootNode; + const ifNode = rootNode.descendantForIndex(0, 0); + const parenNode = rootNode.descendantForIndex(3, 3); + const identifierNode = rootNode.descendantForIndex(4, 4); + assert.equal(ifNode.type, "if"); + assert.equal(ifNode.toString(), "(\"if\")"); + assert.equal(parenNode.type, "("); + assert.equal(parenNode.toString(), "(\"(\")"); + assert.equal(identifierNode.type, "identifier"); + assert.equal(identifierNode.toString(), "(identifier)"); + }); + }); + describe(".text", () => { Object.entries({ '.parse(String)': (parser, src) => parser.parse(src), @@ -402,16 +698,206 @@ describe("Node", () => { const quotientNode = tree.rootNode.firstChild.firstChild; const [numerator, slash, denominator] = quotientNode.children; - assert.equal(src, tree.rootNode.text, 'root node text'); + assert.equal(src, tree.rootNode.text, 'root node text'); assert.equal(denominatorSrc, denominator.text, 'denominator text'); - assert.equal(src, quotientNode.text, 'quotient text'); - assert.equal(numeratorSrc, numerator.text, 'numerator text'); - assert.equal('/', slash.text, '"/" text'); + assert.equal(src, quotientNode.text, 'quotient text'); + assert.equal(numeratorSrc, numerator.text, 'numerator text'); + assert.equal('/', slash.text, '"/" text'); }) ) }); - describe("VSCode", () => { + describe(".descendantCount", () => { + it("returns the number of descendants", () => { + parser.setLanguage(JSON); + const 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, node, `index ${i}`); + } + + for (let i = allNodes.length - 1; i >= 0; i--) { + const node = allNodes[i]; + cursor.gotoDescendant(i) + assert.equal(cursor.currentNode, node, `rev index ${i}`); + } + }); + + it("tests a single node tree", () => { + parser.setLanguage(EmbeddedTemplate); + const 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, nodes[0]); + + cursor.gotoDescendant(1); + assert.equal(cursor.currentDepth, 1); + assert.equal(cursor.currentNode, nodes[1]); + }); + }); + + describe(".descendantFor{Index, Position}", () => { + it("returns the descendant at the given index", () => { + parser.setLanguage(JSON); + const tree = parser.parse(JSON_EXAMPLE); + const arrayNode = tree.rootNode; + + // Leaf node exactly matches the given bounds - index query + const colonIndex = JSON_EXAMPLE.indexOf(":"); + let colonNode = arrayNode.descendantForIndex(colonIndex, colonIndex + 1); + assert.equal(colonNode.type, ":"); + assert.equal(colonNode.startIndex, colonIndex); + assert.equal(colonNode.endIndex, colonIndex + 1); + assert.deepEqual(colonNode.startPosition, { row: 6, column: 7 }); + assert.deepEqual(colonNode.endPosition, { row: 6, column: 8 }); + + // Leaf node exactly matches the given bounds - point query + colonNode = arrayNode.descendantForPosition({ row: 6, column: 7 }, { row: 6, column: 8 }); + assert.equal(colonNode.type, ":"); + assert.equal(colonNode.startIndex, colonIndex); + assert.equal(colonNode.endIndex, colonIndex + 1); + assert.deepEqual(colonNode.startPosition, { row: 6, column: 7 }); + assert.deepEqual(colonNode.endPosition, { row: 6, column: 8 }); + + // The given point is between two adjacent leaf nodes - index query + colonNode = arrayNode.descendantForIndex(colonIndex, colonIndex); + assert.equal(colonNode.type, ":"); + assert.equal(colonNode.startIndex, colonIndex); + assert.equal(colonNode.endIndex, colonIndex + 1); + assert.deepEqual(colonNode.startPosition, { row: 6, column: 7 }); + assert.deepEqual(colonNode.endPosition, { row: 6, column: 8 }); + + // The given point is between two adjacent leaf nodes - point query + colonNode = arrayNode.descendantForPosition({ row: 6, column: 7 }, { row: 6, column: 7 }); + assert.equal(colonNode.type, ":"); + assert.equal(colonNode.startIndex, colonIndex); + assert.equal(colonNode.endIndex, colonIndex + 1); + assert.deepEqual(colonNode.startPosition, { row: 6, column: 7 }); + assert.deepEqual(colonNode.endPosition, { row: 6, column: 8 }); + + // Leaf node starts at the lower bound, ends after the upper bound - index query + const stringIndex = JSON_EXAMPLE.indexOf('"x"'); + let stringNode = arrayNode.descendantForIndex(stringIndex, stringIndex + 2); + assert.equal(stringNode.type, "string"); + assert.equal(stringNode.startIndex, stringIndex); + assert.equal(stringNode.endIndex, stringIndex + 3); + assert.deepEqual(stringNode.startPosition, { row: 6, column: 4 }); + assert.deepEqual(stringNode.endPosition, { row: 6, column: 7 }); + + // Leaf node starts at the lower bound, ends after the upper bound - point query + stringNode = arrayNode.descendantForPosition({ row: 6, column: 4 }, { row: 6, column: 6 }); + assert.equal(stringNode.type, "string"); + assert.equal(stringNode.startIndex, stringIndex); + assert.equal(stringNode.endIndex, stringIndex + 3); + assert.deepEqual(stringNode.startPosition, { row: 6, column: 4 }); + assert.deepEqual(stringNode.endPosition, { row: 6, column: 7 }); + + // Leaf node starts before the lower bound, ends at the upper bound - index query + const nullIndex = JSON_EXAMPLE.indexOf("null"); + let nullNode = arrayNode.descendantForIndex(nullIndex + 1, nullIndex + 4); + assert.equal(nullNode.type, "null"); + assert.equal(nullNode.startIndex, nullIndex); + assert.equal(nullNode.endIndex, nullIndex + 4); + assert.deepEqual(nullNode.startPosition, { row: 6, column: 9 }); + assert.deepEqual(nullNode.endPosition, { row: 6, column: 13 }); + + // Leaf node starts before the lower bound, ends at the upper bound - point query + nullNode = arrayNode.descendantForPosition({ row: 6, column: 11 }, { row: 6, column: 13 }); + assert.equal(nullNode.type, "null"); + assert.equal(nullNode.startIndex, nullIndex); + assert.equal(nullNode.endIndex, nullIndex + 4); + assert.deepEqual(nullNode.startPosition, { row: 6, column: 9 }); + assert.deepEqual(nullNode.endPosition, { row: 6, column: 13 }); + + // The bounds span multiple leaf nodes - return the smallest node that does span it. + let pairNode = arrayNode.descendantForIndex(stringIndex + 2, stringIndex + 4); + assert.equal(pairNode.type, "pair"); + assert.equal(pairNode.startIndex, stringIndex); + assert.equal(pairNode.endIndex, stringIndex + 9); + assert.deepEqual(pairNode.startPosition, { row: 6, column: 4 }); + assert.deepEqual(pairNode.endPosition, { row: 6, column: 13 }); + + assert.equal(colonNode.parent, pairNode); + + // No leaf spans the given range - return the smallest node that does span it. + pairNode = arrayNode.descendantForPosition({ row: 6, column: 6 }, { row: 6, column: 8 }); + assert.equal(pairNode.type, "pair"); + assert.equal(pairNode.startIndex, stringIndex); + assert.equal(pairNode.endIndex, stringIndex + 9); + assert.deepEqual(pairNode.startPosition, { row: 6, column: 4 }); + assert.deepEqual(pairNode.endPosition, { row: 6, column: 13 }); + }); + }); + + describe('.rootNodeWithOffset', () => { + it('returns the root node of the tree, offset by the given byte offset', () => { + const 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("Numeric symbols respect simple aliases", () => { + it("should ensure numeric symbol ids for an alias match the normal id", () => { + parser.setLanguage(Python); + + // Example 1: + // Python argument lists can contain "splat" arguments, which are not allowed within + // other expressions. This includes `parenthesized_list_splat` nodes like `(*b)`. These + // `parenthesized_list_splat` nodes are aliased as `parenthesized_expression`. Their numeric + // `symbol`, aka `kind_id` should match that of a normal `parenthesized_expression`. + const tree = parser.parse("(a((*b)))"); + const root = tree.rootNode; + assert.equal( + root.toString(), + "(module (expression_statement (parenthesized_expression (call function: (identifier) arguments: (argument_list (parenthesized_expression (list_splat (identifier))))))))" + ); + + const outerExprNode = root.firstChild.firstChild; + assert.equal(outerExprNode.type, "parenthesized_expression"); + + const innerExprNode = outerExprNode.firstNamedChild.childForFieldName("arguments").firstNamedChild; + assert.equal(innerExprNode.type, "parenthesized_expression"); + assert.equal(innerExprNode.typeId, outerExprNode.typeId); + }); + }); + + describe("Property accesses", () => { it("shouldn't segfault when accessing properties on the prototype", () => { const tree = parser.parse('2 + 2'); const nodePrototype = Object.getPrototypeOf(tree.rootNode); diff --git a/test/parser_test.js b/test/parser_test.js index 3ef50671..05e24940 100644 --- a/test/parser_test.js +++ b/test/parser_test.js @@ -1,5 +1,8 @@ const Parser = require(".."); +const HTML = require('tree-sitter-html'); const JavaScript = require('tree-sitter-javascript'); +const JSON = require('tree-sitter-json'); +const Rust = require('tree-sitter-rust'); const { assert } = require("chai"); describe("Parser", () => { @@ -72,7 +75,7 @@ describe("Parser", () => { errorMessages.push([message, error]); }; - parser.setLogger((msg, params) => { + parser.setLogger((_msg, _params) => { throw thrownError; }); }); @@ -92,14 +95,60 @@ describe("Parser", () => { }); }); + describe(".printDotGraphs", () => { + beforeEach(() => { + parser.setLanguage(JavaScript); + }); + + it("prints a dot graph to the output file", () => { + if (process.platform === "win32") { + return; + } + + const hasZeroIndexedRow = s => s.indexOf("position: 0,") !== -1; + + const tmp = require("tmp"); + const debugGraphFile = tmp.fileSync({ postfix: ".dot" }); + parser.printDotGraphs(true, debugGraphFile.fd); + parser.parse("const zero = 0"); + + const fs = require('fs'); + const logReader = fs.readFileSync(debugGraphFile.name, 'utf8').split('\n'); + for (let line of logReader) { + assert.strictEqual(hasZeroIndexedRow(line), false, `Graph log output includes zero-indexed row: ${line}`); + } + + debugGraphFile.removeCallback(); + }); + }); + describe(".parse", () => { beforeEach(() => { parser.setLanguage(JavaScript); }); + it("parses a simple string", () => { + parser.setLanguage(Rust); + const tree = parser.parse(` + struct Stuff {} + fn main() {} + `); + const rootNode = tree.rootNode; + assert.equal(rootNode.type, "source_file"); + + assert.equal( + rootNode.toString(), + "(source_file (struct_item name: (type_identifier) body: (field_declaration_list)) " + + "(function_item name: (identifier) parameters: (parameters) body: (block)))" + ); + + const structNode = rootNode.firstChild; + assert.equal(structNode.type, "struct_item"); + }); + it("reads from the given input", () => { const parts = ["first", "_", "second", "_", "third"]; - const tree = parser.parse((index) => parts.shift()); + const tree = parser.parse((_index) => parts.shift()); assert.equal(tree.rootNode.toString(), "(program (expression_statement (identifier)))"); }); @@ -147,14 +196,14 @@ describe("Parser", () => { { startIndex: start1, endIndex: end1, - startPosition: {row: 0, column: start1}, - endPosition: {row: 0, column: 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} + startPosition: { row: 0, column: start2 }, + endPosition: { row: 0, column: end2 } }, ] }); @@ -165,5 +214,450 @@ describe("Parser", () => { ); }) }) + + describe('invalid chars at eof', () => { + it('shows in the parse tree', () => { + parser.setLanguage(JSON); + const tree = parser.parse('\xdf'); + assert.equal(tree.rootNode.toString(), "(document (ERROR (UNEXPECTED 223)))"); + }); + }); + + describe('unexpected null characters', () => { + it('has a null character in the source', () => { + parser.setLanguage(JavaScript); + const tree = parser.parse('var \0 something;') + assert.equal( + tree.rootNode.toString(), + "(program (variable_declaration (ERROR (UNEXPECTED '\\0')) (variable_declarator name: (identifier))))" + ); + }); + }); + + it("parses an empty file with a reused tree", () => { + parser.setLanguage(Rust); + + let tree = parser.parse(""); + parser.parse("", tree); + + tree = parser.parse("\n "); + parser.parse("\n ", tree); + }); + + describe('parsing with a timeout', () => { + it('stops after a certain number of microseconds', () => { + parser.setLanguage(JSON); + + // dont use Date.now(), we need microseconds + let startTime = performance.now() * 1000; + parser.setTimeoutMicros(1000); + let tree = parser.parse((offset, _) => offset === 0 ? " [" : ",0"); + assert.equal(tree, null); + assert.isBelow(performance.now() * 1000 - startTime, 2000); + + startTime = performance.now() * 1000; + parser.setTimeoutMicros(5000); + tree = parser.parse((offset, _) => offset === 0 ? " [" : ",0"); + assert.equal(tree, null); + assert.isAbove(performance.now() * 1000 - startTime, 100); + assert.isBelow(performance.now() * 1000 - startTime, 10000); + + parser.setTimeoutMicros(0); + tree = parser.parse((offset, _) => offset > 5000 ? "" : offset == 5000 ? "]" : ",0"); + assert.equal(tree.rootNode.firstChild.type, "array"); + }); + }); + + describe('parsing with a timeout and reset', () => { + it('stops after a certain number of microseconds and resets the parser', () => { + parser.setLanguage(JSON); + + parser.setTimeoutMicros(5); + let tree = parser.parse( + '["ok", 1, 2, 3, 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]' + ); + assert.equal(tree, null); + + // Without calling reset, the parser continues from where it left off, so + // it does not see the changes to the beginning of the source code. + parser.setTimeoutMicros(0); + tree = parser.parse( + '[null, 1, 2, 3, 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]' + ); + assert.equal(tree.rootNode.firstNamedChild.firstNamedChild.type, "string"); + + parser.setTimeoutMicros(5); + tree = parser.parse( + '[\"ok\", 1, 2, 3, 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]' + ); + assert.equal(tree, null); + + // By calling reset, we force the parser to start over from scratch so + // that it sees the changes to the beginning of the source code. + parser.setTimeoutMicros(0); + parser.reset(); + tree = parser.parse( + '[null, 1, 2, 3, 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]' + ); + assert.equal(tree.rootNode.firstNamedChild.firstNamedChild.type, "null"); + }); + }); + + describe('parsing with a timeout and implicit reset', () => { + it('stops after a certain number of microseconds and resets the parser', () => { + parser.setLanguage(JavaScript); + + parser.setTimeoutMicros(5); + let tree = parser.parse( + '[\"ok\", 1, 2, 3, 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]' + ); + assert.equal(tree, null); + + // Changing the parser's language implicitly resets, discarding + // the previous partial parse. + parser.setLanguage(JSON); + parser.setTimeoutMicros(0); + tree = parser.parse( + '[null, 1, 2, 3, 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]' + ); + assert.equal(tree.rootNode.firstNamedChild.firstNamedChild.type, "null"); + }); + }); + + describe('parsing with a timeout and no completion', () => { + it('stops after a certain number of microseconds and returns before completion', () => { + parser.setLanguage(JavaScript); + + parser.setTimeoutMicros(5); + let tree = parser.parse( + '[\"ok\", 1, 2, 3, 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]' + ); + assert.equal(tree, null); + }); + }); + + describe('one included range', () => { + it('parses the text within a range', () => { + parser.setLanguage(HTML); + const sourceCode = "hi"; + const htmlTree = parser.parse(sourceCode); + const scriptContentNode = htmlTree.rootNode.child(1).child(1); + assert.equal(scriptContentNode.type, "raw_text"); + + parser.setLanguage(JavaScript); + const jsTree = parser.parse( + sourceCode, + null, + { + includedRanges: [{ + startIndex: scriptContentNode.startIndex, + endIndex: scriptContentNode.endIndex, + startPosition: scriptContentNode.startPosition, + endPosition: scriptContentNode.endPosition + }] + } + ); + + assert.equal( + jsTree.rootNode.toString(), + "(program (expression_statement (call_expression " + + "function: (member_expression object: (identifier) property: (property_identifier)) " + + "arguments: (arguments (string (string_fragment))))))" + ); + assert.deepEqual(jsTree.rootNode.startPosition, { row: 0, column: sourceCode.indexOf('console') }); + }); + }); + + describe('multiple included ranges', () => { + it('parses the text within multiple ranges', () => { + parser.setLanguage(JavaScript); + const sourceCode = "html `
Hello, ${name.toUpperCase()}, it's ${now()}.
`"; + 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(' ')); + + assert.equal(bStartTagNode.type, "start_tag"); + assert.equal(bStartTagNode.startIndex, sourceCode.indexOf('')); + assert.equal(bStartTagNode.endIndex, sourceCode.indexOf('${now()}')); + + assert.equal(bEndTagNode.type, "end_tag"); + assert.equal(bEndTagNode.startIndex, sourceCode.indexOf('')); + assert.equal(bEndTagNode.endIndex, sourceCode.indexOf('.')); + }); + }); + + describe('an included range containing mismatched positions', () => { + it('parses the text within the range', () => { + const sourceCode = "
test
{_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('parsing error in invalid included ranges', () => { + it('throws an exception', () => { + const ranges = [ + { startIndex: 23, endIndex: 29, startPosition: { row: 0, column: 23 }, endPosition: { row: 0, column: 29 } }, + { startIndex: 0, endIndex: 5, startPosition: { row: 0, column: 0 }, endPosition: { row: 0, column: 5 } }, + { startIndex: 50, endIndex: 60, startPosition: { row: 0, column: 50 }, endPosition: { row: 0, column: 60 } }, + ]; + parser.setLanguage(JavaScript); + assert.throws(() => parser.parse('var a = 1;', null, { includedRanges: ranges }), /Overlapping ranges/); + }); + }); + + describe('test parsing with external scanner that uses included range boundaries', () => { + it('parses the text within the range', () => { + const sourceCode = 'a <%= b() %> c <% d() %>'; + const range1StartByte = sourceCode.indexOf(' b() '); + const range1EndByte = range1StartByte + ' b() '.length; + const range2StartByte = sourceCode.indexOf(' d() '); + const range2EndByte = range2StartByte + ' d() '.length; + + parser.setLanguage(JavaScript); + const tree = parser.parse(sourceCode, null, { + includedRanges: [ + { + startIndex: range1StartByte, + endIndex: range1EndByte, + startPosition: { row: 0, column: range1StartByte }, + endPosition: { row: 0, column: range1EndByte } + }, + { + startIndex: range2StartByte, + endIndex: range2EndByte, + startPosition: { row: 0, column: range2StartByte }, + endPosition: { row: 0, column: range2EndByte } + }, + ] + }); + + const root = tree.rootNode; + const statement1 = root.firstChild; + const statement2 = root.child(1); + + assert.equal( + root.toString(), + "(program" + + " (expression_statement (call_expression function: (identifier) arguments: (arguments)))" + + " (expression_statement (call_expression function: (identifier) arguments: (arguments))))" + ); + + assert.equal(statement1.startIndex, sourceCode.indexOf("b()")); + assert.equal(statement1.endIndex, sourceCode.indexOf(" %> c")); + assert.equal(statement2.startIndex, sourceCode.indexOf("d()")); + assert.equal(statement2.endIndex, sourceCode.length - " %>".length); + }); + }); + + describe('parsing with a newly excluded range', () => { + it('parses code after an edit', () => { + let sourceCode = '
<%= something %>
'; + + parser.setLanguage(HTML); + let firstTree = parser.parse(sourceCode); + + // Insert code at the beginning of the document. + const prefix = 'a very very long line of plain text. '; + firstTree.edit({ + startIndex: 0, + oldEndIndex: 0, + newEndIndex: prefix.length, + startPosition: { row: 0, column: 0 }, + oldEndPosition: { row: 0, column: 0 }, + newEndPosition: { row: 0, column: prefix.length }, + }); + sourceCode = prefix + sourceCode; + + // Parse the HTML again, this time *excluding* the template directive + // (which has moved since the previous parse). + const directiveStart = sourceCode.indexOf('<%='); + const directiveEnd = sourceCode.indexOf(''); + const sourceCodeEnd = sourceCode.length; + const tree = parser.parse(sourceCode, firstTree, { + includedRanges: [ + { + startIndex: 0, + endIndex: directiveStart, + startPosition: { row: 0, column: 0 }, + endPosition: { row: 0, column: directiveStart }, + }, + { + startIndex: directiveEnd, + endIndex: sourceCodeEnd, + startPosition: { row: 0, column: directiveEnd }, + endPosition: { row: 0, column: sourceCodeEnd }, + }, + ] + }); + + assert.equal( + tree.rootNode.toString(), + "(document (text) (element" + + " (start_tag (tag_name))" + + " (element (start_tag (tag_name)) (end_tag (tag_name)))" + + " (end_tag (tag_name))))" + ); + + assert.deepEqual( + tree.getChangedRanges(firstTree), + [ + // The first range that has changed syntax is the range of the newly-inserted text. + { + startIndex: 0, + endIndex: prefix.length, + startPosition: { row: 0, column: 0 }, + endPosition: { row: 0, column: prefix.length }, + }, + // Even though no edits were applied to the outer `div` element, + // its contents have changed syntax because a range of text that + // was previously included is now excluded. + { + startIndex: directiveStart, + endIndex: directiveEnd, + startPosition: { row: 0, column: directiveStart }, + endPosition: { row: 0, column: directiveEnd }, + }, + ] + ); + }); + }); + + describe('parsing with a newly included range', () => { + function simpleRange(startIndex, endIndex) { + return { + startIndex, + endIndex, + startPosition: { row: 0, column: startIndex }, + endPosition: { row: 0, column: endIndex }, + }; + } + + it('parses code after an edit', () => { + const sourceCode = '
<%= foo() %>
<%= bar() %><%= baz() %>'; + const range1Start = sourceCode.indexOf(' foo'); + const range2Start = sourceCode.indexOf(' bar'); + const range3Start = sourceCode.indexOf(' baz'); + const range1End = range1Start + 7; + const range2End = range2Start + 7; + const range3End = range3Start + 7; + + // Parse only the first code directive as JavaScript + parser.setLanguage(JavaScript); + const tree = parser.parse(sourceCode, null, { + includedRanges: [simpleRange(range1Start, range1End)] + }); + assert.equal( + tree.rootNode.toString(), + "(program" + + " (expression_statement (call_expression function: (identifier) arguments: (arguments))))" + ); + + // Parse both the first and third code directives as JavaScript, using the old tree as a + // reference. + const tree2 = parser.parse(sourceCode, tree, { + includedRanges: [ + simpleRange(range1Start, range1End), + simpleRange(range3Start, range3End) + ] + }); + assert.equal( + tree2.rootNode.toString(), + "(program" + + " (expression_statement (call_expression function: (identifier) arguments: (arguments)))" + + " (expression_statement (call_expression function: (identifier) arguments: (arguments))))" + ); + assert.deepEqual( + tree2.getChangedRanges(tree), + [simpleRange(range1End, range3End)] + ); + + const tree3 = parser.parse(sourceCode, tree, { + includedRanges: [ + simpleRange(range1Start, range1End), + simpleRange(range2Start, range2End), + simpleRange(range3Start, range3End), + ] + }); + assert.equal( + tree3.rootNode.toString(), + "(program" + + " (expression_statement (call_expression function: (identifier) arguments: (arguments)))" + + " (expression_statement (call_expression function: (identifier) arguments: (arguments)))" + + " (expression_statement (call_expression function: (identifier) arguments: (arguments))))" + ); + assert.deepEqual( + tree3.getChangedRanges(tree2), + [simpleRange(range2Start + 1, range2End - 1)] + ); + }); + }); }); }); diff --git a/test/query_test.js b/test/query_test.js index d4e0aab8..4fd2b714 100644 --- a/test/query_test.js +++ b/test/query_test.js @@ -1,27 +1,96 @@ const fs = require("fs"); const Parser = require(".."); +const C = require("tree-sitter-c"); +const Java = require("tree-sitter-java"); const JavaScript = require("tree-sitter-javascript"); +const JSON = require("tree-sitter-json"); +const Python = require("tree-sitter-python"); +const Ruby = require("tree-sitter-ruby"); +const Rust = require("tree-sitter-rust"); const { assert } = require("chai"); -const {Query, QueryCursor} = Parser +const { Query, QueryCursor } = Parser describe("Query", () => { const parser = new Parser(); parser.setLanguage(JavaScript); + describe("errors", () => { + it("errors on invalid syntax", () => { + assert.doesNotThrow(() => new Query(JavaScript, "(if_statement)")) + assert.doesNotThrow(() => new Query(JavaScript, "(if_statement condition:(parenthesized_expression (identifier)))")) + assert.throws(() => new Query(JavaScript, "(if_statement"), "TSQueryErrorSyntax"); + assert.throws(() => new Query(JavaScript, "; comment 1\n; comment 2\n (if_statement))"), "TSQueryErrorSyntax"); + assert.throws(() => new Query(JavaScript, "(if_statement identifier)"), "TSQueryErrorSyntax"); + assert.throws(() => new Query(JavaScript, "(if_statement condition:)"), "TSQueryErrorSyntax"); + assert.throws(() => new Query(JavaScript, "(identifier) \"h "), "TSQueryErrorSyntax"); + assert.throws(() => new Query(JavaScript, "((identifier) ()"), "TSQueryErrorSyntax"); + assert.throws(() => new Query(JavaScript, "((identifier) [])"), "TSQueryErrorSyntax"); + assert.throws(() => new Query(JavaScript, "((identifier) (#a)"), "TSQueryErrorSyntax"); + assert.throws(() => new Query(JavaScript, "((identifier) @x (#eq? @x a"), "TSQueryErrorSyntax"); + assert.throws(() => new Query(JavaScript, "(statement_block .)"), "TSQueryErrorSyntax"); + assert.throws(() => new Query(JavaScript, "(statement_block ! (if_statement))"), "TSQueryErrorSyntax"); + assert.throws(() => new Query(C, "(parameter_list [ \")\" @foo)"), "TSQueryErrorSyntax"); + assert.throws(() => new Query(Python, "[(unary_operator (_) @operand) (not_operator (_) @operand]"), ""); + }); + + it("errors on invalid symbols", () => { + assert.throws(() => new Query(JavaScript, "(clas)"), "TSQueryErrorNodeType"); + assert.throws(() => new Query(JavaScript, "(if_statement (arrayyyyy))"), "TSQueryErrorNodeType"); + assert.throws(() => new Query(JavaScript, "(if_statement condition: (non_existent3))"), "TSQueryErrorNodeType"); + assert.throws(() => new Query(JavaScript, "(if_statement condit: (identifier))"), "TSQueryErrorField"); + assert.throws(() => new Query(JavaScript, "(if_statement conditioning: (identifier))"), "TSQueryErrorField"); + assert.throws(() => new Query(JavaScript, "(if_statement !alternativ)"), "TSQueryErrorField"); + assert.throws(() => new Query(JavaScript, "(if_statement !alternatives)"), "TSQueryErrorField"); + }); + + it("errors on invalid predicates", () => { + assert.throws(() => new Query(JavaScript, "((identifier) @id (@id))"), "TSQueryErrorSyntax"); + assert.throws(() => new Query(JavaScript, "((identifier) @id (#eq? @id))"), "Wrong number of arguments"); + assert.throws(() => new Query(JavaScript, "((identifier) @id (#eq? @id @ok))"), "TSQueryErrorCapture"); + }); + + it("errors on impossible patterns", () => { + assert.throws(() => new Query(JavaScript, "(binary_expression left: (expression (identifier)) left: (expression (identifier)))"), "TSQueryErrorStructure"); + assert.doesNotThrow(() => new Query(JavaScript, "(function_declaration name: (identifier) (statement_block))")); + assert.throws(() => new Query(JavaScript, "(function_declaration name: (statement_block))"), "TSQueryErrorStructure"); + assert.doesNotThrow(() => new Query(Ruby, "(call receiver: (call))")); + assert.throws(() => new Query(Ruby, "(call receiver: (binary))"), "TSQueryErrorStructure"); + assert.doesNotThrow(() => new Query(JavaScript, `[ + (function_expression (identifier)) + (function_declaration (identifier)) + (generator_function_declaration (identifier)) + ]`)); + assert.throws(() => new Query(JavaScript, `[ + (function_expression (identifier)) + (function_declaration (object)) + (generator_function_declaration (identifier)) + ]`), "TSQueryErrorStructure"); + assert.throws(() => new Query(JavaScript, "(identifier (identifier))"), "TSQueryErrorStructure"); + assert.throws(() => new Query(JavaScript, "(true (true))"), "TSQueryErrorStructure"); + assert.doesNotThrow(() => new Query(JavaScript, "(if_statement condition: (parenthesized_expression(expression) @cond))")); + assert.throws(() => new Query(JavaScript, "(if_statement condition: (expression))"), "TSQueryErrorStructure"); + }); + }); + describe("new", () => { it("works with string", () => { - const query = new Query(JavaScript, ` + assert.doesNotThrow(() => new Query(JavaScript, ` (function_declaration name: (identifier) @fn-def) (call_expression function: (identifier) @fn-ref) - `); + `)); }); it("works with Buffer", () => { - const query = new Query(JavaScript, Buffer.from(` + assert.doesNotThrow(() => new Query(JavaScript, Buffer.from(` (function_declaration name: (identifier) @fn-def) (call_expression function: (identifier) @fn-ref) - `)); + `))); + }); + + it("verifies possible patterns with aliased parent nodes", () => { + assert.doesNotThrow(() => new Query(Ruby, "(destructured_parameter (identifier))")); + assert.throws(() => new Query(Ruby, "(destructured_parameter (string))"), "TSQueryErrorStructure"); }); }); @@ -45,8 +114,7 @@ describe("Query", () => { const query = new Query(JavaScript, "(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(tree, matches), [ { pattern: 0, captures: [{ name: "element", text: "d" }] }, @@ -203,7 +271,7 @@ describe("Query", () => { /// baz `); - let query = new Query(JavaScript,` + let query = new Query(JavaScript, ` ( (comment)+ @foo (#any-eq? @foo "/// foo") @@ -211,7 +279,7 @@ describe("Query", () => { `); let expectCount = (tree, queryText, expectedCount) => { - query = new Query(JavaScript,queryText); + query = new Query(JavaScript, queryText); captures = query.captures(tree.rootNode); assert.equal(captures.length, expectedCount); }; @@ -277,6 +345,749 @@ describe("Query", () => { ); }) }); + + describe("match limit", () => { + it("has too many permutations to track", () => { + const query = new Query(JavaScript, ` + (array (identifier) @pre (identifier) @post) + `); + + const source = "[" + "hello, ".repeat(50) + "];"; + + const tree = parser.parse(source); + const matches = query.matches(tree.rootNode, { matchLimit: 32 }); + + // For this pathological query, some match permutations will be dropped. + // Just check that a subset of the results are returned, and crash or + // leak occurs. + assert.deepEqual( + formatMatches(tree, matches)[0], + { pattern: 0, captures: [{ name: "pre", text: "hello" }, { name: "post", text: "hello" }] }, + ) + assert.equal(query.matchLimit, 32); + assert.equal(query.didExceedMatchLimit(), true); + }); + + it("has alternatives", () => { + const query = new Query(JavaScript, ` + ( + (comment) @doc + ; not immediate + (class_declaration) @class + ) + (call_expression + function: [ + (identifier) @function + (member_expression property: (property_identifier) @method) + ] + ) + `); + + const source = '/* hi */ a.b(); '.repeat(50); + + const tree = parser.parse(source); + const matches = query.matches(tree.rootNode, { matchLimit: 32 }); + + assert.deepEqual( + formatMatches(tree, matches), + Array(50).fill({ pattern: 1, captures: [{ name: "method", text: "b" }] }), + ); + assert.equal(query.matchLimit, 32); + assert.equal(query.didExceedMatchLimit(), true); + }); + }); + + describe(".disableCapture", () => { + it("disables a capture", () => { + const query = new Query(JavaScript, ` + (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(tree, 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(tree, matches), [ + { + pattern: 0, + captures: [ + { name: "name1", text: "foo" }, + { name: "name3", text: "foo" }, + { name: "body1", text: "{ return 1; }" }, + { name: "body2", text: "{ return 1; }" }, + ], + }, + ]); + }); + }); + + describe(".disablePattern", () => { + it("disables a pattern", () => { + const query = new Query(JavaScript, ` + (function_declaration + name: (identifier) @name) + (function_declaration + body: (statement_block) @body) + (class_declaration + name: (identifier) @name) + (class_declaration + body: (class_body) @body) + `); + + // disable the patterns that match names + query.disablePattern(0); + query.disablePattern(2); + + const source = "class A { constructor() {} } function b() { return 1; }"; + const tree = parser.parse(source); + + const matches = query.matches(tree.rootNode); + + assert.deepEqual(formatMatches(tree, matches), [ + { + pattern: 3, + captures: [ + { name: "body", text: "{ constructor() {} }" }, + ], + }, + { + pattern: 1, + captures: [ + { name: "body", text: "{ return 1; }" }, + ], + }, + ]); + }); + }); + + describe(".startIndexForPattern", () => { + it("returns the index where the given pattern starts in the query's source", () => { + const patterns1 = ` + "+" @operator + "-" @operator + "*" @operator + "=" @operator + "=>" @operator + `.trimStart(); + + const patterns2 = ` + (identifier) @a + (string) @b + `.trimStart(); + + const patterns3 = ` + ((identifier) @b (#match? @b i)) + (function_declaration name: (identifier) @c) + (method_definition name: (property_identifier) @d) + `.trimStart(); + + const source = patterns1 + patterns2 + patterns3; + + const query = new Query(JavaScript, source); + + assert.equal(query.startIndexForPattern(0), 0); + assert.equal(query.startIndexForPattern(5), patterns1.length); + assert.equal(query.startIndexForPattern(7), patterns1.length + patterns2.length); + }); + }); + + describe(".isPatternGuaranteedAtStep", () => { + it("returns true if the given pattern is guaranteed to match at the given step", () => { + const rows = [ + { + description: "no guaranteed steps", + language: Python, + pattern: `(expression_statement (string))`, + resultsBySubstring: [["expression_statement", false], ["string", false]], + }, + { + description: "all guaranteed steps", + language: JavaScript, + pattern: `(object "{" "}")`, + resultsBySubstring: [["object", false], ["{", true], ["}", true]], + }, + { + description: "a fallible step that is optional", + language: JavaScript, + pattern: `(object "{" (identifier)? @foo "}")`, + resultsBySubstring: [ + ["object", false], + ["{", true], + ["(identifier)?", false], + ["}", true], + ], + }, + { + description: "multiple fallible steps that are optional", + language: JavaScript, + pattern: `(object "{" (identifier)? @id1 ("," (identifier) @id2)? "}")`, + resultsBySubstring: [ + ["object", false], + ["{", true], + ["(identifier)? @id1", false], + ["\",\"", false], + ["}", true], + ], + }, + { + description: "guaranteed step after fallibe step", + language: JavaScript, + pattern: `(pair (property_identifier) ":")`, + resultsBySubstring: [["pair", false], ["property_identifier", false], [":", true]], + }, + { + description: "fallible step in between two guaranteed steps", + language: JavaScript, + pattern: `(ternary_expression + condition: (_) + "?" + consequence: (call_expression) + ":" + alternative: (_))`, + resultsBySubstring: [ + ["condition:", false], + ["\"?\"", false], + ["consequence:", false], + ["\":\"", true], + ["alternative:", true], + ], + }, + { + description: "one guaranteed step after a repetition", + language: JavaScript, + pattern: `(object "{" (_) "}")`, + resultsBySubstring: [["object", false], ["{", false], ["(_)", false], ["}", true]], + }, + { + description: "guaranteed steps after multiple repetitions", + language: JSON, + pattern: `(object "{" (pair) "," (pair) "," (_) "}")`, + resultsBySubstring: [ + ["object", false], + ["{", false], + ["(pair) \",\" (pair)", false], + ["(pair) \",\" (_)", false], + ["\",\" (_)", false], + ["(_)", true], + ["}", true], + ], + }, + { + description: "a guaranteed step with a field", + language: JavaScript, + pattern: `(binary_expression left: (expression) right: (_))`, + resultsBySubstring: [ + ["binary_expression", false], + ["(expression)", false], + ["(_)", true], + ], + }, + { + description: "multiple guaranteed steps with fields", + language: JavaScript, + pattern: `(function_declaration name: (identifier) body: (statement_block))`, + resultsBySubstring: [ + ["function_declaration", false], + ["identifier", true], + ["statement_block", true], + ], + }, + { + description: "nesting, one guaranteed step", + language: JavaScript, + pattern: ` + (function_declaration + name: (identifier) + body: (statement_block "{" (expression_statement) "}"))`, + resultsBySubstring: [ + ["function_declaration", false], + ["identifier", false], + ["statement_block", false], + ["{", false], + ["expression_statement", false], + ["}", true], + ], + }, + { + description: "a guaranteed step after some deeply nested hidden nodes", + language: Ruby, + pattern: ` + (singleton_class + value: (constant) + "end") + `, + resultsBySubstring: [ + ["singleton_class", false], + ["constant", false], + ["end", true], + ], + }, + { + description: "nesting, no guaranteed steps", + language: JavaScript, + pattern: ` + (call_expression + function: (member_expression + property: (property_identifier) @template-tag) + arguments: (template_string)) @template-call + `, + resultsBySubstring: [["property_identifier", false], ["template_string", false]], + }, + { + description: "a guaranteed step after a nested node", + language: JavaScript, + pattern: ` + (subscript_expression + object: (member_expression + object: (identifier) @obj + property: (property_identifier) @prop) + "[") + `, + resultsBySubstring: [ + ["identifier", false], + ["property_identifier", false], + ["[", true], + ], + }, + { + description: "a step that is fallible due to a predicate", + language: JavaScript, + pattern: ` + (subscript_expression + object: (member_expression + object: (identifier) @obj + property: (property_identifier) @prop) + "[" + (#match? @prop "foo")) + `, + resultsBySubstring: [ + ["identifier", false], + ["property_identifier", false], + ["[", true], + ], + }, + { + description: "alternation where one branch has guaranteed steps", + language: JavaScript, + pattern: ` + [ + (unary_expression (identifier)) + (call_expression + function: (_) + arguments: (_)) + (binary_expression right:(call_expression)) + ] + `, + resultsBySubstring: [ + ["identifier", false], + ["right:", false], + ["function:", true], + ["arguments:", true], + ], + }, + { + description: "guaranteed step at the end of an aliased parent node", + language: Ruby, + pattern: ` + (method_parameters "(" (identifier) @id")") + `, + resultsBySubstring: [["\"(\"", false], ["(identifier)", false], ["\")\"", true]], + }, + { + description: "long, but not too long to analyze", + language: JavaScript, + pattern: ` + (object "{" (pair) (pair) (pair) (pair) "}") + `, + resultsBySubstring: [ + ["\"{\"", false], + ["(pair)", false], + ["(pair) \"}\"", false], + ["\"}\"", true], + ], + }, + { + description: "too long to analyze", + language: JavaScript, + pattern: ` + (object "{" (pair) (pair) (pair) (pair) (pair) (pair) (pair) (pair) (pair) (pair) (pair) (pair) "}") + `, + resultsBySubstring: [ + ["\"{\"", false], + ["(pair)", false], + ["(pair) \"}\"", false], + ["\"}\"", false], + ], + }, + { + description: "hidden nodes that have several fields", + language: Java, + pattern: ` + (method_declaration name: (identifier)) + `, + resultsBySubstring: [["name:", true]], + }, + { + description: "top-level non-terminal extra nodes", + language: Ruby, + pattern: ` + (heredoc_body + (interpolation) + (heredoc_end) @end) + `, + resultsBySubstring: [ + ["(heredoc_body", false], + ["(interpolation)", false], + ["(heredoc_end)", true], + ], + }, + // TODO: figure out why line comments, an extra, are no longer allowed *anywhere* + // likely culprits are the fact that it's no longer a token itself or that it uses an + // external token + // { + // description: "multiple extra nodes", + // language: Rust, + // pattern: ` + // (call_expression + // (line_comment) @a + // (line_comment) @b + // (arguments)) + // `, + // resultsBySubstring: [ + // ["(line_comment) @a", false], + // ["(line_comment) @b", false], + // ["(arguments)", true], + // ], + // }, + ]; + + for (const row of rows) { + const query = new Query(row.language, row.pattern); + for (const [substring, isDefinite] of row.resultsBySubstring) { + const offset = row.pattern.indexOf(substring); + assert.equal( + query.isPatternGuaranteedAtStep(offset), + isDefinite, + `Description: ${row.description}, Pattern: ${row.pattern}, Substring: ${substring}, expected isDefinite to be: ${isDefinite}`, + ); + } + } + }); + }); + + describe(".isPatternRooted", () => { + it("returns true if the given pattern has a single root node", () => { + const rows = [ + { + description: "simple token", + pattern: "(identifier)", + isRooted: true, + }, + { + description: "simple non-terminal", + pattern: "(function_definition name: (identifier))", + isRooted: true, + }, + { + description: "alternative of many tokens", + pattern: '["if" "def" (identifier) (comment)]', + isRooted: true, + }, + { + description: "alternative of many non-terminals", + pattern: ` + [ + (function_definition name: (identifier)) + (class_definition name: (identifier)) + (block) + ] + `, + isRooted: true, + }, + { + description: "two siblings", + pattern: '("{" "}")', + isRooted: false, + }, + { + description: "top-level repetition", + pattern: "(comment)*", + isRooted: false, + }, + { + description: "alternative where one option has two siblings", + pattern: ` + [ + (block) + (class_definition) + ("(" ")") + (function_definition) + ] + `, + isRooted: false, + }, + { + description: "alternative where one option has a top-level repetition", + pattern: ` + [ + (block) + (class_definition) + (comment)* + (function_definition) + ] + `, + isRooted: false, + }, + ]; + + parser.setLanguage(Python); + for (const row of rows) { + const query = new Query(Python, row.pattern); + assert.equal( + query.isPatternRooted(0), + row.isRooted, + `Description: ${row.description}, Pattern: ${row.pattern}`, + ); + } + }); + }); + + describe(".isPatternNonLocal", () => { + it("returns true if the given pattern has a single root node", () => { + const rows = [ + { + description: "simple token", + pattern: "(identifier)", + language: Python, + isNonLocal: false, + }, + { + description: "siblings that can occur in an argument list", + pattern: "((identifier) (identifier))", + language: Python, + isNonLocal: true, + }, + { + description: "siblings that can occur in a statement block", + pattern: "((return_statement) (return_statement))", + language: Python, + isNonLocal: true, + }, + { + description: "siblings that can occur in a source file", + pattern: "((function_definition) (class_definition))", + language: Python, + isNonLocal: true, + }, + { + description: "siblings that can't occur in any repetition", + pattern: `("{" "}")`, + language: Python, + isNonLocal: false, + }, + { + description: "siblings that can't occur in any repetition, wildcard root", + pattern: `(_ "{" "}") @foo`, + language: JavaScript, + isNonLocal: false, + }, + { + description: "siblings that can occur in a class body, wildcard root", + pattern: "(_ (method_definition) (method_definition)) @foo", + language: JavaScript, + isNonLocal: true, + }, + { + description: "top-level repetitions that can occur in a class body", + pattern: "(method_definition)+ @foo", + language: JavaScript, + isNonLocal: true, + }, + { + description: "top-level repetitions that can occur in a statement block", + pattern: "(return_statement)+ @foo", + language: JavaScript, + isNonLocal: true, + }, + { + description: "rooted pattern that can occur in a statement block", + pattern: "(return_statement) @foo", + language: JavaScript, + isNonLocal: false, + }, + ]; + + for (const row of rows) { + const query = new Query(row.language, row.pattern); + assert.equal( + query.isPatternNonLocal(0), + row.isNonLocal, + `Description: ${row.description}, Pattern: ${row.pattern}`, + ); + } + }); + }); + + describe("max start depth", () => { + it("will not explore child nodes beyond the given depth", () => { + const source = ` +if (a1 && a2) { + if (b1 && b2) { } + if (c) { } +} +if (d) { + if (e1 && e2) { } + if (f) { } +} +`; + + const rows = [ + { + description: "depth 0: match translation unit", + depth: 0, + pattern: ` + (translation_unit) @capture + `, + matches: [ + [0, [["capture", "if (a1 && a2) {\n if (b1 && b2) { }\n if (c) { }\n}\nif (d) {\n if (e1 && e2) { }\n if (f) { }\n}\n"]]], + ] + }, + { + description: "depth 0: match none", + depth: 0, + pattern: ` + (if_statement) @capture + `, + matches: [] + }, + { + description: "depth 1: match 2 if statements at the top level", + depth: 1, + pattern: ` + (if_statement) @capture + `, + matches: [ + [0, [["capture", "if (a1 && a2) {\n if (b1 && b2) { }\n if (c) { }\n}"]]], + [0, [["capture", "if (d) {\n if (e1 && e2) { }\n if (f) { }\n}"]]], + ] + }, + { + description: "depth 1 with deep pattern: match the only the first if statement", + depth: 1, + pattern: ` + (if_statement + condition: (parenthesized_expression + (binary_expression) + ) + ) @capture + `, + matches: [ + [0, [["capture", "if (a1 && a2) {\n if (b1 && b2) { }\n if (c) { }\n}"]]], + ] + }, + { + description: "depth 3 with deep pattern: match all if statements with a binexpr condition", + depth: 3, + pattern: ` + (if_statement + condition: (parenthesized_expression + (binary_expression) + ) + ) @capture + `, + matches: [ + [0, [["capture", "if (a1 && a2) {\n if (b1 && b2) { }\n if (c) { }\n}"]]], + [0, [["capture", "if (b1 && b2) { }"]]], + [0, [["capture", "if (e1 && e2) { }"]]], + ] + }, + ]; + + parser.setLanguage(C); + const tree = parser.parse(source); + + for (const row of rows) { + const query = new Query(C, row.pattern); + const matches = query.matches(tree.rootNode, { maxStartDepth: row.depth }); + const expected = row.matches.map(([pattern, captures]) => ({ + pattern, + captures: captures.map(([name, text]) => ({ name, text })), + })); + assert.deepEqual(formatMatches(tree, matches), expected, row.description); + } + }); + + it("tests more", () => { + const source = ` +{ + { } + { + { } + } +} +` + const rows = [ + { + depth: 0, + matches: [ + [0, [["capture", "{\n { }\n {\n { }\n }\n}"]]], + ] + }, + { + depth: 1, + matches: [ + [0, [["capture", "{\n { }\n {\n { }\n }\n}"]]], + [0, [["capture", "{ }"]]], + [0, [["capture", "{\n { }\n }"]]], + ] + }, + { + depth: 2, + matches: [ + [0, [["capture", "{\n { }\n {\n { }\n }\n}"]]], + [0, [["capture", "{ }"]]], + [0, [["capture", "{\n { }\n }"]]], + [0, [["capture", "{ }"]]], + ] + }, + ]; + + parser.setLanguage(C); + const tree = parser.parse(source); + const query = new Query(C, "(compound_statement) @capture"); + const matches = query.matches(tree.rootNode); + const node = matches[0].captures[0].node; + assert.equal(node.type, "compound_statement"); + + for (const row of rows) { + const matches = query.matches(node, { maxStartDepth: row.depth }); + const expected = row.matches.map(([pattern, captures]) => ({ + pattern, + captures: captures.map(([name, text]) => ({ name, text })), + })); + assert.deepEqual(formatMatches(tree, matches), expected); + } + }); + }); }); function formatMatches(tree, matches) { diff --git a/test/tree_test.js b/test/tree_test.js index 579ed06a..5c836d50 100644 --- a/test/tree_test.js +++ b/test/tree_test.js @@ -1,5 +1,6 @@ const Parser = require(".."); const JavaScript = require('tree-sitter-javascript'); +const Rust = require('tree-sitter-rust'); const { assert } = require("chai"); describe("Tree", () => { @@ -99,8 +100,8 @@ describe("Tree", () => { assert.deepEqual(tree.getEditedRange(), { startIndex: 6, endIndex: 23, - startPosition: {row: 0, column: 6}, - endPosition: {row: 0, column: 23}, + startPosition: { row: 0, column: 6 }, + endPosition: { row: 0, column: 23 }, }); }) }); @@ -152,6 +153,236 @@ describe("Tree", () => { }); describe(".walk()", () => { + it("returns a cursor that can be used to walk the tree", () => { + parser.setLanguage(Rust); + + // let mut parser = Parser::new(); + // parser.set_language(&get_language("rust")).unwrap(); + // + // let tree = parser + // .parse( + // " + // struct Stuff { + // a: A, + // b: Option, + // } + // ", + // None, + // ) + // .unwrap(); + // + + const tree = parser.parse(` + struct Stuff { + a: A, + b: Option, + } + `); + + const cursor = tree.walk(); + assert.equal(cursor.nodeType, "source_file"); + + assert(cursor.gotoFirstChild()); + assert.equal(cursor.nodeType, "struct_item"); + + assert(cursor.gotoFirstChild()); + assert.equal(cursor.nodeType, "struct"); + assert(!cursor.nodeIsNamed); + + assert(cursor.gotoNextSibling()); + assert.equal(cursor.nodeType, "type_identifier"); + assert(cursor.nodeIsNamed); + + assert(cursor.gotoNextSibling()); + assert.equal(cursor.nodeType, "field_declaration_list"); + assert(cursor.nodeIsNamed); + + assert(cursor.gotoLastChild()); + assert.equal(cursor.nodeType, "}"); + assert(!cursor.nodeIsNamed); + assert.deepEqual(cursor.startPosition, { row: 4, column: 16 }); + + assert(cursor.gotoPreviousSibling()); + assert.equal(cursor.nodeType, ","); + assert(!cursor.nodeIsNamed); + assert.deepEqual(cursor.startPosition, { row: 3, column: 32 }); + + assert(cursor.gotoPreviousSibling()); + assert.equal(cursor.nodeType, "field_declaration"); + assert(cursor.nodeIsNamed); + assert.deepEqual(cursor.startPosition, { row: 3, column: 20 }); + + assert(cursor.gotoPreviousSibling()); + assert.equal(cursor.nodeType, ","); + assert(!cursor.nodeIsNamed); + assert.deepEqual(cursor.startPosition, { row: 2, column: 24 }); + + assert(cursor.gotoPreviousSibling()); + assert.equal(cursor.nodeType, "field_declaration"); + assert(cursor.nodeIsNamed); + assert.deepEqual(cursor.startPosition, { row: 2, column: 20 }); + + assert(cursor.gotoPreviousSibling()); + assert.equal(cursor.nodeType, "{"); + assert(!cursor.nodeIsNamed); + assert.deepEqual(cursor.startPosition, { row: 1, column: 29 }); + + const copy = tree.walk(); + copy.resetTo(cursor); + + assert.equal(copy.nodeType, "{"); + assert(!copy.nodeIsNamed); + + assert(copy.gotoParent()); + assert.equal(copy.nodeType, "field_declaration_list"); + assert(copy.nodeIsNamed); + + assert(copy.gotoParent()); + assert.equal(copy.nodeType, "struct_item"); + }); + + it("can fetch the previous sibling", () => { + parser.setLanguage(Rust); + + const text = ` + // Hi there + // This is fun! + // Another one! + `; + + const tree = parser.parse(text); + + const cursor = tree.walk(); + assert.equal(cursor.nodeType, "source_file"); + + assert(cursor.gotoLastChild()); + assert.equal(cursor.nodeType, "line_comment"); + assert.equal(cursor.currentNode.text, "// Another one!"); + + assert(cursor.gotoPreviousSibling()); + assert.equal(cursor.nodeType, "line_comment"); + assert.equal(cursor.currentNode.text, "// This is fun!"); + + assert(cursor.gotoPreviousSibling()); + assert.equal(cursor.nodeType, "line_comment"); + assert.equal(cursor.currentNode.text, "// Hi there"); + + assert(!cursor.gotoPreviousSibling()); + }); + + it("can access fields of nodes", () => { + const tree = parser.parse("function /*1*/ bar /*2*/ () {}"); + + const cursor = tree.walk(); + assert.equal(cursor.nodeType, "program"); + + assert(cursor.gotoFirstChild()); + assert.equal(cursor.nodeType, "function_declaration"); + assert.equal(cursor.currentFieldName, null); + + assert(cursor.gotoFirstChild()); + assert.equal(cursor.nodeType, "function"); + assert.equal(cursor.currentFieldName, null); + + assert(cursor.gotoNextSibling()); + assert.equal(cursor.nodeType, "comment"); + assert.equal(cursor.currentFieldName, null); + + assert(cursor.gotoNextSibling()); + assert.equal(cursor.nodeType, "identifier"); + assert.equal(cursor.currentFieldName, "name"); + + assert(cursor.gotoNextSibling()); + assert.equal(cursor.nodeType, "comment"); + assert.equal(cursor.currentFieldName, null); + + assert(cursor.gotoNextSibling()); + assert.equal(cursor.nodeType, "formal_parameters"); + assert.equal(cursor.currentFieldName, "parameters"); + }); + + it("can access children by positions", () => { + const source = ` + [ + one, + { + two: tree + }, + four, five, six + ];`.slice(1); + const tree = parser.parse(source); + + const cursor = tree.walk(); + assert.equal(cursor.nodeType, "program"); + + assert.equal(cursor.gotoFirstChildForPosition({ row: 7, column: 0 }), null); + assert.equal(cursor.gotoFirstChildForPosition({ row: 6, column: 7 }), null); + assert.equal(cursor.nodeType, "program"); + + // descend to expression statement + assert.equal(cursor.gotoFirstChildForPosition({ row: 6, column: 6 }), 0); + assert.equal(cursor.nodeType, "expression_statement"); + + // step into ';' and back up + assert.equal(cursor.gotoFirstChildForPosition({ row: 7, column: 0 }), null); + assert.equal(cursor.gotoFirstChildForPosition({ row: 6, column: 6 }), 1); + assert.deepEqual(cursor.startPosition, { row: 6, column: 5 }); + assert.equal(cursor.nodeType, ";"); + assert(cursor.gotoParent()); + + // descend into array + assert.equal(cursor.gotoFirstChildForPosition({ row: 6, column: 4 }), 0); + assert.equal(cursor.nodeType, "array"); + assert.deepEqual(cursor.startPosition, { row: 0, column: 4 }); + + // step into '[' and back up + assert.equal(cursor.gotoFirstChildForPosition({ row: 0, column: 4 }), 0); + assert.equal(cursor.nodeType, "["); + assert.deepEqual(cursor.startPosition, { row: 0, column: 4 }); + assert(cursor.gotoParent()); + + // step into identifier 'one' and back up + assert.equal(cursor.gotoFirstChildForPosition({ row: 1, column: 0 }), 1); + assert.equal(cursor.nodeType, "identifier"); + assert.deepEqual(cursor.startPosition, { row: 1, column: 8 }); + assert(cursor.gotoParent()); + assert.equal(cursor.gotoFirstChildForPosition({ row: 1, column: 10 }), 1); + assert.equal(cursor.nodeType, "identifier"); + assert.deepEqual(cursor.startPosition, { row: 1, column: 8 }); + assert(cursor.gotoParent()); + + // step into first ',' and back up + assert.equal(cursor.gotoFirstChildForPosition({ row: 1, column: 12 }), 2); + assert.equal(cursor.nodeType, ","); + assert.deepEqual(cursor.startPosition, { row: 1, column: 11 }); + assert(cursor.gotoParent()); + + // step into identifier 'four' and back up + assert.equal(cursor.gotoFirstChildForPosition({ row: 5, column: 0 }), 5); + assert.equal(cursor.nodeType, "identifier"); + assert.deepEqual(cursor.startPosition, { row: 5, column: 8 }); + assert(cursor.gotoParent()); + assert.equal(cursor.gotoFirstChildForPosition({ row: 5, column: 10 }), 5); + assert.equal(cursor.nodeType, "identifier"); + assert.deepEqual(cursor.startPosition, { row: 5, column: 8 }); + assert(cursor.gotoParent()); + + // step into ']' and back up + assert.equal(cursor.gotoFirstChildForPosition({ row: 6, column: 0 }), 10); + assert.equal(cursor.nodeType, "]"); + assert.deepEqual(cursor.startPosition, { row: 6, column: 4 }); + assert(cursor.gotoParent()); + assert.equal(cursor.gotoFirstChildForPosition({ row: 6, column: 0 }), 10); + assert.equal(cursor.nodeType, "]"); + assert.deepEqual(cursor.startPosition, { row: 6, column: 4 }); + assert(cursor.gotoParent()); + + // descend into object + assert.equal(cursor.gotoFirstChildForPosition({ row: 2, column: 0 }), 3); + assert.equal(cursor.nodeType, "object"); + assert.deepEqual(cursor.startPosition, { row: 2, column: 8 }); + }); + it('returns a cursor that can be used to walk the tree', () => { const tree = parser.parse('a * b + c / d'); @@ -160,8 +391,8 @@ describe("Tree", () => { nodeType: 'program', nodeIsNamed: true, nodeIsMissing: false, - startPosition: {row: 0, column: 0}, - endPosition: {row: 0, column: 13}, + startPosition: { row: 0, column: 0 }, + endPosition: { row: 0, column: 13 }, startIndex: 0, endIndex: 13 }); @@ -171,8 +402,8 @@ describe("Tree", () => { nodeType: 'expression_statement', nodeIsNamed: true, nodeIsMissing: false, - startPosition: {row: 0, column: 0}, - endPosition: {row: 0, column: 13}, + startPosition: { row: 0, column: 0 }, + endPosition: { row: 0, column: 13 }, startIndex: 0, endIndex: 13 }); @@ -182,8 +413,8 @@ describe("Tree", () => { nodeType: 'binary_expression', nodeIsNamed: true, nodeIsMissing: false, - startPosition: {row: 0, column: 0}, - endPosition: {row: 0, column: 13}, + startPosition: { row: 0, column: 0 }, + endPosition: { row: 0, column: 13 }, startIndex: 0, endIndex: 13 }); @@ -193,8 +424,8 @@ describe("Tree", () => { nodeType: 'binary_expression', nodeIsNamed: true, nodeIsMissing: false, - startPosition: {row: 0, column: 0}, - endPosition: {row: 0, column: 5}, + startPosition: { row: 0, column: 0 }, + endPosition: { row: 0, column: 5 }, startIndex: 0, endIndex: 5 }); @@ -204,8 +435,8 @@ describe("Tree", () => { nodeType: 'identifier', nodeIsNamed: true, nodeIsMissing: false, - startPosition: {row: 0, column: 0}, - endPosition: {row: 0, column: 1}, + startPosition: { row: 0, column: 0 }, + endPosition: { row: 0, column: 1 }, startIndex: 0, endIndex: 1 }); @@ -216,8 +447,8 @@ describe("Tree", () => { nodeType: '*', nodeIsNamed: false, nodeIsMissing: false, - startPosition: {row: 0, column: 2}, - endPosition: {row: 0, column: 3}, + startPosition: { row: 0, column: 2 }, + endPosition: { row: 0, column: 3 }, startIndex: 2, endIndex: 3 }); @@ -227,8 +458,8 @@ describe("Tree", () => { nodeType: 'identifier', nodeIsNamed: true, nodeIsMissing: false, - startPosition: {row: 0, column: 4}, - endPosition: {row: 0, column: 5}, + startPosition: { row: 0, column: 4 }, + endPosition: { row: 0, column: 5 }, startIndex: 4, endIndex: 5 }); @@ -239,8 +470,8 @@ describe("Tree", () => { nodeType: 'binary_expression', nodeIsNamed: true, nodeIsMissing: false, - startPosition: {row: 0, column: 0}, - endPosition: {row: 0, column: 5}, + startPosition: { row: 0, column: 0 }, + endPosition: { row: 0, column: 5 }, startIndex: 0, endIndex: 5 }); @@ -250,8 +481,8 @@ describe("Tree", () => { nodeType: '+', nodeIsNamed: false, nodeIsMissing: false, - startPosition: {row: 0, column: 6}, - endPosition: {row: 0, column: 7}, + startPosition: { row: 0, column: 6 }, + endPosition: { row: 0, column: 7 }, startIndex: 6, endIndex: 7 }); @@ -261,8 +492,8 @@ describe("Tree", () => { nodeType: 'binary_expression', nodeIsNamed: true, nodeIsMissing: false, - startPosition: {row: 0, column: 8}, - endPosition: {row: 0, column: 13}, + startPosition: { row: 0, column: 8 }, + endPosition: { row: 0, column: 13 }, startIndex: 8, endIndex: 13 }); @@ -272,8 +503,8 @@ describe("Tree", () => { nodeType: 'identifier', nodeIsNamed: true, nodeIsMissing: false, - startPosition: {row: 0, column: 12}, - endPosition: {row: 0, column: 13}, + startPosition: { row: 0, column: 12 }, + endPosition: { row: 0, column: 13 }, startIndex: 12, endIndex: 13 }); @@ -297,8 +528,8 @@ describe("Tree", () => { nodeType: 'binary_expression', nodeIsNamed: true, nodeIsMissing: false, - startPosition: {row: 0, column: 0}, - endPosition: {row: 0, column: 5}, + startPosition: { row: 0, column: 0 }, + endPosition: { row: 0, column: 5 }, startIndex: 0, endIndex: 5 }); @@ -308,8 +539,8 @@ describe("Tree", () => { nodeType: 'identifier', nodeIsNamed: true, nodeIsMissing: false, - startPosition: {row: 0, column: 0}, - endPosition: {row: 0, column: 1}, + startPosition: { row: 0, column: 0 }, + endPosition: { row: 0, column: 1 }, startIndex: 0, endIndex: 1 }); @@ -318,6 +549,19 @@ describe("Tree", () => { assert(!cursor.gotoParent()); }) }); + + describe(".rootNode", () => { + it("tests tree node equality", () => { + parser.setLanguage(Rust); + + const tree = parser.parse("struct A {}"); + const node1 = tree.rootNode; + const node2 = tree.rootNode; + assert.deepEqual(node1, node2); + assert.deepEqual(node1.firstChild, node2.firstChild); + assert.notDeepEqual(node1.firstChild, node2); + }); + }); }); function assertCursorState(cursor, params) { @@ -366,5 +610,5 @@ function getExtent(text) { index++ row++; } - return {row, column: text.length - index}; + return { row, column: text.length - index }; } diff --git a/tree-sitter.d.ts b/tree-sitter.d.ts index 0a54488d..2dda0e26 100644 --- a/tree-sitter.d.ts +++ b/tree-sitter.d.ts @@ -1,11 +1,15 @@ declare module "tree-sitter" { class Parser { parse(input: string | Parser.Input | Parser.InputReader, oldTree?: Parser.Tree, options?: { bufferSize?: number, includedRanges?: Parser.Range[] }): Parser.Tree; + getIncludedRanges(): Parser.Range[]; + getTimeoutMicros(): number; + setTimeoutMicros(timeout: number): void; getLanguage(): any; setLanguage(language: any): void; getLogger(): Parser.Logger; setLogger(logFunc: Parser.Logger): void; - printDotGraphs(enabled: boolean): void; + printDotGraphs(enabled?: boolean, fd?: number): void; + reset(): void; } namespace Parser { @@ -47,8 +51,11 @@ declare module "tree-sitter" { export interface SyntaxNode { tree: Tree; + id: number; + typeId: number; + grammarId: number; type: string; - typeId: string; + grammarName: string; isNamed: boolean; text: string; startPosition: Point; @@ -68,13 +75,23 @@ declare module "tree-sitter" { nextNamedSibling: SyntaxNode | null; previousSibling: SyntaxNode | null; previousNamedSibling: SyntaxNode | null; + parseState: number; + nextParseState: number; + descendantCount: number; hasChanges(): boolean; hasError(): boolean; isMissing(): boolean; + isExtra(): boolean; + isError(): boolean; toString(): string; child(index: number): SyntaxNode | null; namedChild(index: number): SyntaxNode | null; + childForFieldName(fieldName: string): SyntaxNode | null; + childForFieldId(fieldId: number): SyntaxNode | null; + fieldNameForChild(childIndex: number): string | null; + childrenForFieldName(fieldName: string): Array; + childrenForFieldId(fieldId: number): Array; firstChildForIndex(index: number): SyntaxNode | null; firstNamedChildForIndex(index: number): SyntaxNode | null; @@ -103,22 +120,32 @@ declare module "tree-sitter" { endIndex: number; readonly currentNode: SyntaxNode; readonly currentFieldName: string; + readonly currentFieldId: number; + readonly currentDepth: number; + readonly currentDescendantIndex: number; reset(node: SyntaxNode): void + resetTo(other: TreeCursor): void; gotoParent(): boolean; gotoFirstChild(): boolean; - gotoFirstChildForIndex(index: number): boolean; + gotoLastChild(): boolean; + gotoFirstChildForIndex(goalIndex: number): boolean; + gotoFirstChildForPosition(goalPosition: Point): boolean; gotoNextSibling(): boolean; + gotoPreviousSibling(): boolean; + gotoDescendant(goalDescendantIndex: number): boolean; } export interface Tree { readonly rootNode: SyntaxNode; + rootNodeWithOffset(offsetBytes: number, offsetExtent: Point): SyntaxNode; edit(delta: Edit): Tree; walk(): TreeCursor; getChangedRanges(other: Tree): Range[]; + getIncludedRanges(): Range[]; getEditedRange(other: Tree): Range; - printDotGraph(): void; + printDotGraph(fd?: number): void; } export interface QueryMatch { @@ -140,11 +167,52 @@ declare module "tree-sitter" { readonly setProperties: any[]; readonly assertedProperties: any[]; readonly refutedProperties: any[]; + readonly matchLimit: number; + + constructor( + language: any, + source: string | Buffer, + ); + + matches( + rootNode: SyntaxNode, + options?: { + startPosition?: Point; + endPosition?: Point; + startIndex?: number; + endIndex?: number; + matchLimit?: number; + maxStartDepth?: number; + } + ): QueryMatch[]; + captures( + rootNode: SyntaxNode, + options?: { + startPosition?: Point; + endPosition?: Point; + startIndex?: number; + endIndex?: number; + matchLimit?: number; + maxStartDepth?: number; + } + ): QueryCapture[]; + disableCapture(captureName: string): void; + disablePattern(patternIndex: number): void; + isPatternGuaranteedAtStep(byteOffset: number): boolean; + isPatternRooted(patternIndex: number): boolean; + isPatternNonLocal(patternIndex: number): boolean; + startIndexForPattern(patternIndex: number): number; + didExceedMatchLimit(): boolean; + } - constructor(language: any, source: string | Buffer); + export class LookaheadIterator { + readonly currentSymbol: number; + readonly currentSymbolName: string; - matches(rootNode: SyntaxNode, startPosition?: Point, endPosition?: Point): QueryMatch[]; - captures(rootNode: SyntaxNode, startPosition?: Point, endPosition?: Point): QueryCapture[]; + reset(language: any, state: number): boolean; + resetState(state: number): boolean; + next(): number; + iterNames(): string[]; } } From c1d1834ce289d4e0aea602eb0c3ee74507e8ef4c Mon Sep 17 00:00:00 2001 From: Amaan Qureshi Date: Mon, 4 Mar 2024 00:05:51 -0500 Subject: [PATCH 6/8] chore: get rid of jest tests --- jest-tests/constants.js | 10 -- jest-tests/parse_input.js | 9 -- jest-tests/runit.js | 9 -- jest-tests/test.test.js | 208 -------------------------------------- jest-tests/test2.test.js | 18 ---- jest-tests/test3.test.js | 18 ---- 6 files changed, 272 deletions(-) delete mode 100644 jest-tests/constants.js delete mode 100644 jest-tests/parse_input.js delete mode 100644 jest-tests/runit.js delete mode 100644 jest-tests/test.test.js delete mode 100644 jest-tests/test2.test.js delete mode 100644 jest-tests/test3.test.js diff --git a/jest-tests/constants.js b/jest-tests/constants.js deleted file mode 100644 index 06c3a84e..00000000 --- a/jest-tests/constants.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = { -INPUT: ` - const Parser = require("."); - const Javascript = require("tree-sitter-javascript"); - const jsParser = new Parser(); -`, - -// from running runit.js -OUTPUT: "(program (lexical_declaration (variable_declarator name: (identifier) value: (call_expression function: (identifier) arguments: (arguments (string (string_fragment)))))) (lexical_declaration (variable_declarator name: (identifier) value: (call_expression function: (identifier) arguments: (arguments (string (string_fragment)))))) (lexical_declaration (variable_declarator name: (identifier) value: (new_expression constructor: (identifier) arguments: (arguments)))))" -} diff --git a/jest-tests/parse_input.js b/jest-tests/parse_input.js deleted file mode 100644 index 66c36e97..00000000 --- a/jest-tests/parse_input.js +++ /dev/null @@ -1,9 +0,0 @@ -const Parser = require(".."); -const Javascript = require("tree-sitter-javascript"); -const jsParser = new Parser(); -jsParser.setLanguage(Javascript); - -module.exports = (input) => { - const code = jsParser.parse(input) - return code.rootNode; -} diff --git a/jest-tests/runit.js b/jest-tests/runit.js deleted file mode 100644 index 71ff788e..00000000 --- a/jest-tests/runit.js +++ /dev/null @@ -1,9 +0,0 @@ -const Parser = require(".."); -const constants = require("./constants"); -const Javascript = require("tree-sitter-javascript"); -const jsParser = new Parser(); -jsParser.setLanguage(Javascript); - -const code = jsParser.parse(constants.INPUT) -const output = code.rootNode.toString() -console.log(output); diff --git a/jest-tests/test.test.js b/jest-tests/test.test.js deleted file mode 100644 index a65cd396..00000000 --- a/jest-tests/test.test.js +++ /dev/null @@ -1,208 +0,0 @@ -const Parser = require(".."); -const constants = require("./constants"); -const parse_input = require("./parse_input.js"); -const Javascript = require("tree-sitter-javascript"); - -const { Query } = Parser; -const jsParser = new Parser(); -jsParser.setLanguage(Javascript); - -describe("Jest test 1", () => { - it("should work", () => { - const code = jsParser.parse(constants.INPUT); - // Due to the race condition arising from Jest's worker pool, - // code.rootNode is null if the native extension hasn't finished - // loading. In this case, we skip the test. - if (code.rootNode) { - const output = code.rootNode.toString(); - expect(output).toBe(constants.OUTPUT); - } - }); - - it("should work with separate import", () => { - const rootNode = parse_input(constants.INPUT); - if (rootNode) { - expect(rootNode.toString()).toBe(constants.OUTPUT); - } - }); - function assertCursorState(cursor, params) { - expect(cursor.nodeType).toBe(params.nodeType); - expect(cursor.nodeIsNamed).toBe(params.nodeIsNamed); - expect(cursor.startPosition).toEqual(params.startPosition); - expect(cursor.endPosition).toEqual(params.endPosition); - expect(cursor.startIndex).toEqual(params.startIndex); - expect(cursor.endIndex).toEqual(params.endIndex); - - const node = cursor.currentNode; - expect(node.type).toBe(params.nodeType); - expect(node.isNamed).toBe(params.nodeIsNamed); - expect(node.startPosition).toEqual(params.startPosition); - expect(node.endPosition).toEqual(params.endPosition); - expect(node.startIndex).toEqual(params.startIndex); - expect(node.endIndex).toEqual(params.endIndex); - } - - function assert(thing) { - expect(thing).toBeTruthy(); - } - - it("should work with cursors", () => { - const tree = jsParser.parse("a * b + c / d"); - if (tree.rootNode) { - const 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, - }); - expect(childIndex).toBe(2); - - assert(!cursor.gotoNextSibling()); - assert(cursor.gotoParent()); - assert(cursor.gotoParent()); - assert(cursor.gotoParent()); - assert(cursor.gotoParent()); - assert(!cursor.gotoParent()); - } - }); - - it("returns all of the matches for the given query", () => { - const tree = jsParser.parse( - "function one() { two(); function three() {} }" - ); - const query = new Query( - Javascript, - ` - (function_declaration name: (identifier) @fn-def) - (call_expression function: (identifier) @fn-ref) - ` - ); - const matches = query.matches(tree.rootNode); - expect(formatMatches(tree, matches)).toEqual([ - { pattern: 0, captures: [{ name: "fn-def", text: "one" }] }, - { pattern: 1, captures: [{ name: "fn-ref", text: "two" }] }, - { pattern: 0, captures: [{ name: "fn-def", text: "three" }] }, - ]); - }); -}); - -function formatMatches(tree, matches) { - return matches.map(({ pattern, captures }) => ({ - pattern, - captures: formatCaptures(tree, captures), - })); -} - -function formatCaptures(tree, captures) { - return captures.map((c) => { - const node = c.node; - delete c.node; - c.text = tree.getText(node); - return c; - }); -} diff --git a/jest-tests/test2.test.js b/jest-tests/test2.test.js deleted file mode 100644 index 399f6dc7..00000000 --- a/jest-tests/test2.test.js +++ /dev/null @@ -1,18 +0,0 @@ -const Parser = require(".."); -const constants = require("./constants"); -const Javascript = require("tree-sitter-javascript"); -const jsParser = new Parser(); -jsParser.setLanguage(Javascript); - -describe("Jest test 1", () => { - it("should work", () => { - const code = jsParser.parse(constants.INPUT) - // Due to the race condition arising from Jest's worker pool, - // code.rootNode is null if the native extension hasn't finished - // loading. In this case, we skip the test. - if (code.rootNode) { - const output = code.rootNode.toString() - expect(output).toBe(constants.OUTPUT); - } - }) -}) diff --git a/jest-tests/test3.test.js b/jest-tests/test3.test.js deleted file mode 100644 index d405814c..00000000 --- a/jest-tests/test3.test.js +++ /dev/null @@ -1,18 +0,0 @@ -const Parser = require(".."); -const constants = require("./constants"); -const Javascript = require("tree-sitter-javascript"); -const jsParser = new Parser(); -jsParser.setLanguage(Javascript); - -describe("Jest test 1 duplicate", () => { - it("should work", () => { - const code = jsParser.parse(constants.INPUT) - // Due to the race condition arising from Jest's worker pool, - // code.rootNode is null if the native extension hasn't finished - // loading. In this case, we skip the test. - if (code.rootNode) { - const output = code.rootNode.toString() - expect(output).toBe(constants.OUTPUT); - } - }) -}) From a463ed10ce47f5ed067f2e029863e2598e93ef60 Mon Sep 17 00:00:00 2001 From: Amaan Qureshi Date: Mon, 4 Mar 2024 12:40:19 -0500 Subject: [PATCH 7/8] ci: bump actions and drop node 14+16 --- .github/workflows/build.yml | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8d1c24cd..8779f94f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,27 +21,22 @@ jobs: matrix: os: - windows-2019 - - macos-latest + - macos-14 - ubuntu-20.04 node: - - 14 - - 16 - 18 - 20 fail-fast: false name: Testing Node ${{ matrix.node }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: true fetch-depth: 0 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} - - uses: actions/setup-python@v4 - with: - python-version: '3.10' - run: npm install - run: npm test @@ -52,11 +47,11 @@ jobs: runs-on: ubuntu-20.04 needs: test steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: true fetch-depth: 0 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 20 registry-url: https://registry.npmjs.org @@ -70,18 +65,18 @@ jobs: matrix: os: - windows-2019 - - macos-latest + - macos-14 - ubuntu-20.04 fail-fast: false name: Prebuild for ${{ matrix.os }} runs-on: ${{ matrix.os }} needs: publish steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: true fetch-depth: 0 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 20 - run: npm install From b413646df7f771e1be34cdef5556d8b76cb73afd Mon Sep 17 00:00:00 2001 From: Amaan Qureshi Date: Fri, 8 Mar 2024 00:46:52 -0500 Subject: [PATCH 8/8] refactor!: make `is*` and `has*` methods properties --- index.js | 50 +++++++++++++++++++++++----------------------- test/node_test.js | 33 +++++++++++++++--------------- test/tree_test.js | 2 +- vendor/tree-sitter | 2 +- 4 files changed, 44 insertions(+), 43 deletions(-) diff --git a/index.js b/index.js index 83511997..d6ae4088 100644 --- a/index.js +++ b/index.js @@ -104,11 +104,36 @@ class SyntaxNode { return NodeMethods.grammarName(this.tree); } + get isExtra() { + marshalNode(this); + return NodeMethods.isExtra(this.tree); + } + get isNamed() { marshalNode(this); return NodeMethods.isNamed(this.tree); } + get isMissing() { + marshalNode(this); + return NodeMethods.isMissing(this.tree); + } + + get hasChanges() { + marshalNode(this); + return NodeMethods.hasChanges(this.tree); + } + + get hasError() { + marshalNode(this); + return NodeMethods.hasError(this.tree); + } + + get isError() { + marshalNode(this); + return NodeMethods.isError(this.tree); + } + get text() { return this.tree.getText(this); } @@ -215,31 +240,6 @@ class SyntaxNode { return NodeMethods.descendantCount(this.tree); } - hasChanges() { - marshalNode(this); - return NodeMethods.hasChanges(this.tree); - } - - hasError() { - marshalNode(this); - return NodeMethods.hasError(this.tree); - } - - isMissing() { - marshalNode(this); - return NodeMethods.isMissing(this.tree); - } - - isExtra() { - marshalNode(this); - return NodeMethods.isExtra(this.tree); - } - - isError() { - marshalNode(this); - return NodeMethods.isError(this.tree); - } - toString() { marshalNode(this); return NodeMethods.toString(this.tree); diff --git a/test/node_test.js b/test/node_test.js index 408fd70c..b1b62954 100644 --- a/test/node_test.js +++ b/test/node_test.js @@ -621,7 +621,7 @@ describe("Node", () => { }); }); - describe(".hasError()", () => { + describe(".hasError", () => { it("returns true if the node contains an error", () => { const tree = parser.parse("1 + 2 * * 3"); const node = tree.rootNode; @@ -631,14 +631,14 @@ describe("Node", () => { ); const sum = node.firstChild.firstChild; - assert(sum.hasError()); - assert(!sum.children[0].hasError()); - assert(!sum.children[1].hasError()); - assert(sum.children[2].hasError()); + assert(sum.hasError); + assert(!sum.children[0].hasError); + assert(!sum.children[1].hasError); + assert(sum.children[2].hasError); }); }); - describe(".isMissing()", () => { + describe(".isMissing)", () => { it("returns true if the node is missing from the source and was inserted via error recovery", () => { const tree = parser.parse("(2 ||)"); const node = tree.rootNode; @@ -649,14 +649,14 @@ describe("Node", () => { 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()); + assert(sum.hasError); + assert(!sum.children[0].isMissing); + assert(!sum.children[1].isMissing); + assert(sum.children[2].isMissing); }); }); - describe(".isExtra()", () => { + describe(".isExtra", () => { it("returns true if the node is an extra node like comments", () => { const tree = parser.parse("foo(/* hi */);"); const node = tree.rootNode; @@ -664,8 +664,8 @@ describe("Node", () => { assert.equal(node.type, "program"); assert.equal(comment_node.type, "comment"); - assert(!node.isExtra()); - assert(comment_node.isExtra()); + assert(!node.isExtra); + assert(comment_node.isExtra); }); }); @@ -907,6 +907,10 @@ describe("Node", () => { "type", "typeId", "isNamed", + "isMissing", + "hasChanges", + "hasError", + "isError", "text", "startPosition", "endPosition", @@ -931,9 +935,6 @@ describe("Node", () => { } const methods = [ - "hasChanges", - "hasError", - "isMissing", "toString", "walk", // these take arguments but the "this" check happens first diff --git a/test/tree_test.js b/test/tree_test.js index 5c836d50..e4915233 100644 --- a/test/tree_test.js +++ b/test/tree_test.js @@ -579,7 +579,7 @@ function assertCursorState(cursor, params) { const node = cursor.currentNode assert.equal(node.type, params.nodeType); assert.equal(node.isNamed, params.nodeIsNamed); - assert.equal(node.isMissing(), params.nodeIsMissing); + assert.equal(node.isMissing, params.nodeIsMissing); assert.deepEqual(node.startPosition, params.startPosition); assert.deepEqual(node.endPosition, params.endPosition); assert.deepEqual(node.startIndex, params.startIndex); diff --git a/vendor/tree-sitter b/vendor/tree-sitter index f8c41f74..c070c927 160000 --- a/vendor/tree-sitter +++ b/vendor/tree-sitter @@ -1 +1 @@ -Subproject commit f8c41f74f8c1bda0fdd82c07357ae601b6b2f62e +Subproject commit c070c92722c943d7eb6215dc1a97c833a30bc3e5