diff --git a/ets2panda/bindings/native/src/lsp.cpp b/ets2panda/bindings/native/src/lsp.cpp index 21152cd337a0025f35a20e875b96a23cea8e0077..a6f7d8b3b5a53ed612702fbe177fd486b7ec058b 100644 --- a/ets2panda/bindings/native/src/lsp.cpp +++ b/ets2panda/bindings/native/src/lsp.cpp @@ -1344,13 +1344,13 @@ KNativePointer impl_getLocationFromList(KNativePointer listPtr) } TS_INTEROP_1(getLocationFromList, KNativePointer, KNativePointer) -KBoolean impl_getSafeDeleteInfo(KNativePointer context, KInt position, KStringPtr &path) +KBoolean impl_getSafeDeleteInfo(KNativePointer context, KInt position) { LSPAPI const *ctx = GetImpl(); return static_cast( - ctx->getSafeDeleteInfo(reinterpret_cast(context), position, GetStringCopy(path))); + ctx->getSafeDeleteInfo(reinterpret_cast(context), position)); } -TS_INTEROP_3(getSafeDeleteInfo, KBoolean, KNativePointer, KInt, KStringPtr) +TS_INTEROP_2(getSafeDeleteInfo, KBoolean, KNativePointer, KInt) KNativePointer impl_toLineColumnOffset(KNativePointer context, KInt position) { diff --git a/ets2panda/bindings/src/Es2pandaNativeModule.ts b/ets2panda/bindings/src/Es2pandaNativeModule.ts index b998458325f31f4829c3734dedd57b3c397f48d1..0d49d9af62de52bd807c2d49de2e58fc8e5b74eb 100644 --- a/ets2panda/bindings/src/Es2pandaNativeModule.ts +++ b/ets2panda/bindings/src/Es2pandaNativeModule.ts @@ -730,7 +730,7 @@ export class Es2pandaNativeModule { throw new Error('Not implemented'); } - _getSafeDeleteInfo(context: KNativePointer, position: KInt, path: String): boolean { + _getSafeDeleteInfo(context: KNativePointer, position: KInt): boolean { throw new Error('Not implemented'); } diff --git a/ets2panda/bindings/src/lsp_helper.ts b/ets2panda/bindings/src/lsp_helper.ts index bdf10c82e921aac45e71d7346430cc95e94039ee..d9c22c298ad6a765e0c8f6af97b72db9723c987d 100644 --- a/ets2panda/bindings/src/lsp_helper.ts +++ b/ets2panda/bindings/src/lsp_helper.ts @@ -712,7 +712,7 @@ export class Lsp { lspDriverHelper.proceedToState(localCtx, Es2pandaContextState.ES2PANDA_STATE_PARSED); PluginDriver.getInstance().runPluginHook(PluginHook.PARSED); lspDriverHelper.proceedToState(localCtx, Es2pandaContextState.ES2PANDA_STATE_CHECKED); - let result = global.es2panda._getSafeDeleteInfo(localCtx, position, path.resolve(__dirname, '../../..')); + let result = global.es2panda._getSafeDeleteInfo(localCtx, position); PluginDriver.getInstance().runPluginHook(PluginHook.CLEAN); lspDriverHelper.destroyContext(localCtx); lspDriverHelper.destroyConfig(localCfg); diff --git a/ets2panda/lsp/include/api.h b/ets2panda/lsp/include/api.h index 75ded0e385c46553dc6deba2a2e794c6ffb7ff10..625929888bc3c9e157b824574ddb06deb2be89e1 100644 --- a/ets2panda/lsp/include/api.h +++ b/ets2panda/lsp/include/api.h @@ -503,7 +503,7 @@ typedef struct LSPAPI { std::vector (*getClassHierarchiesImpl)(es2panda_Context *context, const char *fileName, size_t pos); - bool (*getSafeDeleteInfo)(es2panda_Context *context, size_t position, const char *path); + bool (*getSafeDeleteInfo)(es2panda_Context *context, size_t position); References (*getReferencesAtPosition)(es2panda_Context *context, DeclInfo *declInfo); es2panda_AstNode *(*getPrecedingToken)(es2panda_Context *context, const size_t pos); std::string (*getCurrentTokenValue)(es2panda_Context *context, size_t position); diff --git a/ets2panda/lsp/include/get_safe_delete_info.h b/ets2panda/lsp/include/get_safe_delete_info.h index 8988c5b14ebceb072da62c45107eca78a37d8787..b1d9979cd67c56e786f39764e617771131c0af2b 100644 --- a/ets2panda/lsp/include/get_safe_delete_info.h +++ b/ets2panda/lsp/include/get_safe_delete_info.h @@ -17,6 +17,6 @@ #include "public/es2panda_lib.h" namespace ark::es2panda::lsp { // This function judge whether indicated part can be deleted. -bool GetSafeDeleteInfoImpl(es2panda_Context *context, size_t position, const std::string &path); +bool GetSafeDeleteInfoImpl(es2panda_Context *context, size_t position); } // 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 f13d00069e04238369006b46f0347811f81f132f..be3cb64c4adf070f51fce67addc93fe364a9a728 100644 --- a/ets2panda/lsp/src/api.cpp +++ b/ets2panda/lsp/src/api.cpp @@ -117,11 +117,11 @@ std::vector GetClassHierarchies(es2panda_Context *contex return GetClassHierarchiesImpl(context, std::string(fileName), pos); } -bool GetSafeDeleteInfo(es2panda_Context *context, size_t position, const char *path) +bool GetSafeDeleteInfo(es2panda_Context *context, size_t position) { auto ctx = reinterpret_cast(context); SetPhaseManager(ctx->phaseManager); - return GetSafeDeleteInfoImpl(context, position, path); + return GetSafeDeleteInfoImpl(context, position); } References GetReferencesAtPosition(es2panda_Context *context, DeclInfo *declInfo) diff --git a/ets2panda/lsp/src/get_safe_delete_info.cpp b/ets2panda/lsp/src/get_safe_delete_info.cpp index b21f5d949d6f9f28b0faa492eb356f6ba5d3a209..825cab7b183e92891b40b27405a539de7758e109 100644 --- a/ets2panda/lsp/src/get_safe_delete_info.cpp +++ b/ets2panda/lsp/src/get_safe_delete_info.cpp @@ -12,6 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + #include #include #include @@ -22,89 +23,119 @@ #include "public/public.h" #include "references.h" +namespace { +constexpr size_t BUILTIN_TYPE_COUNT = 9; +constexpr std::array BUILTIN_TYPES = { + "number", "string", "boolean", "void", "bigint", "never", "undefined", "null", "object"}; +} // namespace + namespace ark::es2panda::lsp { -bool NodeIsEligibleForSafeDelete(ir::AstNode *astNode) + +bool IsBuiltinTypeReference(ir::AstNode *node) { - if (astNode == nullptr) { + if (node == nullptr || node->Type() != ir::AstNodeType::IDENTIFIER) { return false; } - switch (astNode->Type()) { - case ir::AstNodeType::THIS_EXPRESSION: - case ir::AstNodeType::TS_CONSTRUCTOR_TYPE: - case ir::AstNodeType::IDENTIFIER: - return true; - default: - return false; + auto *parent = node->Parent(); + while (parent != nullptr) { + if (parent->Type() == ir::AstNodeType::ETS_TYPE_REFERENCE_PART || + parent->Type() == ir::AstNodeType::ETS_TYPE_REFERENCE) { + std::string nameStr(node->AsIdentifier()->Name()); + auto it = + std::find_if(BUILTIN_TYPES.begin(), BUILTIN_TYPES.end(), [&](const char *s) { return nameStr == s; }); + if (it != BUILTIN_TYPES.end()) { + return true; + } + } + parent = parent->Parent(); } + return false; } -DeclInfo GetDeclInfoCur(es2panda_Context *context, size_t position) +ir::AstNode *FindNearestDeletableDecl(ir::AstNode *node) { - DeclInfo result; - if (context == nullptr) { - return result; + ir::AstNode *cur = node; + while (cur != nullptr) { + if (cur->IsFunctionDeclaration() || cur->IsVariableDeclarator() || cur->IsClassProperty() || + cur->Type() == ir::AstNodeType::METHOD_DEFINITION || cur->Type() == ir::AstNodeType::CLASS_DECLARATION || + cur->IsTSTypeParameterDeclaration() || cur->IsImportDefaultSpecifier() || + cur->Type() == ir::AstNodeType::ETS_TYPE_REFERENCE_PART || cur->IsCallExpression() || + cur->IsETSImportDeclaration() || cur->IsImportSpecifier() || cur->IsBinaryExpression() || + cur->IsTSInterfaceDeclaration() || cur->IsETSTypeReferencePart() || cur->IsImportNamespaceSpecifier() || + cur->IsTSTypeAliasDeclaration() || cur->IsExpressionStatement() || cur->IsMemberExpression() || + cur->IsTypeofExpression()) { + return cur; + } + cur = cur->Parent(); } - auto astNode = GetTouchingToken(context, position, false); - auto declInfo = GetDeclInfoImpl(astNode); - result.fileName = std::get<0>(declInfo); - result.fileText = std::get<1>(declInfo); - return result; + return nullptr; } -// This function judge whether type is standard library file defined type. -bool IsLibrayFile(ir::AstNode *node, const std::string &path) +std::string NormalizeFilePath(const std::string &filePath) { - auto declInfo = GetDeclInfoImpl(node); - auto fileName = std::get<0>(declInfo); - if (fileName.empty()) { - return false; - } - if (fileName.find("ets1.2") != std::string::npos) { - return fileName.find(path) != std::string::npos; - } - return true; + std::string normPath = filePath; + std::transform(normPath.begin(), normPath.end(), normPath.begin(), ::tolower); + std::replace(normPath.begin(), normPath.end(), '\\', '/'); + return normPath; } -bool IsAllowToDeleteDeclaration(ir::AstNode *node, const std::string &path) +bool GetSafeDeleteInfoImpl(es2panda_Context *context, size_t position) { - return (node->IsETSModule() && node->AsETSModule()->IsNamespace()) || node->IsTSTypeParameterDeclaration() || - (node->Type() == ir::AstNodeType::IDENTIFIER && node->Parent()->IsTSModuleDeclaration()) || - IsLibrayFile(node, path) || node->IsArrowFunctionExpression() || node->IsETSStringLiteralType(); -} + auto astNode = GetTouchingToken(context, position, false); + if (astNode == nullptr) { + return false; + } -bool GetSafeDeleteInfoForNode(es2panda_Context *context, size_t position, const std::string &path) -{ - auto declInfoData = GetDeclInfoCur(context, position); - DeclInfoType declInfo = {declInfoData.fileName, declInfoData.fileText}; - std::vector nodes; + auto declInfo = GetDeclInfoImpl(astNode); + auto fileName = std::get<0>(declInfo); + + if (IsBuiltinTypeReference(astNode)) { + return false; + } - auto ctx = reinterpret_cast(context); - if (ctx->parserProgram == nullptr || ctx->parserProgram->Ast() == nullptr) { + astNode = FindNearestDeletableDecl(astNode); + if (astNode == nullptr) { return false; } - auto astNode = reinterpret_cast(ctx->parserProgram->Ast()); - astNode->IterateRecursively([declInfo, &nodes](ir::AstNode *child) { - auto info = GetDeclInfoImpl(child); - if (info == declInfo) { - nodes.push_back(child); + + // Distinguish a namespace from a class by checking whether the class body contains only the $init$ method and has + // no constructor. + if (astNode->Type() == ir::AstNodeType::CLASS_DECLARATION) { + auto *classDecl = static_cast(astNode); + const auto &body = classDecl->Definition()->Body(); + bool hasConstructor = false; + bool onlyInit = true; + for (auto *member : body) { + if (member->Type() == ir::AstNodeType::METHOD_DEFINITION) { + auto *method = static_cast(member); + util::StringView keyName = method->Key()->AsIdentifier()->Name(); + if (keyName == "constructor") { + hasConstructor = true; + } + if (keyName != "_$init$_") { + onlyInit = false; + } + } } - }); - std::vector filterNodes; - std::for_each(nodes.begin(), nodes.end(), [&filterNodes, path](ir::AstNode *node) { - if (IsAllowToDeleteDeclaration(node, path)) { - filterNodes.push_back(node); + if (!hasConstructor && onlyInit) { + return false; } - }); + } - return !filterNodes.empty(); -} + if (astNode->IsTSTypeParameterDeclaration()) { + return false; + } -bool GetSafeDeleteInfoImpl(es2panda_Context *context, size_t position, const std::string &path) -{ - auto astNode = GetTouchingToken(context, position, false); - if (NodeIsEligibleForSafeDelete(astNode)) { - return GetSafeDeleteInfoForNode(context, position, path); + std::string normFileName = NormalizeFilePath(fileName); + + if (!normFileName.empty() && normFileName.find("ets1.2/build-tools/ets2panda/lib/stdlib") != std::string::npos) { + return false; } - return false; + + if (!normFileName.empty() && normFileName.find("/sdk/") != std::string::npos) { + return false; + } + + return true; } } // namespace ark::es2panda::lsp diff --git a/ets2panda/test/unit/lsp/get_safe_delete_info_test.cpp b/ets2panda/test/unit/lsp/get_safe_delete_info_test.cpp index ac249130b62c587ccba3c2172546b7c598b1c238..327975daf28d9843b31127ed7897b610ef0ea278 100644 --- a/ets2panda/test/unit/lsp/get_safe_delete_info_test.cpp +++ b/ets2panda/test/unit/lsp/get_safe_delete_info_test.cpp @@ -22,31 +22,27 @@ #include "lsp/include/api.h" #include "public/es2panda_lib.h" -using ark::es2panda::lsp::Initializer; namespace { +using ark::es2panda::lsp::Initializer; class LspGetSafeDeleteInfoTest : public LSPAPITests {}; TEST_F(LspGetSafeDeleteInfoTest, GetSafeDeleteInfoCase1) { - using ark::es2panda::public_lib::Context; + std::vector files = {"get-safe-delete-info-case1.ets"}; + std::vector texts = {R"(function a(): number { return 1; } a())"}; + auto filePaths = CreateTempFile(files, texts); - std::vector fileNames = {"firstFile.ets", "secondFile.ets"}; - std::vector fileContents = { - "const greet = (name: string) => {\n" - "return 'Hello, ${name}!';\n};\n" - "export default greet;\n", - "import greet from \"./firstFile.ets\""}; + Initializer initializer; + es2panda_Context *ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED); + LSPAPI const *lspApi = GetImpl(); - auto filePaths = CreateTempFile(fileNames, fileContents); - const int fileIndex = 1; + constexpr size_t OFFSET = 16; + bool result = lspApi->getSafeDeleteInfo(ctx, OFFSET); + ASSERT_EQ(result, false); - Initializer initializer = Initializer(); - es2panda_Context *ctx = initializer.CreateContext(filePaths[fileIndex].c_str(), ES2PANDA_STATE_CHECKED); - ASSERT_EQ(ContextState(ctx), ES2PANDA_STATE_CHECKED); - LSPAPI const *lspApi = GetImpl(); - size_t const offset = 7; - bool result = lspApi->getSafeDeleteInfo(ctx, offset, ""); + constexpr size_t OFFSET2 = 35; + result = lspApi->getSafeDeleteInfo(ctx, OFFSET2); ASSERT_EQ(result, true); initializer.DestroyContext(ctx); @@ -54,27 +50,182 @@ TEST_F(LspGetSafeDeleteInfoTest, GetSafeDeleteInfoCase1) TEST_F(LspGetSafeDeleteInfoTest, GetSafeDeleteInfoCase2) { - Initializer initializer = Initializer(); - es2panda_Context *ctx = - initializer.CreateContext("get-safe-delete-info-case2.ets", ES2PANDA_STATE_CHECKED, "class A {\n\n}"); - ASSERT_EQ(ContextState(ctx), ES2PANDA_STATE_CHECKED); + std::vector files = {"get-safe-delete-info-case2.ets"}; + std::vector texts = {R"(export const PI = 3.1415926;)"}; + auto filePaths = CreateTempFile(files, texts); + + Initializer initializer; + es2panda_Context *ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED); LSPAPI const *lspApi = GetImpl(); - size_t const offset = 8; - bool result = lspApi->getSafeDeleteInfo(ctx, offset, ""); + + constexpr size_t OFFSET = 14; + bool result = lspApi->getSafeDeleteInfo(ctx, OFFSET); ASSERT_EQ(result, true); + initializer.DestroyContext(ctx); } TEST_F(LspGetSafeDeleteInfoTest, GetSafeDeleteInfoCase3) { - Initializer initializer = Initializer(); - es2panda_Context *ctx = initializer.CreateContext("get-safe-delete-info-case3.ets", ES2PANDA_STATE_CHECKED, - "let arr: Array;\n"); - ASSERT_EQ(ContextState(ctx), ES2PANDA_STATE_CHECKED); + std::vector files = {"get-safe-delete-info-case3.ets"}; + std::vector texts = {R"( + function setAlarmDefaultTime(alarmItem?: AlarmItem) { + let hour; + let minute; + })"}; + auto filePaths = CreateTempFile(files, texts); + + Initializer initializer; + es2panda_Context *ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED); LSPAPI const *lspApi = GetImpl(); - size_t const offset = 10; - bool result = lspApi->getSafeDeleteInfo(ctx, offset, "stdlib/escompat/Array.ets"); + + constexpr size_t OFFSET = 71; + bool result = lspApi->getSafeDeleteInfo(ctx, OFFSET); ASSERT_EQ(result, true); + + constexpr size_t OFFSET2 = 89; + result = lspApi->getSafeDeleteInfo(ctx, OFFSET2); + ASSERT_EQ(result, true); + + initializer.DestroyContext(ctx); +} + +TEST_F(LspGetSafeDeleteInfoTest, GetSafeDeleteInfoCase4) +{ + std::vector files = {"get-safe-delete-info-namespace.ets"}; + std::vector texts = {R"( + namespace Foo {} + class Foo2{} + )"}; + auto filePaths = CreateTempFile(files, texts); + + Initializer initializer; + es2panda_Context *ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED); + LSPAPI const *lspApi = GetImpl(); + + constexpr size_t OFFSET = 14; + bool result = lspApi->getSafeDeleteInfo(ctx, OFFSET); + ASSERT_EQ(result, false); + + constexpr size_t OFFSET2 = 31; + result = lspApi->getSafeDeleteInfo(ctx, OFFSET2); + ASSERT_EQ(result, true); + + initializer.DestroyContext(ctx); +} + +TEST_F(LspGetSafeDeleteInfoTest, GetSafeDeleteInfoCase5) +{ + std::vector files = {"get-safe-delete-info-typeparam.ets"}; + std::vector texts = {R"( + function foo(a: T) { return a; } + )"}; + auto filePaths = CreateTempFile(files, texts); + + Initializer initializer; + es2panda_Context *ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED); + + LSPAPI const *lspApi = GetImpl(); + constexpr size_t OFFSET = 17; + bool result = lspApi->getSafeDeleteInfo(ctx, OFFSET); + ASSERT_EQ(result, false); + + initializer.DestroyContext(ctx); +} + +TEST_F(LspGetSafeDeleteInfoTest, GetSafeDeleteInfoCase6) +{ + std::vector files = {"get-safe-delete-info-import.ets"}; + std::vector texts = {R"(import { foo } from "./mod";)"}; + auto filePaths = CreateTempFile(files, texts); + + Initializer initializer; + es2panda_Context *ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED); + LSPAPI const *lspApi = GetImpl(); + + constexpr size_t OFFSET = 9; + bool result = lspApi->getSafeDeleteInfo(ctx, OFFSET); + ASSERT_EQ(result, true); + + constexpr size_t OFFSET2 = 23; + result = lspApi->getSafeDeleteInfo(ctx, OFFSET2); + ASSERT_EQ(result, true); + + initializer.DestroyContext(ctx); +} + +TEST_F(LspGetSafeDeleteInfoTest, GetSafeDeleteInfoCase7) +{ + std::vector files = {"get-safe-delete-info-arrow.ets"}; + std::vector texts = {R"( + export const calcDistance = (time: number) => { + const ret = 0.5 * time * time; + return ret; + }; + )"}; + auto filePaths = CreateTempFile(files, texts); + + Initializer initializer; + es2panda_Context *ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED); + LSPAPI const *lspApi = GetImpl(); + + constexpr size_t OFFSET = 28; + bool result = lspApi->getSafeDeleteInfo(ctx, OFFSET); + ASSERT_EQ(result, true); + + initializer.DestroyContext(ctx); +} + +TEST_F(LspGetSafeDeleteInfoTest, GetSafeDeleteInfoCase8) +{ + std::vector files = {"get-safe-delete-info-interface.ets"}; + std::vector texts = {R"( + export interface VideoPlayer { + prepare(): Promise; + } + + function bar(parameter: VideoPlayer) { + parameter.prepare(); + } + )"}; + auto filePaths = CreateTempFile(files, texts); + + Initializer initializer; + es2panda_Context *ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED); + LSPAPI const *lspApi = GetImpl(); + + constexpr size_t OFFSET = 31; + bool result = lspApi->getSafeDeleteInfo(ctx, OFFSET); + ASSERT_EQ(result, true); + + constexpr size_t OFFSET2 = 143; + result = lspApi->getSafeDeleteInfo(ctx, OFFSET2); + ASSERT_EQ(result, true); + + initializer.DestroyContext(ctx); +} + +TEST_F(LspGetSafeDeleteInfoTest, GetSafeDeleteInfoCase9) +{ + std::vector files = {"get-safe-delete-info-importdefault.ets"}; + std::vector texts = {R"( + import BuildProfile from 'BuildProfile'; + BuildProfile.bundleName + )"}; + auto filePaths = CreateTempFile(files, texts); + + Initializer initializer; + es2panda_Context *ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED); + LSPAPI const *lspApi = GetImpl(); + + constexpr size_t OFFSET = 15; + bool result = lspApi->getSafeDeleteInfo(ctx, OFFSET); + ASSERT_EQ(result, true); + + constexpr size_t OFFSET2 = 52; + result = lspApi->getSafeDeleteInfo(ctx, OFFSET2); + ASSERT_EQ(result, true); + initializer.DestroyContext(ctx); } } // namespace