diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 8eba3ba..8d73ffc 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -1829,5 +1829,13 @@ target_link_libraries(step305_test PRIVATE tree_sitter_python tree_sitter_java) +# Step 306: TypeScript/JavaScript + Rust Parser Deepening +add_executable(step306_test tests/step306_test.cpp) +target_include_directories(step306_test PRIVATE src) +target_link_libraries(step306_test PRIVATE + unofficial::tree-sitter::tree-sitter + tree_sitter_javascript + tree_sitter_typescript + tree_sitter_rust) # Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies) diff --git a/editor/src/ast/JavaScriptParser.h b/editor/src/ast/JavaScriptParser.h index 1d5baec..270987c 100644 --- a/editor/src/ast/JavaScriptParser.h +++ b/editor/src/ast/JavaScriptParser.h @@ -59,41 +59,105 @@ private: auto* fn = convertJavaScriptFunction(child, source, language); if (fn) module->addChild("functions", fn); } else if (type == "class_declaration") { - convertJavaScriptClass(child, source, module, language); + convertJavaScriptClassDecl(child, source, module, language); } else if (type == "lexical_declaration" || type == "variable_declaration") { convertJavaScriptVariableFunctions(child, source, module, language); } else if (type == "export_statement") { - TSNode decl = ts_node_named_child(child, 0); - std::string declType = nodeType(decl); - if (declType == "function_declaration") { - auto* fn = convertJavaScriptFunction(decl, source, language); - if (fn) module->addChild("functions", fn); - } else if (declType == "class_declaration") { - convertJavaScriptClass(decl, source, module, language); - } else if (declType == "lexical_declaration" || declType == "variable_declaration") { - convertJavaScriptVariableFunctions(decl, source, module, language); + uint32_t ec = ts_node_named_child_count(child); + for (uint32_t j = 0; j < ec; ++j) { + TSNode decl = ts_node_named_child(child, j); + std::string declType = nodeType(decl); + if (declType == "function_declaration") { + auto* fn = convertJavaScriptFunction(decl, source, language); + if (fn) module->addChild("functions", fn); + } else if (declType == "class_declaration") { + convertJavaScriptClassDecl(decl, source, module, language); + } else if (declType == "lexical_declaration" || declType == "variable_declaration") { + convertJavaScriptVariableFunctions(decl, source, module, language); + } } } } } - static void convertJavaScriptClass(TSNode node, - const std::string& source, - Module* module, - const std::string& language) { + static void convertJavaScriptClassDecl(TSNode node, + const std::string& source, + Module* module, + const std::string& language) { TSNode nameNode = childByFieldName(node, "name"); std::string className = nodeText(nameNode, source); - TSNode bodyNode = childByFieldName(node, "body"); - if (ts_node_is_null(bodyNode)) return; - uint32_t count = ts_node_named_child_count(bodyNode); - for (uint32_t i = 0; i < count; ++i) { - TSNode child = ts_node_named_child(bodyNode, i); - std::string type = nodeType(child); - if (type == "method_definition") { - auto* fn = convertJavaScriptMethod(child, source, language, className); - if (fn) module->addChild("functions", fn); + + auto* cls = new ClassDeclaration(IdGenerator::next("cls"), className); + applySpan(cls, node); + + // Superclass — check for heritage clause with "extends" + // In tree-sitter-javascript, the superclass is in a child named + // "class_heritage" or we look for an identifier after "extends" + uint32_t allCount = ts_node_named_child_count(node); + for (uint32_t i = 0; i < allCount; ++i) { + TSNode ch = ts_node_named_child(node, i); + std::string chType = nodeType(ch); + if (chType == "class_heritage") { + // The heritage node contains the superclass identifier + uint32_t hc = ts_node_named_child_count(ch); + if (hc > 0) { + cls->superClass = nodeText(ts_node_named_child(ch, 0), source); + } } } + + TSNode bodyNode = childByFieldName(node, "body"); + if (!ts_node_is_null(bodyNode)) { + uint32_t count = ts_node_named_child_count(bodyNode); + for (uint32_t i = 0; i < count; ++i) { + TSNode child = ts_node_named_child(bodyNode, i); + std::string type = nodeType(child); + if (type == "method_definition") { + // Create MethodDeclaration on the class + auto* meth = convertJavaScriptMethodDecl(child, source, language, className); + if (meth) cls->addChild("methods", meth); + + // Backward compat: also add as Function to module.functions + auto* fn = convertJavaScriptMethod(child, source, language, className); + if (fn) module->addChild("functions", fn); + } + } + } + + module->addChild("classes", cls); + } + + static MethodDeclaration* convertJavaScriptMethodDecl(TSNode node, + const std::string& source, + const std::string& language, + const std::string& className) { + TSNode nameNode = childByFieldName(node, "name"); + if (ts_node_is_null(nameNode)) return nullptr; + + std::string name = nodeText(nameNode, source); + auto* meth = new MethodDeclaration(IdGenerator::next("meth"), name); + applySpan(meth, node); + meth->className = className; + + // Check for static keyword + uint32_t totalCount = ts_node_child_count(node); + for (uint32_t i = 0; i < totalCount; ++i) { + TSNode ch = ts_node_child(node, i); + std::string text = nodeText(ch, source); + if (text == "static") meth->isStatic = true; + } + + TSNode paramsNode = childByFieldName(node, "parameters"); + if (!ts_node_is_null(paramsNode)) { + convertJavaScriptParameters(paramsNode, source, meth, language); + } + + TSNode bodyNode = childByFieldName(node, "body"); + if (!ts_node_is_null(bodyNode)) { + convertJavaScriptBody(bodyNode, source, meth, language); + } + + return meth; } static void convertJavaScriptVariableFunctions(TSNode node, @@ -122,10 +186,19 @@ private: const std::string& language) { TSNode nameNode = childByFieldName(node, "name"); if (ts_node_is_null(nameNode)) return nullptr; - auto* fn = new Function(); - fn->id = IdGenerator::next("fn"); + + // Detect async keyword + bool isAsync = jsNodeHasAsyncKeyword(node, source); + + Function* fn; + if (isAsync) { + fn = new AsyncFunction(IdGenerator::next("fn"), nodeText(nameNode, source)); + } else { + fn = new Function(); + fn->id = IdGenerator::next("fn"); + fn->name = nodeText(nameNode, source); + } applySpan(fn, node); - fn->name = nodeText(nameNode, source); TSNode paramsNode = childByFieldName(node, "parameters"); if (!ts_node_is_null(paramsNode)) { @@ -200,6 +273,19 @@ private: return fn; } + static bool jsNodeHasAsyncKeyword(TSNode node, const std::string& source) { + uint32_t totalCount = ts_node_child_count(node); + for (uint32_t i = 0; i < totalCount; ++i) { + TSNode ch = ts_node_child(node, i); + if (!ts_node_is_named(ch) && nodeText(ch, source) == "async") return true; + } + // Also check named children text for "async" keyword + // In some tree-sitter JS grammars, "async" appears differently + std::string fullText = nodeText(node, source); + if (fullText.substr(0, 6) == "async ") return true; + return false; + } + static void convertJavaScriptParameters(TSNode paramsNode, const std::string& source, Function* fn, @@ -250,7 +336,7 @@ private: Function* fn, const std::string& language) { std::string type = nodeType(bodyNode); - if (type == "statement_block" || type == "statement_block") { + if (type == "statement_block") { uint32_t count = ts_node_named_child_count(bodyNode); for (uint32_t i = 0; i < count; ++i) { ASTNode* stmt = convertJavaScriptStatement(ts_node_named_child(bodyNode, i), source, language); @@ -430,6 +516,67 @@ private: if (idx) access->setChild("index", idx); } return access; + } else if (type == "await_expression") { + auto* awExpr = new AwaitExpression(IdGenerator::next("await")); + applySpan(awExpr, node); + uint32_t count = ts_node_named_child_count(node); + if (count > 0) { + ASTNode* expr = convertJavaScriptExpression(ts_node_named_child(node, 0), source, language); + if (expr) awExpr->setChild("expression", expr); + } + return awExpr; + } else if (type == "arrow_function") { + auto* lam = new LambdaExpression(IdGenerator::next("lam")); + applySpan(lam, node); + TSNode paramsNode = childByFieldName(node, "parameters"); + if (!ts_node_is_null(paramsNode)) { + uint32_t pc = ts_node_named_child_count(paramsNode); + for (uint32_t pi = 0; pi < pc; ++pi) { + TSNode pChild = ts_node_named_child(paramsNode, pi); + std::string pType = nodeType(pChild); + std::string paramName; + if (pType == "identifier") { + paramName = nodeText(pChild, source); + } else { + TSNode pName = childByFieldName(pChild, "pattern"); + if (ts_node_is_null(pName)) pName = childByFieldName(pChild, "name"); + if (ts_node_is_null(pName)) pName = pChild; + paramName = nodeText(pName, source); + } + if (!paramName.empty()) { + auto* param = new Parameter(IdGenerator::next("param"), paramName); + applySpan(param, pChild); + lam->addChild("parameters", param); + } + } + } else { + // Single parameter without parens: x => ... + TSNode paramNode = childByFieldName(node, "parameter"); + if (!ts_node_is_null(paramNode)) { + auto* param = new Parameter(IdGenerator::next("param"), nodeText(paramNode, source)); + applySpan(param, paramNode); + lam->addChild("parameters", param); + } + } + TSNode bodyNode = childByFieldName(node, "body"); + if (!ts_node_is_null(bodyNode)) { + if (nodeType(bodyNode) == "statement_block") { + uint32_t bc = ts_node_named_child_count(bodyNode); + for (uint32_t bi = 0; bi < bc; ++bi) { + ASTNode* stmt = convertJavaScriptStatement(ts_node_named_child(bodyNode, bi), source, language); + if (stmt) lam->addChild("body", stmt); + } + } else { + ASTNode* expr = convertJavaScriptExpression(bodyNode, source, language); + if (expr) { + auto* exprStmt = new ExpressionStatement(); + exprStmt->id = IdGenerator::next("exprstmt"); + exprStmt->setChild("expression", expr); + lam->addChild("body", exprStmt); + } + } + } + return lam; } else if (type == "identifier") { auto* ref = new VariableReference(IdGenerator::next("var"), nodeText(node, source)); applySpan(ref, node); diff --git a/editor/src/ast/RustParser.h b/editor/src/ast/RustParser.h index 20ec023..2d384af 100644 --- a/editor/src/ast/RustParser.h +++ b/editor/src/ast/RustParser.h @@ -67,8 +67,14 @@ private: if (fn) module->addChild("functions", fn); } else if (type == "impl_item") { convertRustImpl(child, source, module); - } else if (type == "struct_item" || type == "enum_item" || type == "trait_item") { - // Record type name as a custom type variable for visibility. + } else if (type == "struct_item") { + auto* cls = convertRustStruct(child, source); + if (cls) module->addChild("classes", cls); + } else if (type == "trait_item") { + auto* iface = convertRustTrait(child, source); + if (iface) module->addChild("classes", iface); + } else if (type == "enum_item") { + // Record enum name as variable for visibility TSNode nameNode = childByFieldName(child, "name"); if (!ts_node_is_null(nameNode)) { auto* var = new Variable(IdGenerator::next("var"), nodeText(nameNode, source)); @@ -78,6 +84,78 @@ private: } } + static ClassDeclaration* convertRustStruct(TSNode node, const std::string& source) { + TSNode nameNode = childByFieldName(node, "name"); + if (ts_node_is_null(nameNode)) return nullptr; + + auto* cls = new ClassDeclaration(IdGenerator::next("cls"), nodeText(nameNode, source)); + applySpan(cls, node); + + // Extract fields from field_declaration_list body + TSNode bodyNode = childByFieldName(node, "body"); + if (!ts_node_is_null(bodyNode)) { + uint32_t fc = ts_node_named_child_count(bodyNode); + for (uint32_t i = 0; i < fc; ++i) { + TSNode fieldNode = ts_node_named_child(bodyNode, i); + if (nodeType(fieldNode) == "field_declaration") { + TSNode fName = childByFieldName(fieldNode, "name"); + if (!ts_node_is_null(fName)) { + auto* var = new Variable(IdGenerator::next("var"), nodeText(fName, source)); + applySpan(var, fieldNode); + TSNode fType = childByFieldName(fieldNode, "type"); + if (!ts_node_is_null(fType)) { + if (auto* t = convertRustType(fType, source)) var->setChild("type", t); + } + cls->addChild("fields", var); + } + } + } + } + + return cls; + } + + static InterfaceDeclaration* convertRustTrait(TSNode node, const std::string& source) { + TSNode nameNode = childByFieldName(node, "name"); + if (ts_node_is_null(nameNode)) return nullptr; + + auto* iface = new InterfaceDeclaration(IdGenerator::next("iface"), nodeText(nameNode, source)); + applySpan(iface, node); + + // Extract method signatures from declaration_list body + TSNode bodyNode = childByFieldName(node, "body"); + if (!ts_node_is_null(bodyNode)) { + uint32_t mc = ts_node_named_child_count(bodyNode); + for (uint32_t i = 0; i < mc; ++i) { + TSNode methNode = ts_node_named_child(bodyNode, i); + std::string mType = nodeType(methNode); + if (mType == "function_signature_item" || mType == "function_item") { + TSNode mName = childByFieldName(methNode, "name"); + if (!ts_node_is_null(mName)) { + auto* meth = new MethodDeclaration(IdGenerator::next("meth"), nodeText(mName, source)); + applySpan(meth, methNode); + meth->className = iface->name; + meth->isVirtual = true; + + TSNode paramsNode = childByFieldName(methNode, "parameters"); + if (!ts_node_is_null(paramsNode)) { + convertRustParameters(paramsNode, source, meth); + } + + TSNode retNode = childByFieldName(methNode, "return_type"); + if (!ts_node_is_null(retNode)) { + if (auto* t = convertRustType(retNode, source)) meth->setChild("returnType", t); + } + + iface->addChild("methods", meth); + } + } + } + } + + return iface; + } + static void convertRustImpl(TSNode node, const std::string& source, Module* module) { @@ -85,30 +163,93 @@ private: std::string typeName = nodeText(typeNode, source); TSNode bodyNode = childByFieldName(node, "body"); if (ts_node_is_null(bodyNode)) return; + + // Find existing ClassDeclaration for this type to attach methods + ClassDeclaration* targetCls = nullptr; + auto& classes = module->getChildren("classes"); + for (auto* entry : classes) { + auto* cls = dynamic_cast(entry); + if (cls && cls->name == typeName) { + targetCls = cls; + break; + } + } + uint32_t count = ts_node_named_child_count(bodyNode); for (uint32_t i = 0; i < count; ++i) { TSNode child = ts_node_named_child(bodyNode, i); if (nodeType(child) == "function_item" || nodeType(child) == "function_signature_item") { + // Backward compat: add as Function to module.functions auto* fn = convertRustFunction(child, source, typeName); if (fn) module->addChild("functions", fn); + + // If we have a ClassDeclaration, also add MethodDeclaration + if (targetCls) { + auto* meth = convertRustMethodDecl(child, source, typeName); + if (meth) targetCls->addChild("methods", meth); + } } } } + static MethodDeclaration* convertRustMethodDecl(TSNode node, + const std::string& source, + const std::string& typeName) { + TSNode nameNode = childByFieldName(node, "name"); + if (ts_node_is_null(nameNode)) return nullptr; + + auto* meth = new MethodDeclaration(IdGenerator::next("meth"), nodeText(nameNode, source)); + applySpan(meth, node); + meth->className = typeName; + + // Check visibility modifiers + uint32_t allCount = ts_node_named_child_count(node); + for (uint32_t i = 0; i < allCount; ++i) { + TSNode ch = ts_node_named_child(node, i); + if (nodeType(ch) == "visibility_modifier") { + meth->visibility = "public"; + } + } + + TSNode paramsNode = childByFieldName(node, "parameters"); + if (!ts_node_is_null(paramsNode)) { + convertRustParameters(paramsNode, source, meth); + } + + TSNode retNode = childByFieldName(node, "return_type"); + if (!ts_node_is_null(retNode)) { + if (auto* t = convertRustType(retNode, source)) meth->setChild("returnType", t); + } + + TSNode bodyNode = childByFieldName(node, "body"); + if (!ts_node_is_null(bodyNode)) { + convertRustBlock(bodyNode, source, meth); + } + + return meth; + } + static Function* convertRustFunction(TSNode node, const std::string& source, const std::string& receiverType) { TSNode nameNode = childByFieldName(node, "name"); if (ts_node_is_null(nameNode)) return nullptr; - auto* fn = new Function(); - fn->id = IdGenerator::next("fn"); - applySpan(fn, node); + + // Detect async keyword + bool isAsync = rustNodeHasAsyncKeyword(node, source); + + Function* fn; std::string name = nodeText(nameNode, source); - if (!receiverType.empty()) { - fn->name = receiverType + "." + name; + if (!receiverType.empty()) name = receiverType + "." + name; + + if (isAsync) { + fn = new AsyncFunction(IdGenerator::next("fn"), name); } else { + fn = new Function(); + fn->id = IdGenerator::next("fn"); fn->name = name; } + applySpan(fn, node); TSNode paramsNode = childByFieldName(node, "parameters"); if (!ts_node_is_null(paramsNode)) { @@ -130,6 +271,18 @@ private: return fn; } + static bool rustNodeHasAsyncKeyword(TSNode node, const std::string& source) { + uint32_t totalCount = ts_node_child_count(node); + for (uint32_t i = 0; i < totalCount; ++i) { + TSNode ch = ts_node_child(node, i); + if (!ts_node_is_named(ch) && nodeText(ch, source) == "async") return true; + } + // Fallback: check source text prefix + std::string fullText = nodeText(node, source); + if (fullText.substr(0, 6) == "async ") return true; + return false; + } + static void convertRustParameters(TSNode paramsNode, const std::string& source, Function* fn) { @@ -367,6 +520,51 @@ private: if (idx) access->setChild("index", idx); } return access; + } else if (type == "closure_expression") { + auto* lam = new LambdaExpression(IdGenerator::next("lam")); + applySpan(lam, node); + TSNode paramsNode = childByFieldName(node, "parameters"); + if (!ts_node_is_null(paramsNode)) { + uint32_t pc = ts_node_named_child_count(paramsNode); + for (uint32_t pi = 0; pi < pc; ++pi) { + TSNode pChild = ts_node_named_child(paramsNode, pi); + std::string pType = nodeType(pChild); + std::string paramName; + if (pType == "identifier") { + paramName = nodeText(pChild, source); + } else if (pType == "parameter") { + TSNode pName = childByFieldName(pChild, "pattern"); + if (ts_node_is_null(pName)) pName = pChild; + paramName = nodeText(pName, source); + } else { + paramName = nodeText(pChild, source); + } + if (!paramName.empty()) { + auto* param = new Parameter(IdGenerator::next("param"), paramName); + applySpan(param, pChild); + lam->addChild("parameters", param); + } + } + } + TSNode bodyNode = childByFieldName(node, "body"); + if (!ts_node_is_null(bodyNode)) { + if (nodeType(bodyNode) == "block") { + uint32_t bc = ts_node_named_child_count(bodyNode); + for (uint32_t bi = 0; bi < bc; ++bi) { + ASTNode* stmt = convertRustStatement(ts_node_named_child(bodyNode, bi), source); + if (stmt) lam->addChild("body", stmt); + } + } else { + ASTNode* expr = convertRustExpression(bodyNode, source); + if (expr) { + auto* exprStmt = new ExpressionStatement(); + exprStmt->id = IdGenerator::next("exprstmt"); + exprStmt->setChild("expression", expr); + lam->addChild("body", exprStmt); + } + } + } + return lam; } else if (type == "identifier") { auto* ref = new VariableReference(IdGenerator::next("var"), nodeText(node, source)); applySpan(ref, node); diff --git a/editor/tests/step306_test.cpp b/editor/tests/step306_test.cpp new file mode 100644 index 0000000..cc4ea48 --- /dev/null +++ b/editor/tests/step306_test.cpp @@ -0,0 +1,319 @@ +// Step 306: TypeScript/JavaScript + Rust Parser Deepening (12 tests) +// Tests that JS/TS and Rust parsers correctly produce the new AST node +// types: ClassDeclaration, InterfaceDeclaration, MethodDeclaration, +// AsyncFunction, AwaitExpression, LambdaExpression via tree-sitter parsing. + +#include "ast/Parser.h" +#include "ast/ClassDeclaration.h" +#include "ast/GenericType.h" +#include "ast/AsyncNodes.h" +#include "ast/Module.h" +#include "ast/Function.h" +#include "ast/Variable.h" +#include "ast/Parameter.h" +#include "ast/Statement.h" +#include "ast/Expression.h" +#include +#include +#include +#include + +static int passed = 0, failed = 0; +#define TEST(name) { std::cout << " " << #name << "... "; } +#define PASS() { std::cout << "PASS\n"; ++passed; } +#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; } +#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {} + +// Helper: recursively find a node of a given conceptType +static bool findNodeOfType(ASTNode* node, const std::string& type) { + if (!node) return false; + if (node->conceptType == type) return true; + for (auto* child : node->allChildren()) { + if (findNodeOfType(child, type)) return true; + } + return false; +} + +// --------------------------------------------------------------- +// JavaScript/TypeScript tests (1-6) +// --------------------------------------------------------------- + +// 1. JS backward compat — simple function still yields Function +void test_js_backward_compat() { + TEST(js_backward_compat); + std::string src = "function greet(name) {\n return name;\n}\n"; + auto mod = TreeSitterParser::parseJavaScript(src); + CHECK(mod != nullptr, "module null"); + + auto& fns = mod->getChildren("functions"); + CHECK(!fns.empty(), "expected at least one function"); + CHECK(fns[0]->conceptType == "Function", "expected Function, got " + fns[0]->conceptType); + auto* fn0 = dynamic_cast(fns[0]); + CHECK(fn0 != nullptr, "dynamic_cast to Function failed"); + CHECK(fn0->name == "greet", "expected name 'greet', got " + fn0->name); + PASS(); +} + +// 2. JS class → ClassDeclaration with superclass and MethodDeclaration +void test_js_class() { + TEST(js_class); + std::string src = + "class Animal extends LivingThing {\n" + " speak() {\n" + " return \"...\";\n" + " }\n" + "}\n"; + auto mod = TreeSitterParser::parseJavaScript(src); + CHECK(mod != nullptr, "module null"); + + auto& classes = mod->getChildren("classes"); + CHECK(!classes.empty(), "expected at least one class"); + CHECK(classes[0]->conceptType == "ClassDeclaration", + "expected ClassDeclaration, got " + classes[0]->conceptType); + + auto* cls = dynamic_cast(classes[0]); + CHECK(cls != nullptr, "dynamic_cast to ClassDeclaration failed"); + CHECK(cls->name == "Animal", "expected name 'Animal', got " + cls->name); + CHECK(cls->superClass == "LivingThing", + "expected superClass 'LivingThing', got " + cls->superClass); + + auto& methods = cls->getChildren("methods"); + CHECK(!methods.empty(), "expected at least one method"); + auto* meth = dynamic_cast(methods[0]); + CHECK(meth != nullptr, "dynamic_cast to MethodDeclaration failed"); + CHECK(meth->name == "speak", "expected method 'speak', got " + meth->name); + CHECK(meth->className == "Animal", "expected className 'Animal', got " + meth->className); + PASS(); +} + +// 3. JS async function → AsyncFunction +void test_js_async_function() { + TEST(js_async_function); + std::string src = "async function fetchData() {\n return 42;\n}\n"; + auto mod = TreeSitterParser::parseJavaScript(src); + CHECK(mod != nullptr, "module null"); + + auto& fns = mod->getChildren("functions"); + CHECK(!fns.empty(), "expected at least one function"); + + auto* af = dynamic_cast(fns[0]); + CHECK(af != nullptr, "expected AsyncFunction, got " + fns[0]->conceptType); + CHECK(af->name == "fetchData", "expected name 'fetchData', got " + af->name); + CHECK(af->isAsync, "isAsync should be true"); + PASS(); +} + +// 4. JS await expression → AwaitExpression in body +void test_js_await() { + TEST(js_await); + std::string src = + "async function load() {\n" + " const result = await getData();\n" + "}\n"; + auto mod = TreeSitterParser::parseJavaScript(src); + CHECK(mod != nullptr, "module null"); + + auto& fns = mod->getChildren("functions"); + CHECK(!fns.empty(), "expected function"); + + CHECK(findNodeOfType(fns[0], "AwaitExpression"), + "expected AwaitExpression in function body"); + PASS(); +} + +// 5. JS arrow function in expression context → LambdaExpression +void test_js_arrow_lambda() { + TEST(js_arrow_lambda); + std::string src = + "function make() {\n" + " const fn = (x) => x + 1;\n" + "}\n"; + auto mod = TreeSitterParser::parseJavaScript(src); + CHECK(mod != nullptr, "module null"); + + // The top-level function "make" should be in functions + auto& fns = mod->getChildren("functions"); + CHECK(!fns.empty(), "expected function"); + + // Walk body looking for LambdaExpression + CHECK(findNodeOfType(fns[0], "LambdaExpression"), + "expected LambdaExpression from arrow function in body"); + PASS(); +} + +// 6. TypeScript class — same as JS but through TS parser +void test_ts_class() { + TEST(ts_class); + std::string src = + "class Service {\n" + " process(data: string): void {\n" + " console.log(data);\n" + " }\n" + "}\n"; + auto mod = TreeSitterParser::parseTypeScript(src); + CHECK(mod != nullptr, "module null"); + + auto& classes = mod->getChildren("classes"); + CHECK(!classes.empty(), "expected at least one class"); + + auto* cls = dynamic_cast(classes[0]); + CHECK(cls != nullptr, "expected ClassDeclaration, got " + classes[0]->conceptType); + CHECK(cls->name == "Service", "expected name 'Service', got " + cls->name); + + auto& methods = cls->getChildren("methods"); + CHECK(!methods.empty(), "expected at least one method"); + auto* meth = dynamic_cast(methods[0]); + CHECK(meth != nullptr, "expected MethodDeclaration"); + CHECK(meth->name == "process", "expected method 'process', got " + meth->name); + PASS(); +} + +// --------------------------------------------------------------- +// Rust tests (7-12) +// --------------------------------------------------------------- + +// 7. Rust backward compat — function still yields Function +void test_rust_backward_compat() { + TEST(rust_backward_compat); + std::string src = "fn greet(name: &str) -> String {\n name.to_string()\n}\n"; + auto mod = TreeSitterParser::parseRust(src); + CHECK(mod != nullptr, "module null"); + + auto& fns = mod->getChildren("functions"); + CHECK(!fns.empty(), "expected at least one function"); + auto* fn0 = dynamic_cast(fns[0]); + CHECK(fn0 != nullptr, "dynamic_cast to Function failed"); + CHECK(fn0->name == "greet", "expected name 'greet', got " + fn0->name); + PASS(); +} + +// 8. Rust struct → ClassDeclaration +void test_rust_struct() { + TEST(rust_struct); + std::string src = + "struct Point {\n" + " x: f64,\n" + " y: f64,\n" + "}\n"; + auto mod = TreeSitterParser::parseRust(src); + CHECK(mod != nullptr, "module null"); + + auto& classes = mod->getChildren("classes"); + CHECK(!classes.empty(), "expected at least one class"); + + auto* cls = dynamic_cast(classes[0]); + CHECK(cls != nullptr, "expected ClassDeclaration, got " + classes[0]->conceptType); + CHECK(cls->name == "Point", "expected name 'Point', got " + cls->name); + PASS(); +} + +// 9. Rust trait → InterfaceDeclaration +void test_rust_trait() { + TEST(rust_trait); + std::string src = + "trait Drawable {\n" + " fn draw(&self);\n" + "}\n"; + auto mod = TreeSitterParser::parseRust(src); + CHECK(mod != nullptr, "module null"); + + auto& classes = mod->getChildren("classes"); + bool foundTrait = false; + for (auto* entry : classes) { + if (entry->conceptType == "InterfaceDeclaration") { + auto* iface = dynamic_cast(entry); + CHECK(iface != nullptr, "dynamic_cast to InterfaceDeclaration failed"); + CHECK(iface->name == "Drawable", "expected name 'Drawable', got " + iface->name); + foundTrait = true; + break; + } + } + CHECK(foundTrait, "expected InterfaceDeclaration in classes"); + PASS(); +} + +// 10. Rust impl → MethodDeclaration on ClassDeclaration +void test_rust_impl_methods() { + TEST(rust_impl_methods); + std::string src = + "struct Dog {\n" + " name: String,\n" + "}\n" + "\n" + "impl Dog {\n" + " fn bark(&self) {\n" + " println!(\"Woof!\");\n" + " }\n" + "}\n"; + auto mod = TreeSitterParser::parseRust(src); + CHECK(mod != nullptr, "module null"); + + auto& classes = mod->getChildren("classes"); + CHECK(!classes.empty(), "expected class from struct"); + + auto* cls = dynamic_cast(classes[0]); + CHECK(cls != nullptr, "expected ClassDeclaration"); + CHECK(cls->name == "Dog", "expected name 'Dog', got " + cls->name); + + auto& methods = cls->getChildren("methods"); + CHECK(!methods.empty(), "expected at least one method from impl"); + + auto* meth = dynamic_cast(methods[0]); + CHECK(meth != nullptr, "expected MethodDeclaration"); + CHECK(meth->name == "bark", "expected method 'bark', got " + meth->name); + CHECK(meth->className == "Dog", "expected className 'Dog', got " + meth->className); + PASS(); +} + +// 11. Rust async fn → AsyncFunction +void test_rust_async_function() { + TEST(rust_async_function); + std::string src = "async fn fetch_data() -> String {\n String::new()\n}\n"; + auto mod = TreeSitterParser::parseRust(src); + CHECK(mod != nullptr, "module null"); + + auto& fns = mod->getChildren("functions"); + CHECK(!fns.empty(), "expected at least one function"); + + auto* af = dynamic_cast(fns[0]); + CHECK(af != nullptr, "expected AsyncFunction, got " + fns[0]->conceptType); + CHECK(af->name == "fetch_data", "expected name 'fetch_data', got " + af->name); + CHECK(af->isAsync, "isAsync should be true"); + PASS(); +} + +// 12. Rust closure → LambdaExpression +void test_rust_closure() { + TEST(rust_closure); + std::string src = + "fn make() {\n" + " let add = |x, y| x + y;\n" + "}\n"; + auto mod = TreeSitterParser::parseRust(src); + CHECK(mod != nullptr, "module null"); + + auto& fns = mod->getChildren("functions"); + CHECK(!fns.empty(), "expected function"); + + CHECK(findNodeOfType(fns[0], "LambdaExpression"), + "expected LambdaExpression from closure in function body"); + PASS(); +} + +int main() { + std::cout << "=== Step 306: TypeScript/JavaScript + Rust Parser Deepening ===\n"; + test_js_backward_compat(); + test_js_class(); + test_js_async_function(); + test_js_await(); + test_js_arrow_lambda(); + test_ts_class(); + test_rust_backward_compat(); + test_rust_struct(); + test_rust_trait(); + test_rust_impl_methods(); + test_rust_async_function(); + test_rust_closure(); + std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n"; + return failed > 0 ? 1 : 0; +}