From cca47b365d779ccd95a987b97661842a236b2263 Mon Sep 17 00:00:00 2001 From: leo9001 Date: Fri, 13 Jun 2025 15:32:04 +0800 Subject: [PATCH] Multi-file search of getClassHierarchies Issue:https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICEQW9 Signed-off-by: leo9001 --- ets2panda/bindings/native/src/lsp.cpp | 46 ++- .../bindings/src/Es2pandaNativeModule.ts | 4 + ets2panda/bindings/src/lspNode.ts | 2 +- ets2panda/bindings/src/lsp_helper.ts | 52 ++- ets2panda/lsp/include/api.h | 5 +- ets2panda/lsp/include/class_hierarchies.h | 4 +- ets2panda/lsp/src/api.cpp | 12 +- ets2panda/lsp/src/class_hierarchy.cpp | 383 +++++++++--------- .../test/unit/lsp/class_hierarchys_test.cpp | 163 +++++++- 9 files changed, 430 insertions(+), 241 deletions(-) diff --git a/ets2panda/bindings/native/src/lsp.cpp b/ets2panda/bindings/native/src/lsp.cpp index a64d6b0215..60e984f94a 100644 --- a/ets2panda/bindings/native/src/lsp.cpp +++ b/ets2panda/bindings/native/src/lsp.cpp @@ -114,19 +114,19 @@ KNativePointer impl_getDisplayNameFromPropertyInfo(KNativePointer infoPtr) } TS_INTEROP_1(getDisplayNameFromPropertyInfo, KNativePointer, KNativePointer) -KNativePointer impl_getStartFromPropertyInfo(KNativePointer infoPtr) +KInt impl_getStartFromPropertyInfo(KNativePointer infoPtr) { auto info = reinterpret_cast(infoPtr); - return new std::size_t(info->start); + return info->start; } -TS_INTEROP_1(getStartFromPropertyInfo, KNativePointer, KNativePointer) +TS_INTEROP_1(getStartFromPropertyInfo, KInt, KNativePointer) -KNativePointer impl_getEndFromPropertyInfo(KNativePointer infoPtr) +KInt impl_getEndFromPropertyInfo(KNativePointer infoPtr) { auto info = reinterpret_cast(infoPtr); - return new std::size_t(info->end); + return info->end; } -TS_INTEROP_1(getEndFromPropertyInfo, KNativePointer, KNativePointer) +TS_INTEROP_1(getEndFromPropertyInfo, KInt, KNativePointer) KNativePointer impl_getSyntacticDiagnostics(KNativePointer context) { @@ -778,14 +778,28 @@ KInt impl_getAliasScriptElementKind(KNativePointer context, KInt position) } TS_INTEROP_2(getAliasScriptElementKind, KInt, KNativePointer, KInt) +KNativePointer impl_pushBackToNativeContextVector(KNativePointer context, KNativePointer contextList, KBoolean isNew) +{ + auto contextPtr = reinterpret_cast(context); + if (isNew != 0) { + auto *newVector = new std::vector(); + newVector->push_back(contextPtr); + return newVector; + } + auto contextVector = reinterpret_cast *>(contextList); + contextVector->push_back(contextPtr); + return contextVector; +} +TS_INTEROP_3(pushBackToNativeContextVector, KNativePointer, KNativePointer, KNativePointer, KBoolean) + KNativePointer impl_getClassHierarchies(KNativePointer context, KStringPtr &fileNamePtr, KInt pos) { LSPAPI const *ctx = GetImpl(); if (ctx == nullptr) { return nullptr; } - auto infos = - ctx->getClassHierarchiesImpl(reinterpret_cast(context), GetStringCopy(fileNamePtr), pos); + auto *contextlist = reinterpret_cast *>(context); + auto infos = ctx->getClassHierarchiesImpl(contextlist, GetStringCopy(fileNamePtr), pos); std::vector ptrs; ptrs.reserve(infos.size()); for (auto &info : infos) { @@ -797,10 +811,10 @@ TS_INTEROP_3(getClassHierarchies, KNativePointer, KNativePointer, KStringPtr, KI KNativePointer impl_getClassHierarchyList(KNativePointer infosPtr) { - auto *infos = reinterpret_cast *>(infosPtr); + auto *infos = reinterpret_cast *>(infosPtr); std::vector infoPtrList; for (auto &info : *infos) { - infoPtrList.push_back(new ark::es2panda::lsp::ClassHierarchyItemInfo(info)); + infoPtrList.push_back(info); } return new std::vector(infoPtrList); } @@ -834,9 +848,8 @@ KNativePointer impl_getOverriddenFromClassHierarchyItemInfo(KNativePointer infoP auto &overridden = info->overridden; std::vector overriddenPtrList; overriddenPtrList.reserve(overridden.size()); - size_t idx = 0; for (auto &details : overridden) { - overriddenPtrList[idx++] = new ark::es2panda::lsp::ClassRelationDetails(details); + overriddenPtrList.push_back(new ark::es2panda::lsp::ClassRelationDetails(details)); } return new std::vector(std::move(overriddenPtrList)); } @@ -848,9 +861,8 @@ KNativePointer impl_getOverridingFromClassHierarchyItemInfo(KNativePointer infoP auto &overriding = info->overriding; std::vector overridingPtrList; overridingPtrList.reserve(overriding.size()); - size_t idx = 0; for (auto &details : overriding) { - overridingPtrList[idx++] = new ark::es2panda::lsp::ClassRelationDetails(details); + overridingPtrList.push_back(new ark::es2panda::lsp::ClassRelationDetails(details)); } return new std::vector(std::move(overridingPtrList)); } @@ -862,9 +874,8 @@ KNativePointer impl_getImplementedFromClassHierarchyItemInfo(KNativePointer info auto implemented = info->implemented; std::vector implementedPtrList; implementedPtrList.reserve(implemented.size()); - size_t idx = 0; for (auto &details : implemented) { - implementedPtrList[idx++] = new ark::es2panda::lsp::ClassRelationDetails(details); + implementedPtrList.push_back(new ark::es2panda::lsp::ClassRelationDetails(details)); } return new std::vector(std::move(implementedPtrList)); } @@ -876,9 +887,8 @@ KNativePointer impl_getImplementingFromClassHierarchyItemInfo(KNativePointer inf auto implementing = info->implementing; std::vector implementingPtrList; implementingPtrList.reserve(implementing.size()); - size_t idx = 0; for (auto &details : implementing) { - implementingPtrList[idx++] = new ark::es2panda::lsp::ClassRelationDetails(details); + implementingPtrList.push_back(new ark::es2panda::lsp::ClassRelationDetails(details)); } return new std::vector(std::move(implementingPtrList)); } diff --git a/ets2panda/bindings/src/Es2pandaNativeModule.ts b/ets2panda/bindings/src/Es2pandaNativeModule.ts index 955a70e095..24994fe666 100644 --- a/ets2panda/bindings/src/Es2pandaNativeModule.ts +++ b/ets2panda/bindings/src/Es2pandaNativeModule.ts @@ -395,6 +395,10 @@ export class Es2pandaNativeModule { throw new Error('Not implemented'); } + _pushBackToNativeContextVector(context: KNativePointer, contextList: KNativePointer, isNew: KBoolean): KPtr { + throw new Error('Not implemented'); + } + _getClassHierarchyList(ptr: KNativePointer): KPtr { throw new Error('Not implemented'); } diff --git a/ets2panda/bindings/src/lspNode.ts b/ets2panda/bindings/src/lspNode.ts index e294dcee92..f5265a570d 100644 --- a/ets2panda/bindings/src/lspNode.ts +++ b/ets2panda/bindings/src/lspNode.ts @@ -310,7 +310,7 @@ export class FieldListProperty extends LspNode { this.modifierKinds = new NativePtrDecoder() .decode(global.es2panda._getModifierKindsFromPropertyInfo(peer)) .map((elPeer: KNativePointer) => { - return new String(elPeer); + return new String(unpackString(elPeer)); }); this.displayName = unpackString(global.es2panda._getDisplayNameFromPropertyInfo(peer)); this.start = global.es2panda._getStartFromPropertyInfo(peer); diff --git a/ets2panda/bindings/src/lsp_helper.ts b/ets2panda/bindings/src/lsp_helper.ts index 2995d1ef79..62958892b1 100644 --- a/ets2panda/bindings/src/lsp_helper.ts +++ b/ets2panda/bindings/src/lsp_helper.ts @@ -377,8 +377,8 @@ export class Lsp { let arktsconfig = this.fileNameToArktsconfig[filePath]; let ets2pandaCmd = ets2pandaCmdPrefix.concat(arktsconfig); let localCfg = lspDriverHelper.createCfg(ets2pandaCmd, filePath, this.pandaLibPath); - const source = this.getFileContent(filePath).replace(/\r\n/g, '\n'); - let localCtx = lspDriverHelper.createCtx(source, filePath, localCfg, this.globalContextPtr); + const source = this.getFileSource(filePath); + let localCtx = lspDriverHelper.createCtx(source, filePath, localCfg); PluginDriver.getInstance().getPluginContext().setContextPtr(localCtx); lspDriverHelper.proceedToState(localCtx, Es2pandaContextState.ES2PANDA_STATE_PARSED); PluginDriver.getInstance().runPluginHook(PluginHook.PARSED); @@ -396,8 +396,8 @@ export class Lsp { let arktsconfig = this.fileNameToArktsconfig[filePath]; let ets2pandaCmd = ets2pandaCmdPrefix.concat(arktsconfig); let localCfg = lspDriverHelper.createCfg(ets2pandaCmd, filePath, this.pandaLibPath); - const source = this.getFileContent(filePath).replace(/\r\n/g, '\n'); - let localCtx = lspDriverHelper.createCtx(source, filePath, localCfg, this.globalContextPtr); + const source = this.getFileSource(filePath); + let localCtx = lspDriverHelper.createCtx(source, filePath, localCfg); PluginDriver.getInstance().getPluginContext().setContextPtr(localCtx); lspDriverHelper.proceedToState(localCtx, Es2pandaContextState.ES2PANDA_STATE_PARSED); PluginDriver.getInstance().runPluginHook(PluginHook.PARSED); @@ -410,21 +410,45 @@ export class Lsp { } getClassHierarchies(filename: String, offset: number): LspClassHierarchies { + let contextList = []; let lspDriverHelper = new LspDriverHelper(); - let filePath = path.resolve(filename.valueOf()); - let arktsconfig = this.fileNameToArktsconfig[filePath]; + let localFilePath = path.resolve(filename.valueOf()); + let arktsconfig = this.fileNameToArktsconfig[localFilePath]; let ets2pandaCmd = ets2pandaCmdPrefix.concat(arktsconfig); - let localCfg = lspDriverHelper.createCfg(ets2pandaCmd, filePath, this.pandaLibPath); - const source = this.getFileContent(filePath).replace(/\r\n/g, '\n'); - let localCtx = lspDriverHelper.createCtx(source, filePath, localCfg, this.globalContextPtr); + let localCfg = lspDriverHelper.createCfg(ets2pandaCmd, localFilePath, this.pandaLibPath); + const source = this.getFileSource(localFilePath); + let localCtx = lspDriverHelper.createCtx(source, localFilePath, localCfg); PluginDriver.getInstance().getPluginContext().setContextPtr(localCtx); lspDriverHelper.proceedToState(localCtx, Es2pandaContextState.ES2PANDA_STATE_PARSED); PluginDriver.getInstance().runPluginHook(PluginHook.PARSED); lspDriverHelper.proceedToState(localCtx, Es2pandaContextState.ES2PANDA_STATE_CHECKED); - let ptr = global.es2panda._getClassHierarchies(localCtx, filename, offset); + contextList.push({ ctx: localCtx, cfg: localCfg }); + let nativeContextList = global.es2panda._pushBackToNativeContextVector(localCtx, localCtx, 1); + let moduleName = path.basename(path.dirname(arktsconfig)); + let buildConfig = this.moduleToBuildConfig[moduleName]; + for (let i = 0; i < buildConfig.compileFiles.length; i++) { + let filePath = path.resolve(buildConfig.compileFiles[i]); + if (localFilePath === filePath) { + continue; + } + let arktsconfig = this.fileNameToArktsconfig[filePath]; + let ets2pandaCmd = ets2pandaCmdPrefix.concat(arktsconfig); + let searchCfg = lspDriverHelper.createCfg(ets2pandaCmd, filePath, this.pandaLibPath); + const source = this.getFileSource(filePath); + let searchCtx = lspDriverHelper.createCtx(source, filePath, searchCfg); + PluginDriver.getInstance().getPluginContext().setContextPtr(searchCtx); + lspDriverHelper.proceedToState(searchCtx, Es2pandaContextState.ES2PANDA_STATE_PARSED); + PluginDriver.getInstance().runPluginHook(PluginHook.PARSED); + lspDriverHelper.proceedToState(searchCtx, Es2pandaContextState.ES2PANDA_STATE_CHECKED); + contextList.push({ ctx: searchCtx, cfg: searchCfg }); + global.es2panda._pushBackToNativeContextVector(searchCtx, nativeContextList, 0); + } + let ptr = global.es2panda._getClassHierarchies(nativeContextList, filename, offset); PluginDriver.getInstance().runPluginHook(PluginHook.CLEAN); - lspDriverHelper.destroyContext(localCtx); - lspDriverHelper.destroyConfig(localCfg); + for (const { ctx, cfg } of contextList) { + lspDriverHelper.destroyContext(ctx); + lspDriverHelper.destroyConfig(cfg); + } return new LspClassHierarchies(ptr); } @@ -438,8 +462,8 @@ export class Lsp { let arktsconfig = this.fileNameToArktsconfig[filePath]; let ets2pandaCmd = ets2pandaCmdPrefix.concat(arktsconfig); let localCfg = lspDriverHelper.createCfg(ets2pandaCmd, filePath, this.pandaLibPath); - const source = this.getFileContent(filePath).replace(/\r\n/g, '\n'); - let localCtx = lspDriverHelper.createCtx(source, filePath, localCfg, this.globalContextPtr); + const source = this.getFileSource(filePath); + let localCtx = lspDriverHelper.createCtx(source, filePath, localCfg); PluginDriver.getInstance().getPluginContext().setContextPtr(localCtx); lspDriverHelper.proceedToState(localCtx, Es2pandaContextState.ES2PANDA_STATE_PARSED); PluginDriver.getInstance().runPluginHook(PluginHook.PARSED); diff --git a/ets2panda/lsp/include/api.h b/ets2panda/lsp/include/api.h index bd47d74f9d..3d5da72db3 100644 --- a/ets2panda/lsp/include/api.h +++ b/ets2panda/lsp/include/api.h @@ -499,9 +499,8 @@ typedef struct LSPAPI { ark::es2panda::lsp::CompletionEntryKind (*getAliasScriptElementKind)(es2panda_Context *context, size_t position); References (*getFileReferences)(char const *fileName, es2panda_Context *context, bool isPackageModule); DeclInfo (*getDeclInfo)(es2panda_Context *context, size_t position); - std::vector (*getClassHierarchiesImpl)(es2panda_Context *context, - const char *fileName, - size_t pos); + std::vector (*getClassHierarchiesImpl)( + std::vector *contextList, const char *fileName, size_t pos); bool (*getSafeDeleteInfo)(es2panda_Context *context, size_t position, const char *path); References (*getReferencesAtPosition)(es2panda_Context *context, DeclInfo *declInfo); es2panda_AstNode *(*getPrecedingToken)(es2panda_Context *context, const size_t pos); diff --git a/ets2panda/lsp/include/class_hierarchies.h b/ets2panda/lsp/include/class_hierarchies.h index a4856d2060..73c06a69f0 100644 --- a/ets2panda/lsp/include/class_hierarchies.h +++ b/ets2panda/lsp/include/class_hierarchies.h @@ -55,8 +55,8 @@ struct ClassHierarchyItemInfo { * @param fileName Source file name. * @param pos Character offset in source code. */ -std::vector GetClassHierarchiesImpl(es2panda_Context *context, const std::string &fileName, - size_t pos); +std::vector GetClassHierarchiesImpl(std::vector *contextList, + const std::string &fileName, size_t pos); } // namespace ark::es2panda::lsp #endif \ No newline at end of file diff --git a/ets2panda/lsp/src/api.cpp b/ets2panda/lsp/src/api.cpp index 7e28b5d5ba..09369c4fb9 100644 --- a/ets2panda/lsp/src/api.cpp +++ b/ets2panda/lsp/src/api.cpp @@ -110,11 +110,15 @@ DeclInfo GetDeclInfo(es2panda_Context *context, size_t position) return result; } -std::vector GetClassHierarchies(es2panda_Context *context, const char *fileName, size_t pos) +std::vector GetClassHierarchies(std::vector *contextList, + const char *fileName, size_t pos) { - auto ctx = reinterpret_cast(context); - SetPhaseManager(ctx->phaseManager); - return GetClassHierarchiesImpl(context, std::string(fileName), pos); + auto *ctxList = reinterpret_cast *>(contextList); + for (auto *context : *ctxList) { + auto ctx = reinterpret_cast(context); + SetPhaseManager(ctx->phaseManager); + } + return GetClassHierarchiesImpl(contextList, std::string(fileName), pos); } bool GetSafeDeleteInfo(es2panda_Context *context, size_t position, const char *path) diff --git a/ets2panda/lsp/src/class_hierarchy.cpp b/ets2panda/lsp/src/class_hierarchy.cpp index 368a3ac5a5..0d7462903a 100644 --- a/ets2panda/lsp/src/class_hierarchy.cpp +++ b/ets2panda/lsp/src/class_hierarchy.cpp @@ -307,6 +307,156 @@ TypeHierarchiesInfo GetTypeHierarchiesImpl(es2panda_Context *context, size_t pos return result; } +/** + * @brief Helper function to get directly implemented interfaces + * @param node - Class definition node + * @return Vector of directly implemented interface nodes + */ +std::vector GetImplements(ir::AstNode *node) +{ + std::vector result {}; + if (node == nullptr) { + return result; + } + auto classDefinition = node->AsClassDefinition(); + auto implements = classDefinition->Implements(); + for (auto implement : implements) { + auto partNode = GetIdentifierFromTSInterfaceHeritage(implement); + if (partNode == nullptr || !partNode->IsIdentifier()) { + continue; + } + auto interfaceDecl = compiler::DeclarationFromIdentifier(partNode->AsIdentifier()); + if (interfaceDecl->IsTSInterfaceDeclaration()) { + result.push_back(interfaceDecl->AsTSInterfaceDeclaration()); + } + } + return result; +} + +/** + * @brief (查找当前类的子类,或者当前接口的子接口) Finds direct descendants (subclasses or subinterfaces) of + * the given node + * @param context - Compiler context containing program AST + * @param node - Class or interface declaration node to find descendants for + * @return Vector of direct descendant nodes (subclasses or subinterfaces) + */ +std::vector FindDirectDescendants(es2panda_Context *context, const ir::AstNode *node) +{ + std::vector result; + if (context == nullptr) { + return result; + } + auto ctx = reinterpret_cast(context); + if (ctx->parserProgram == nullptr || ctx->parserProgram->Ast() == nullptr) { + return result; + } + auto astNode = reinterpret_cast(ctx->parserProgram->Ast()); + astNode->IterateRecursively([node, &result](ir::AstNode *child) { + if (child == nullptr || (!child->IsClassDeclaration() && !child->IsTSInterfaceDeclaration())) { + return; + } + auto name = GetHierarchyDeclarationName(child); + if (name.empty()) { + return; + } + auto fileName = GetHierarchyDeclarationFileName(child); + if (fileName.empty()) { + return; + } + if (!IsChildNode(child, node)) { + return; + } + result.emplace_back(child); + }); + return result; +} + +/** + * @brief (查找当前接口的实现类) Finds classes that directly implement the given interface + * @param context - Compiler context containing program AST + * @param node - Interface declaration node + * @return Vector of class nodes that directly implement the interface + */ +std::vector FindDirectImplementingClasses(es2panda_Context *context, const ir::AstNode *node) +{ + std::vector result; + if (context == nullptr) { + return result; + } + auto ctx = reinterpret_cast(context); + if (ctx->parserProgram == nullptr || ctx->parserProgram->Ast() == nullptr) { + return result; + } + if (!node->IsTSInterfaceDeclaration()) { + return result; + } + auto astNode = reinterpret_cast(ctx->parserProgram->Ast()); + astNode->IterateRecursively([node, &result](ir::AstNode *child) { + if (child == nullptr || !child->IsClassDeclaration()) { + return; + } + auto name = GetHierarchyDeclarationName(child); + if (name.empty()) { + return; + } + auto fileName = GetHierarchyDeclarationFileName(child); + if (fileName.empty()) { + return; + } + auto impls = GetImplements(child->AsClassDeclaration()->Definition()); + for (auto impl : impls) { + if (GetIdentifierName(const_cast(impl)) == + GetIdentifierName(const_cast(node)) && + impl->Start().index == node->Start().index && + GetHierarchyDeclarationFileName(impl) == GetHierarchyDeclarationFileName(node)) { + result.emplace_back(child); + return; + } + } + }); + return result; +} + +/** + * + * @brief 如果当前node是接口类型,查找有哪些接口是继承或者间接的继承当前的接口;如果当前node是类,查找当前类的子类 + * 以及间接的实现类 + * Recursively collects all descendant nodes (subclasses/subinterfaces) + * @param contextList - List of compiler contexts to search + * @param node - Starting class/interface declaration node + * @param result - Output vector to store all descendant nodes + */ +void CollectAllDescendants(std::vector *contextList, const ir::AstNode *node, + std::vector &result) +{ + for (auto context : *contextList) { + auto rs = FindDirectDescendants(context, node); + result.insert(result.end(), rs.begin(), rs.end()); + for (auto subNode : rs) { + CollectAllDescendants(contextList, subNode, result); + } + } +} + +/** + * @brief 查找当前接口的实现类,或者间接的实现类 + * Recursively collects all implementing classes for an interface + * @param contextList - List of compiler contexts to search + * @param node - Starting interface declaration node + * @param result - Output vector to store implementing classes + */ +void CollectAllImplementingClasses(std::vector *contextList, const ir::AstNode *node, + std::vector &result) +{ + for (auto context : *contextList) { + auto rs = FindDirectImplementingClasses(context, node); + result.insert(result.end(), rs.begin(), rs.end()); + for (auto subNode : rs) { + CollectAllDescendants(contextList, subNode, result); + } + } +} + /** * @brief (查找当前类的父类) Find immediate superclass of current class node * @param node - current class node declaration @@ -343,7 +493,8 @@ ir::AstNode *GetClassDirectSuperClass(ir::AstNode *node) * @param node - Current class declaration node * @return Vector of superclass nodes in inheritance order */ -std::vector GetClassSuperClasses([[maybe_unused]] es2panda_Context *context, ir::AstNode *node) +std::vector GetClassSuperClasses([[maybe_unused]] std::vector *contextList, + ir::AstNode *node) { std::vector res; @@ -360,74 +511,16 @@ std::vector GetClassSuperClasses([[maybe_unused]] es2panda_Contex return res; } -/** - * @brief (查找当前类的子类) Find immediate subclass of current class node - * @param program - Pointer to the program AST - * @param node - Current class declaration node - * @return Set of direct subclass nodes - */ -std::unordered_set GetClassDirectSubClasses(ark::es2panda::parser::Program *program, ir::AstNode *node) -{ - std::unordered_set res; - - if (node == nullptr) { - return res; - } - if (!node->IsClassDeclaration()) { - return res; - } - auto rootNode = program->Ast(); - if (rootNode == nullptr) { - return res; - } - auto statements = rootNode->Statements(); - for (auto statement : statements) { - if (!statement->IsClassDeclaration()) { - continue; - } - if (GetClassDirectSuperClass(statement) == node) { - res.insert(statement); - } - } - - return res; -} - /** * @brief (2. 查找当前类的(所有)子类) Find all possible implementing classes of current class node * @param context - Compiler context containing program AST * @param node - Current class declaration node * @return Vector of all subclass nodes */ -std::vector GetClassSubClasses(es2panda_Context *context, ir::AstNode *node) +std::vector GetClassSubClasses(std::vector *contextList, ir::AstNode *node) { - auto pctx = reinterpret_cast(context); - auto program = pctx->parserProgram; std::vector result; - std::unordered_set resultSet; - std::unordered_set gottenSet; - std::unordered_set superClasses; - superClasses.insert(node); - std::unordered_set directSubClasses; - - do { - directSubClasses.clear(); - for (auto superClass : superClasses) { - auto it = gottenSet.find(superClass); - if (it == gottenSet.end()) { - auto subClasses = GetClassDirectSubClasses(program, superClass); - directSubClasses.insert(subClasses.begin(), subClasses.end()); - } - } - gottenSet.insert(superClasses.begin(), superClasses.end()); - if (!directSubClasses.empty()) { - resultSet.insert(directSubClasses.begin(), directSubClasses.end()); - superClasses.clear(); - superClasses.insert(directSubClasses.begin(), directSubClasses.end()); - } - } while (!directSubClasses.empty()); - - result.insert(result.end(), resultSet.begin(), resultSet.end()); + CollectAllDescendants(contextList, node, result); return result; } @@ -495,7 +588,8 @@ std::unordered_set GetInterfaceDirectExtendedInterfaces(ir::AstNo * @param node - Current class declaration node * @return Vector of implemented interface nodes */ -std::vector GetClassImplementedInterfaces([[maybe_unused]] es2panda_Context *context, ir::AstNode *node) +std::vector GetClassImplementedInterfaces([[maybe_unused]] std::vector *contextList, + ir::AstNode *node) { std::vector result; std::unordered_set resultSet; @@ -547,7 +641,8 @@ std::vector GetClassImplementedInterfaces([[maybe_unused]] es2pan * @param node - Current interface node * @return Vector of ancestor interface nodes */ -std::vector GetInterfaceSuperInterfaces([[maybe_unused]] es2panda_Context *context, ir::AstNode *node) +std::vector GetInterfaceSuperInterfaces([[maybe_unused]] std::vector *contextList, + ir::AstNode *node) { std::vector superInterfaces {}; std::unordered_set visited; @@ -576,121 +671,17 @@ std::vector GetInterfaceSuperInterfaces([[maybe_unused]] es2panda return superInterfaces; } -/** - * @brief Helper function to get directly implemented interfaces - * @param node - Class definition node - * @return Vector of directly implemented interface nodes - */ -std::vector GetImplements(ir::AstNode *node) -{ - std::vector result {}; - if (node == nullptr) { - return result; - } - auto classDefinition = node->AsClassDefinition(); - auto implements = classDefinition->Implements(); - for (auto implement : implements) { - auto partNode = GetIdentifierFromTSInterfaceHeritage(implement); - if (partNode == nullptr || !partNode->IsIdentifier()) { - continue; - } - auto interfaceDecl = compiler::DeclarationFromIdentifier(partNode->AsIdentifier()); - if (interfaceDecl->IsTSInterfaceDeclaration()) { - result.push_back(interfaceDecl->AsTSInterfaceDeclaration()); - } - } - return result; -} - -std::vector GetInterfaceOrClasses(es2panda_Context *context, ir::AstNode *node, bool isInterfaceMode) -{ - std::vector result; - std::unordered_set parentSet; - parentSet.insert(node); - auto ctx = reinterpret_cast(context); - auto rootNode = ctx->parserProgram->Ast(); - if (rootNode == nullptr) { - return result; - } - for (auto statement : rootNode->Statements()) { - if (isInterfaceMode) { - // The current interface obtains the interface - if (!statement->IsTSInterfaceDeclaration()) { - continue; - } - auto child = statement->AsTSInterfaceDeclaration(); - auto extends = GetInterfaceDirectExtendedInterfaces(child); - bool isSubInterface = std::any_of(extends.begin(), extends.end(), - [&](ir::AstNode *base) { return parentSet.count(base) > 0; }); - if (isSubInterface) { - result.push_back(child); - } - } else { - // The current interface gets the subclass - if (!statement->IsClassDeclaration()) { - continue; - } - auto classDef = statement->AsClassDeclaration()->Definition(); - auto implements = GetImplements(classDef); - bool isImplement = std::any_of(implements.begin(), implements.end(), - [&](ir::AstNode *base) { return parentSet.count(base) > 0; }); - if (isImplement) { - result.push_back(classDef); - } - } - } - return result; -} - -void AddMissingExtends(std::vector &implementingClasses, const std::vector &extends) -{ - std::unordered_set existing(implementingClasses.begin(), implementingClasses.end()); - std::copy_if(extends.begin(), extends.end(), std::back_inserter(implementingClasses), - [&existing](ir::AstNode *node) { return existing.find(node) == existing.end(); }); -} - -/** - * @brief (通用函数,用于查找接口的子接口或实现类及其子类) Generic function to find sub-interfaces or implementing - * classes and their subclasses of an interface - * @param context - Compiler context containing program AST - * @param node - Current interface node - * @param isInterfaceMode - Flag to determine lookup mode (true for interfaces, false for classes) - * @return Vector of found nodes - */ -std::vector GetRelatedNodes(es2panda_Context *context, ir::AstNode *node, bool isInterfaceMode) -{ - std::vector result; - auto ctx = reinterpret_cast(context); - if (ctx->parserProgram->Ast() == nullptr) { - return result; - } - result = GetInterfaceOrClasses(context, node, isInterfaceMode); - for (size_t i = 0; i < result.size(); ++i) { - auto elem = result[i]; - if (!isInterfaceMode && elem->IsClassDefinition()) { - elem = elem->Parent(); - result[i] = elem; - } - std::vector extends; - if (isInterfaceMode) { - extends = GetRelatedNodes(context, elem, isInterfaceMode); - } else { - extends = GetClassSubClasses(context, elem); - } - AddMissingExtends(result, extends); - } - return result; -} - /** * @brief (5. 查找当前接口的(所有)子接口) Find all interfaces extended by current interface node * @param context - Compiler context containing program AST * @param node - Current interface node * @return Vector of descendant interface nodes */ -std::vector GetInterfaceSubInterfaces(es2panda_Context *context, ir::AstNode *node) +std::vector GetInterfaceSubInterfaces(std::vector *contextList, ir::AstNode *node) { - return GetRelatedNodes(context, node, true); + std::vector result; + CollectAllDescendants(contextList, node, result); + return result; } /** @@ -700,9 +691,12 @@ std::vector GetInterfaceSubInterfaces(es2panda_Context *context, * @param node - Current interface node * @return Vector of implementing class nodes */ -std::vector GetInterfaceImplementingClasses(es2panda_Context *context, ir::AstNode *node) +std::vector GetInterfaceImplementingClasses(std::vector *contextList, + ir::AstNode *node) { - return GetRelatedNodes(context, node, false); + std::vector result; + CollectAllImplementingClasses(contextList, node, result); + return result; } /** @@ -711,7 +705,7 @@ std::vector GetInterfaceImplementingClasses(es2panda_Context *con * @param node AST node (TSInterfaceDeclaration or ClassDeclaration). * @return Vector of AST nodes for class properties and filtered methods. */ -std::vector GetMembers([[maybe_unused]] es2panda_Context *context, ir::AstNode *node) +std::vector GetMembers([[maybe_unused]] std::vector *contextList, ir::AstNode *node) { std::vector res; std::vector body; @@ -760,7 +754,8 @@ bool IsMethodMatch(ir::AstNode *a, ir::AstNode *b) void CompareMembersCommon(const std::vector ¤tMembers, const std::vector &targetMembers, std::vector &matchedContainer, - std::vector &unmatchedContainer, const std::string &fileName) + std::vector &unmatchedContainer, + [[maybe_unused]] const std::string &fileName) { for (auto *targetMember : targetMembers) { auto kind = targetMember->IsMethodDefinition() ? ClassRelationKind::METHOD : ClassRelationKind::PROPERTY; @@ -768,12 +763,14 @@ void CompareMembersCommon(const std::vector ¤tMembers, for (auto *currentMember : currentMembers) { if (IsMethodMatch(currentMember, targetMember)) { isMatch = true; - matchedContainer.emplace_back(fileName, currentMember->Start().index, kind); + std::string fileNamePath(currentMember->Start().ToLocation().Program()->SourceFilePath()); + matchedContainer.emplace_back(fileNamePath, currentMember->Start().index, kind); break; } } if (!isMatch) { - unmatchedContainer.emplace_back(fileName, targetMember->Start().index, kind); + std::string fileNamePath(targetMember->Start().ToLocation().Program()->SourceFilePath()); + unmatchedContainer.emplace_back(fileNamePath, targetMember->Start().index, kind); } } } @@ -795,21 +792,21 @@ void CompareMembersForOverride(const std::vector ¤tMembers, struct ProcessItemsParams { const std::vector ¤tMembers; std::vector &result; - std::vector (*getListFunc)(es2panda_Context *, ir::AstNode *); + std::vector (*getListFunc)(std::vector *, ir::AstNode *); ClassRelationKind kind; bool swapCompareArgs; void (*compareFunc)(const std::vector &, const std::vector &, ClassHierarchyItemInfo &, const std::string &); }; -void ProcessItems(es2panda_Context *context, ir::AstNode *node, const std::string &fileName, +void ProcessItems(std::vector *contextList, ir::AstNode *node, const std::string &fileName, const ProcessItemsParams ¶ms) { - auto itemList = params.getListFunc(context, node); + auto itemList = params.getListFunc(contextList, node); for (auto *item : itemList) { std::string name = GetIdentifierName(item); ClassHierarchyItemInfo info(name, params.kind, item->Start().index); - auto itemMembers = GetMembers(context, item); + auto itemMembers = GetMembers(contextList, item); if (params.swapCompareArgs) { params.compareFunc(itemMembers, params.currentMembers, info, fileName); } else { @@ -819,38 +816,36 @@ void ProcessItems(es2panda_Context *context, ir::AstNode *node, const std::strin } } -std::vector GetClassHierarchiesImpl(es2panda_Context *context, const std::string &fileName, - size_t pos) +std::vector GetClassHierarchiesImpl(std::vector *contextList, + [[maybe_unused]] const std::string &fileName, size_t pos) { - auto pctx = reinterpret_cast(context); - auto program = pctx->parserProgram; std::vector result; - if (program == nullptr) { + if (contextList->empty()) { return result; } - auto classNode = GetTargetDeclarationNodeByPosition(context, pos); + auto classNode = GetTargetDeclarationNodeByPosition(contextList->at(0), pos); if (classNode == nullptr) { return result; } - std::vector currentMembers = GetMembers(context, classNode); + std::vector currentMembers = GetMembers(contextList, classNode); if (classNode->IsClassDeclaration()) { - ProcessItems(context, classNode, fileName, + ProcessItems(contextList, classNode, fileName, ProcessItemsParams {currentMembers, result, GetClassSuperClasses, ClassRelationKind::CLASS, false, CompareMembersForOverride}); - ProcessItems(context, classNode, fileName, + ProcessItems(contextList, classNode, fileName, ProcessItemsParams {currentMembers, result, GetClassImplementedInterfaces, ClassRelationKind::INTERFACE, false, CompareMembersForImplementation}); - ProcessItems(context, classNode, fileName, + ProcessItems(contextList, classNode, fileName, ProcessItemsParams {currentMembers, result, GetClassSubClasses, ClassRelationKind::CLASS, true, CompareMembersForOverride}); } else if (classNode->IsTSInterfaceDeclaration()) { - ProcessItems(context, classNode, fileName, + ProcessItems(contextList, classNode, fileName, ProcessItemsParams {currentMembers, result, GetInterfaceSuperInterfaces, ClassRelationKind::INTERFACE, false, CompareMembersForOverride}); - ProcessItems(context, classNode, fileName, + ProcessItems(contextList, classNode, fileName, ProcessItemsParams {currentMembers, result, GetInterfaceSubInterfaces, ClassRelationKind::INTERFACE, true, CompareMembersForOverride}); - ProcessItems(context, classNode, fileName, + ProcessItems(contextList, classNode, fileName, ProcessItemsParams {currentMembers, result, GetInterfaceImplementingClasses, ClassRelationKind::CLASS, false, CompareMembersForImplementation}); } diff --git a/ets2panda/test/unit/lsp/class_hierarchys_test.cpp b/ets2panda/test/unit/lsp/class_hierarchys_test.cpp index 5a4647aeb9..ac83082e08 100644 --- a/ets2panda/test/unit/lsp/class_hierarchys_test.cpp +++ b/ets2panda/test/unit/lsp/class_hierarchys_test.cpp @@ -132,8 +132,10 @@ TEST_F(LspClassHierarchiesTests, GetClassHierarchiesImpl_001) auto filePath = std::string {sourceFiles[sourceIndex].filePath}; auto fileContent = std::string {sourceFiles[sourceIndex].source}; auto context = initializer.CreateContext(filePath.c_str(), ES2PANDA_STATE_CHECKED, fileContent.c_str()); + auto *newVector = new std::vector(); + newVector->push_back(context); std::vector infos = - ark::es2panda::lsp::GetClassHierarchiesImpl(context, fileNames[sourceIndex], tokenOffset); + ark::es2panda::lsp::GetClassHierarchiesImpl(newVector, fileNames[sourceIndex], tokenOffset); ASSERT_EQ(expectedInfoCount, infos.size()); } @@ -155,8 +157,10 @@ TEST_F(LspClassHierarchiesTests, GetClassHierarchiesImpl_002) auto filePath = std::string {sourceFiles[sourceIndex].filePath}; auto fileContent = std::string {sourceFiles[sourceIndex].source}; auto context = initializer.CreateContext(filePath.c_str(), ES2PANDA_STATE_CHECKED, fileContent.c_str()); + auto *newVector = new std::vector(); + newVector->push_back(context); std::vector infos = - ark::es2panda::lsp::GetClassHierarchiesImpl(context, fileNames[sourceIndex], tokenOffset); + ark::es2panda::lsp::GetClassHierarchiesImpl(newVector, fileNames[sourceIndex], tokenOffset); ASSERT_EQ(expectedInfoCount, infos.size()); } @@ -178,8 +182,10 @@ TEST_F(LspClassHierarchiesTests, GetClassHierarchiesImpl_003) auto filePath = std::string {sourceFiles[sourceIndex].filePath}; auto fileContent = std::string {sourceFiles[sourceIndex].source}; auto context = initializer.CreateContext(filePath.c_str(), ES2PANDA_STATE_CHECKED, fileContent.c_str()); + auto *newVector = new std::vector(); + newVector->push_back(context); std::vector infos = - ark::es2panda::lsp::GetClassHierarchiesImpl(context, fileNames[sourceIndex], tokenOffset); + ark::es2panda::lsp::GetClassHierarchiesImpl(newVector, fileNames[sourceIndex], tokenOffset); ASSERT_EQ(expectedInfoCount, infos.size()); } @@ -201,8 +207,10 @@ TEST_F(LspClassHierarchiesTests, GetClassHierarchiesImpl_004) auto filePath = std::string {sourceFiles[sourceIndex].filePath}; auto fileContent = std::string {sourceFiles[sourceIndex].source}; auto context = initializer.CreateContext(filePath.c_str(), ES2PANDA_STATE_CHECKED, fileContent.c_str()); + auto *newVector = new std::vector(); + newVector->push_back(context); std::vector infos = - ark::es2panda::lsp::GetClassHierarchiesImpl(context, fileNames[sourceIndex], tokenOffset); + ark::es2panda::lsp::GetClassHierarchiesImpl(newVector, fileNames[sourceIndex], tokenOffset); ASSERT_EQ(expectedInfoCount, infos.size()); } @@ -224,8 +232,153 @@ TEST_F(LspClassHierarchiesTests, GetClassHierarchiesImpl_005) auto filePath = std::string {sourceFiles[sourceIndex].filePath}; auto fileContent = std::string {sourceFiles[sourceIndex].source}; auto context = initializer.CreateContext(filePath.c_str(), ES2PANDA_STATE_CHECKED, fileContent.c_str()); + auto *newVector = new std::vector(); + newVector->push_back(context); std::vector infos = - ark::es2panda::lsp::GetClassHierarchiesImpl(context, fileNames[sourceIndex], tokenOffset); + ark::es2panda::lsp::GetClassHierarchiesImpl(newVector, fileNames[sourceIndex], tokenOffset); + ASSERT_EQ(expectedInfoCount, infos.size()); +} + +std::vector fileContentsDisributed = { + R"( +import { Iaa } from "./GetClassHierarchiesImpl_006_Iaa" + +export abstract class Caa implements Iaa { + name: number = 1 + name1: number = 1 + name2: number = 1 + + hello(): void { + throw new Error("Method not implemented.") + } + + age: number = 0 + age2: number = 0 + age3: number = 0 + + abstract classHello(): void + + classHello1() { + return 1 + } +} +)", + R"( +import { Caa } from "./GetClassHierarchiesImpl_006_Caa" +import { Iaaa, Iaab } from "./GetClassHierarchiesImpl_006_Iaaa_Iaab" + +export class Caaa extends Caa implements Iaaa { + age: number = 0 + age4: number = 0 + age5: number = 0 + + classHello(): void { + throw new Error("Method not implemented.") + } +} + +export class Caab extends Caa implements Iaab { + name: number = 2 +} +)", + R"( +import { Caaa, Caab } from "./GetClassHierarchiesImpl_006_Caaa_Caab" +import { Iaa } from "./GetClassHierarchiesImpl_006_Iaa" +import { Iaaaa, Iaaab } from "./GetClassHierarchiesImpl_006_Iaaaa_Iaaab" +import { Iaaa, Iaab } from "./GetClassHierarchiesImpl_006_Iaaa_Iaab" + +export class Caaaa extends Caaa implements Iaaaa { + name: number = 3 +} + +export class Caaab extends Caaa implements Iaaab { + name: number = 4 +} + +export class Caaba extends Caab implements Iaaa { + name: number = 5 +} + +export class Caabb extends Caab implements Iaab { + name: number = 6 +} +)", + R"( +export interface Iaa { + name: number + name1: number + name2: number + + hello(): void + hello1(): void +} +)", + R"( +import { Iaa } from "./GetClassHierarchiesImpl_006_Iaa" + +export interface Iaaa extends Iaa { + name: number +} + +export interface Iaab extends Iaa { + name: number +} +)", + R"( +import { Iaa } from "./GetClassHierarchiesImpl_006_Iaa" +import { Iaaa } from "./GetClassHierarchiesImpl_006_Iaaa_Iaab" + +export interface Iaaaa extends Iaaa { + name: number +} + +export interface Iaaab extends Iaaa { + name: number +} +)", + R"( +import { Caabb } from "./GetClassHierarchiesImpl_006_Caaaa_Caaab_Caaba_Caabb"; +import { Iaab } from "./GetClassHierarchiesImpl_006_Iaaa_Iaab"; + +export class Test extends Caabb implements Iaab { + name: number = 7 +} +)"}; + +TEST_F(LspClassHierarchiesTests, GetClassHierarchiesImpl_006) +{ + constexpr size_t expectedInfoCount = 8; + constexpr size_t tokenOffset = 80; + + std::vector fileNames = {"GetClassHierarchiesImpl_006_Caa.ets", + "GetClassHierarchiesImpl_006_Caaa_Caab.ets", + "GetClassHierarchiesImpl_006_Caaaa_Caaab_Caaba_Caabb.ets", + "GetClassHierarchiesImpl_006_Iaa.ets", + "GetClassHierarchiesImpl_006_Iaaa_Iaab.ets", + "GetClassHierarchiesImpl_006_Iaaaa_Iaaab.ets", + "GetClassHierarchiesImpl_006_Test.ets"}; + auto filePaths = CreateTempFile(fileNames, fileContentsDisributed); + std::vector sourceFiles; + for (size_t i = 0; i < filePaths.size(); ++i) { + sourceFiles.emplace_back(filePaths[i], fileContentsDisributed[i]); + } + ASSERT_EQ(fileNames.size(), sourceFiles.size()); + auto *newVector = new std::vector(); + auto *initVector = new std::vector(); + for (size_t i = 0; i < filePaths.size(); ++i) { + auto initializer = new Initializer(); + auto filePath = std::string {sourceFiles[i].filePath}; + auto fileContent = std::string {sourceFiles[i].source}; + auto context = initializer->CreateContext(filePath.c_str(), ES2PANDA_STATE_CHECKED, fileContent.c_str()); + newVector->push_back(context); + initVector->push_back(initializer); + } + size_t sourceIndex = 0; + std::vector infos = + ark::es2panda::lsp::GetClassHierarchiesImpl(newVector, fileNames[sourceIndex], tokenOffset); + for (size_t i = 0; i < filePaths.size(); ++i) { + initVector->at(i)->DestroyContext(newVector->at(i)); + } ASSERT_EQ(expectedInfoCount, infos.size()); } // NOLINTEND -- Gitee