diff --git a/ets2panda/bindings/native/src/lsp.cpp b/ets2panda/bindings/native/src/lsp.cpp index 94e1722c30af933ddb07b006d7d9d5ec04066eb3..831c4b438a7716b751d1936ac7ee1cafdd52db07 100644 --- a/ets2panda/bindings/native/src/lsp.cpp +++ b/ets2panda/bindings/native/src/lsp.cpp @@ -1354,13 +1354,12 @@ 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))); + return static_cast(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 f1919ade1dd82e9bf9b76d0005646e507d4f895a..0d02950ff3654fb6c1a0e4f4f64795c8918089c8 100644 --- a/ets2panda/bindings/src/Es2pandaNativeModule.ts +++ b/ets2panda/bindings/src/Es2pandaNativeModule.ts @@ -734,7 +734,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 6e432ffd60434a5c2ebdaaf70ef8c82023af1519..3ccf2c7022ae352244cebd30948289d8d9f08495 100644 --- a/ets2panda/bindings/src/lsp_helper.ts +++ b/ets2panda/bindings/src/lsp_helper.ts @@ -736,7 +736,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 3e0e25e484631899e57a1875bc585de235e4b656..801f9f086c757c895131e0e563ce376811ef6b07 100644 --- a/ets2panda/lsp/include/api.h +++ b/ets2panda/lsp/include/api.h @@ -502,7 +502,7 @@ typedef struct LSPAPI { DeclInfo (*getDeclInfo)(es2panda_Context *context, size_t position); std::vector (*getClassHierarchiesImpl)( std::vector *contextList, 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 ea02eb3bd787274023844011baad2a73a421a7f1..205f7f3c05b21fbda57f33227dac1d9f8dc4e8ea 100644 --- a/ets2panda/lsp/src/api.cpp +++ b/ets2panda/lsp/src/api.cpp @@ -121,11 +121,11 @@ std::vector GetClassHierarchies(std::vector(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..ddeeb93ddee4413b46e33d058bf5db65b957aab3 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,129 @@ #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) +bool IsDeletableDecl(ir::AstNode *node) { - DeclInfo result; - if (context == nullptr) { - return result; - } - 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 node->IsFunctionDeclaration() || node->IsVariableDeclarator() || node->IsClassProperty() || + node->Type() == ir::AstNodeType::METHOD_DEFINITION || node->Type() == ir::AstNodeType::CLASS_DECLARATION || + node->IsTSTypeParameterDeclaration() || node->IsImportDefaultSpecifier() || + node->Type() == ir::AstNodeType::ETS_TYPE_REFERENCE_PART || node->IsCallExpression() || + node->IsETSImportDeclaration() || node->IsImportSpecifier() || node->IsBinaryExpression() || + node->IsTSInterfaceDeclaration() || node->IsETSTypeReferencePart() || node->IsImportNamespaceSpecifier() || + node->IsTSTypeAliasDeclaration() || node->IsExpressionStatement() || node->IsMemberExpression() || + node->IsTypeofExpression(); } -// This function judge whether type is standard library file defined type. -bool IsLibrayFile(ir::AstNode *node, const std::string &path) +ir::AstNode *FindNearestDeletableDecl(ir::AstNode *node) { - 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; + ir::AstNode *cur = node; + while (cur != nullptr) { + if (IsDeletableDecl(cur)) { + return cur; + } + cur = cur->Parent(); } - return true; + return nullptr; } -bool IsAllowToDeleteDeclaration(ir::AstNode *node, const std::string &path) +std::string NormalizeFilePath(const std::string &filePath) { - return (node->IsETSModule() && node->AsETSModule()->IsNamespace()) || node->IsTSTypeParameterDeclaration() || - (node->Type() == ir::AstNodeType::IDENTIFIER && node->Parent()->IsTSModuleDeclaration()) || - IsLibrayFile(node, path) || node->IsArrowFunctionExpression() || node->IsETSStringLiteralType(); + std::string normPath = filePath; + std::transform(normPath.begin(), normPath.end(), normPath.begin(), ::tolower); + std::replace(normPath.begin(), normPath.end(), '\\', '/'); + return normPath; } -bool GetSafeDeleteInfoForNode(es2panda_Context *context, size_t position, const std::string &path) +/** + * Distinguish a namespace from a class by checking whether the class body contains only the $init$ method and has + * no constructor + */ +bool IsNamespaceClass(ir::AstNode *astNode) { - auto declInfoData = GetDeclInfoCur(context, position); - DeclInfoType declInfo = {declInfoData.fileName, declInfoData.fileText}; - std::vector nodes; - - auto ctx = reinterpret_cast(context); - if (ctx->parserProgram == nullptr || ctx->parserProgram->Ast() == nullptr) { + if (astNode->Type() != ir::AstNodeType::CLASS_DECLARATION) { 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); - } - }); - std::vector filterNodes; - std::for_each(nodes.begin(), nodes.end(), [&filterNodes, path](ir::AstNode *node) { - if (IsAllowToDeleteDeclaration(node, path)) { - filterNodes.push_back(node); + 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; + } } - }); - - return !filterNodes.empty(); + } + return !hasConstructor && onlyInit; } -bool GetSafeDeleteInfoImpl(es2panda_Context *context, size_t position, const std::string &path) +bool GetSafeDeleteInfoImpl(es2panda_Context *context, size_t position) { auto astNode = GetTouchingToken(context, position, false); - if (NodeIsEligibleForSafeDelete(astNode)) { - return GetSafeDeleteInfoForNode(context, position, path); + if (astNode == nullptr) { + return false; } - return false; + + auto declInfo = GetDeclInfoImpl(astNode); + auto fileName = std::get<0>(declInfo); + std::string normFileName = NormalizeFilePath(fileName); + if (!normFileName.empty() && normFileName.find("ets1.2/build-tools/ets2panda/lib/stdlib") != std::string::npos) { + return false; + } + if (!normFileName.empty() && normFileName.find("/sdk/") != std::string::npos) { + return false; + } + + if (IsBuiltinTypeReference(astNode)) { + return false; + } + + astNode = FindNearestDeletableDecl(astNode); + if (astNode == nullptr) { + return false; + } + + if (IsNamespaceClass(astNode)) { + return false; + } + + if (astNode->IsTSTypeParameterDeclaration()) { + 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..6ffb8d648e28d531c5103b8754cb0c1cc7191643 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,29 @@ #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; + // NOLINTNEXTLINE(readability-identifier-naming,-warnings-as-errors) + 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, ""); + // NOLINTNEXTLINE(readability-identifier-naming,-warnings-as-errors) + constexpr size_t offsetT2 = 35; + result = lspApi->getSafeDeleteInfo(ctx, offsetT2); ASSERT_EQ(result, true); initializer.DestroyContext(ctx); @@ -54,27 +52,195 @@ 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, ""); + + // NOLINTNEXTLINE(readability-identifier-naming,-warnings-as-errors) + 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"); + + // NOLINTNEXTLINE(readability-identifier-naming,-warnings-as-errors) + constexpr size_t offset = 71; + bool result = lspApi->getSafeDeleteInfo(ctx, offset); ASSERT_EQ(result, true); + + // NOLINTNEXTLINE(readability-identifier-naming,-warnings-as-errors) + constexpr size_t offsetT2 = 89; + result = lspApi->getSafeDeleteInfo(ctx, offsetT2); + 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(); + + // NOLINTNEXTLINE(readability-identifier-naming,-warnings-as-errors) + constexpr size_t offset = 14; + bool result = lspApi->getSafeDeleteInfo(ctx, offset); + ASSERT_EQ(result, false); + + // NOLINTNEXTLINE(readability-identifier-naming,-warnings-as-errors) + constexpr size_t offsetT2 = 31; + result = lspApi->getSafeDeleteInfo(ctx, offsetT2); + 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(); + + // NOLINTNEXTLINE(readability-identifier-naming,-warnings-as-errors) + 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(); + + // NOLINTNEXTLINE(readability-identifier-naming,-warnings-as-errors) + constexpr size_t offset = 9; + bool result = lspApi->getSafeDeleteInfo(ctx, offset); + ASSERT_EQ(result, true); + + // NOLINTNEXTLINE(readability-identifier-naming,-warnings-as-errors) + constexpr size_t offsetT2 = 23; + result = lspApi->getSafeDeleteInfo(ctx, offsetT2); + 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(); + + // NOLINTNEXTLINE(readability-identifier-naming,-warnings-as-errors) + 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(); + + // NOLINTNEXTLINE(readability-identifier-naming,-warnings-as-errors) + constexpr size_t offset = 31; + bool result = lspApi->getSafeDeleteInfo(ctx, offset); + ASSERT_EQ(result, true); + + // NOLINTNEXTLINE(readability-identifier-naming,-warnings-as-errors) + constexpr size_t offsetT2 = 143; + result = lspApi->getSafeDeleteInfo(ctx, offsetT2); + 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(); + + // NOLINTNEXTLINE(readability-identifier-naming,-warnings-as-errors) + constexpr size_t offset = 15; + bool result = lspApi->getSafeDeleteInfo(ctx, offset); + ASSERT_EQ(result, true); + + // NOLINTNEXTLINE(readability-identifier-naming,-warnings-as-errors) + constexpr size_t offsetT2 = 52; + result = lspApi->getSafeDeleteInfo(ctx, offsetT2); + ASSERT_EQ(result, true); + initializer.DestroyContext(ctx); } } // namespace