From b82d66a4f6ba02be8fd6547b4dcb1c343310b0e6 Mon Sep 17 00:00:00 2001 From: dongchao Date: Sun, 6 Jul 2025 14:41:30 +0800 Subject: [PATCH 1/6] Change the enum in glue code in declgen_ets2ts Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICK9ZG Signed-off-by: dongchao Change-Id: I45d9d1957a037943d8eace6f84e83229b48e55ae --- ets2panda/declgen_ets2ts/declgenEts2Ts.cpp | 23 +++++++--------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/ets2panda/declgen_ets2ts/declgenEts2Ts.cpp b/ets2panda/declgen_ets2ts/declgenEts2Ts.cpp index 5b25c6f04f..bc9a500dd4 100644 --- a/ets2panda/declgen_ets2ts/declgenEts2Ts.cpp +++ b/ets2panda/declgen_ets2ts/declgenEts2Ts.cpp @@ -491,22 +491,18 @@ void TSDeclGen::GenLiteral(const ir::Literal *literal) const auto number = literal->AsNumberLiteral()->Number(); if (number.IsInt()) { OutDts(std::to_string(number.GetInt())); - OutTs(std::to_string(number.GetInt())); return; } if (number.IsLong()) { OutDts(std::to_string(number.GetLong())); - OutTs(std::to_string(number.GetLong())); return; } if (number.IsFloat()) { OutDts(std::to_string(number.GetFloat())); - OutTs(std::to_string(number.GetFloat())); return; } if (number.IsDouble()) { OutDts(std::to_string(number.GetDouble())); - OutTs(std::to_string(number.GetDouble())); return; } LogError(diagnostic::UNEXPECTED_NUMBER_LITERAL_TYPE, {}, literal->Start()); @@ -514,7 +510,6 @@ void TSDeclGen::GenLiteral(const ir::Literal *literal) const auto string = literal->AsStringLiteral()->ToString(); importSet_.insert(string); OutDts("\"" + string + "\""); - OutTs("\"" + string + "\""); } else if (literal->IsBooleanLiteral()) { OutDts(literal->AsBooleanLiteral()->ToString()); } else { @@ -1555,12 +1550,10 @@ void TSDeclGen::GenEnumDeclaration(const ir::ClassProperty *enumMember) ProcessIndent(); OutDts(GetKeyIdent(enumMember->Key())->Name()); - OutTs(GetKeyIdent(enumMember->Key())->Name()); const auto *init = originEnumMember->Init(); if (init != nullptr) { OutDts(" = "); - OutTs(" = "); if (!init->IsLiteral()) { LogError(diagnostic::NOT_LITERAL_ENUM_INITIALIZER, {}, init->Start()); } @@ -1568,8 +1561,6 @@ void TSDeclGen::GenEnumDeclaration(const ir::ClassProperty *enumMember) GenLiteral(init->AsLiteral()); } - OutTs(","); - OutEndlTs(); OutDts(","); OutEndlDts(); } @@ -1748,8 +1739,6 @@ void TSDeclGen::EmitClassDeclaration(const ir::ClassDefinition *classDef, const OutEndlTs(); } else if (classDef->IsEnumTransformed()) { EmitDeclarationPrefix(classDef, "enum ", className); - OutTs("export const enum ", className, " {"); - OutEndlTs(); } else if (classDef->IsFromStruct()) { EmitDeclarationPrefix(classDef, "struct ", className); } else if (classDef->IsAbstract()) { @@ -1783,9 +1772,9 @@ void TSDeclGen::GenPartName(std::string &partName) void TSDeclGen::ProcessIndent() { - if (state_.isInterfaceInNamespace) { + if (state_.isInterfaceInNamespace || state_.inEnum) { OutDts(GetIndent()); - } else if (classNode_.hasNestedClass || state_.inNamespace || state_.inEnum) { + } else if (classNode_.hasNestedClass || state_.inNamespace) { auto indent = GetIndent(); OutDts(indent); OutTs(indent); @@ -1945,7 +1934,7 @@ void TSDeclGen::GenClassDeclaration(const ir::ClassDeclaration *classDecl) } if (!state_.inGlobalClass && ShouldEmitDeclarationSymbol(classDef->Ident())) { HandleClassDeclarationTypeInfo(classDef, className); - if (!classDef->IsNamespaceTransformed() && !classDef->IsEnumTransformed()) { + if (!classDef->IsNamespaceTransformed()) { EmitClassGlueCode(classDef, className); } ProcessClassBody(classDef); @@ -1960,10 +1949,12 @@ void TSDeclGen::GenClassDeclaration(const ir::ClassDeclaration *classDecl) return; } ES2PANDA_ASSERT(classNode_.indentLevel != static_cast(-1)); - if (!state_.isClassInNamespace || state_.inEnum) { - state_.inEnum = false; + if (!state_.isClassInNamespace) { CloseClassBlock(false); } + if (state_.inEnum) { + state_.inEnum = false; + } } if (classDef->IsDefaultExported()) { OutDts("export default ", className, ";"); -- Gitee From 8f54511adbce51e03b7634711c28166cb7dd290f Mon Sep 17 00:00:00 2001 From: dongchao Date: Wed, 9 Jul 2025 14:42:43 +0800 Subject: [PATCH 2/6] Change any to ESObject in declgen_ets2ts Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICL1JU Signed-off-by: dongchao Change-Id: Icd708bb87052b8c9041b036f449fb883b4b3f273 --- ets2panda/declgen_ets2ts/declgenEts2Ts.cpp | 16 ++++++++++++---- .../declgen-ets2ts-runtime-ignored.txt | 1 + 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/ets2panda/declgen_ets2ts/declgenEts2Ts.cpp b/ets2panda/declgen_ets2ts/declgenEts2Ts.cpp index bc9a500dd4..0631225d50 100644 --- a/ets2panda/declgen_ets2ts/declgenEts2Ts.cpp +++ b/ets2panda/declgen_ets2ts/declgenEts2Ts.cpp @@ -448,6 +448,9 @@ bool TSDeclGen::HandleETSSpecificTypes(const checker::Type *checkerType) case checker::TypeFlag::ETS_TUPLE: GenTupleType(checkerType->AsETSTupleType()); return true; + case checker::TypeFlag::ETS_ANY: + OutDts("ESObject"); + return true; default: LogError(diagnostic::UNSUPPORTED_TYPE, {GetDebugTypeName(checkerType)}); } @@ -606,7 +609,7 @@ void TSDeclGen::ProcessFuncParameter(varbinder::LocalVariable *param) ProcessTypeAnnotationType(typeAnnotation, paramType); return; } - OutDts("any"); + OutDts("ESObject"); } void TSDeclGen::ProcessFuncParameters(const checker::Signature *sig) @@ -829,7 +832,7 @@ void TSDeclGen::GenObjectType(const checker::ETSObjectType *objectType) std::string typeStr = objectType->Name().Mutf8(); if (objectType->Name().Empty()) { LogWarning(diagnostic::EMPTY_TYPE_NAME); - OutDts("any"); + OutDts("ESObject"); } else { if (typeStr == "Exception" || typeStr == "NullPointerError") { OutDts("Error"); @@ -1393,6 +1396,9 @@ bool TSDeclGen::ProcessTypeAnnotationSpecificTypes(const checker::Type *checkerT case checker::TypeFlag::ETS_READONLY: OutDts(checkerType->ToString()); return true; + case checker::TypeFlag::ETS_ANY: + OutDts("ESObject"); + return true; default: return false; } @@ -1767,6 +1773,8 @@ void TSDeclGen::GenPartName(std::string &partName) partName = "bigint"; } else if (partName == "Exception" || partName == "NullPointerError") { partName = "Error"; + } else if (partName == "Any") { + partName = "ESObject"; } } @@ -2088,7 +2096,7 @@ void TSDeclGen::GenMethodSignature(const ir::MethodDefinition *methodDef, const if (methodDef->TsType() == nullptr) { LogWarning(diagnostic::UNTYPED_METHOD, {methodName}, methodIdent->Start()); - OutDts(": any"); + OutDts(": ESObject"); return; } if (methodDef->TsType()->IsETSFunctionType()) { @@ -2189,7 +2197,7 @@ void TSDeclGen::ProcessClassPropDeclaration(const ir::ClassProperty *classProp) OutDts(propName); OutDts(": "); if (!state_.inNamespace) { - classProp->IsStatic() ? OutDts("any") : GenType(classProp->TsType()); + classProp->IsStatic() ? OutDts("ESObject") : GenType(classProp->TsType()); } else { ProcessClassPropertyType(classProp); } diff --git a/ets2panda/test/test-lists/declgenets2ts/ets-runtime/declgen-ets2ts-runtime-ignored.txt b/ets2panda/test/test-lists/declgenets2ts/ets-runtime/declgen-ets2ts-runtime-ignored.txt index 385049ecc2..11275c689b 100644 --- a/ets2panda/test/test-lists/declgenets2ts/ets-runtime/declgen-ets2ts-runtime-ignored.txt +++ b/ets2panda/test/test-lists/declgenets2ts/ets-runtime/declgen-ets2ts-runtime-ignored.txt @@ -27,3 +27,4 @@ static-invoke.ets top_level_03.ets multisource_inheritance.ets as_string.ets +function_type_with_receiver/extensionFunctionType_return_this.ets -- Gitee From 88a6a4c216157877de09d41ee69a67f867dc5623 Mon Sep 17 00:00:00 2001 From: dongchao Date: Tue, 15 Jul 2025 17:02:51 +0800 Subject: [PATCH 3/6] Fix interface prop scene in generate_static_abc Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICMHOR Signed-off-by: dongchao Change-Id: I57260e9258f87e1613eb2a5facb486e3bf7a4b9a --- ets2panda/declgen_ets2ts/declgenEts2Ts.cpp | 20 +++++++++++++++++++- ets2panda/declgen_ets2ts/declgenEts2Ts.h | 3 +++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/ets2panda/declgen_ets2ts/declgenEts2Ts.cpp b/ets2panda/declgen_ets2ts/declgenEts2Ts.cpp index 0631225d50..315307935d 100644 --- a/ets2panda/declgen_ets2ts/declgenEts2Ts.cpp +++ b/ets2panda/declgen_ets2ts/declgenEts2Ts.cpp @@ -1331,7 +1331,14 @@ bool TSDeclGen::ProcessTSQualifiedName(const ir::ETSTypeReference *typeReference if (typeReference->Part()->Name()->IsTSQualifiedName() && typeReference->Part()->Name()->AsTSQualifiedName()->Name() != nullptr) { const auto qualifiedName = typeReference->Part()->Name()->AsTSQualifiedName()->Name().Mutf8(); - std::istringstream stream(qualifiedName.data()); + std::istringstream stream(qualifiedName); + std::string firstSegment; + if (std::getline(stream, firstSegment, '.') && stdlibNamespaceList_.count(firstSegment) != 0U) { + OutDts("ESObject"); + return true; + } + importSet_.insert(firstSegment); + indirectDependencyObjects_.insert(firstSegment); std::string segment; while (std::getline(stream, segment, '.')) { importSet_.insert(segment); @@ -1347,6 +1354,10 @@ void TSDeclGen::ProcessETSTypeReferenceType(const ir::ETSTypeReference *typeRefe { auto typePart = typeReference->Part(); auto partName = typePart->GetIdent()->Name().Mutf8(); + if (partName == "Type" || partName == "Function0") { + OutDts("ESObject"); + return; + } importSet_.insert(partName); if (typePart->TypeParams() != nullptr && typePart->TypeParams()->IsTSTypeParameterInstantiation()) { indirectDependencyObjects_.insert(partName); @@ -1670,6 +1681,13 @@ bool TSDeclGen::GenInterfaceProp(const ir::MethodDefinition *methodDef) OutDts("?"); } OutDts(": "); + if (methodDef->TsType()->IsETSFunctionType()) { + const auto *sig = GetFuncSignature(methodDef->TsType()->AsETSFunctionType(), methodDef); + ProcessFunctionReturnType(sig); + OutDts(";"); + OutEndlDts(); + return true; + } ES2PANDA_ASSERT(methodDef->Function() != nullptr); GenType(methodDef->Function()->Signature()->ReturnType()); OutDts(";"); diff --git a/ets2panda/declgen_ets2ts/declgenEts2Ts.h b/ets2panda/declgen_ets2ts/declgenEts2Ts.h index 78b6cce4a0..5c7b279096 100644 --- a/ets2panda/declgen_ets2ts/declgenEts2Ts.h +++ b/ets2panda/declgen_ets2ts/declgenEts2Ts.h @@ -302,6 +302,9 @@ private: "Observed", "ObjectLink", "Watch", "Track", "ObservedV2", "Trace", "ComponentV2", "Local", "Param", "Once", "Event", "Provider", "Consumer", "Monitor", "Computed", "Type"}; + const std::unordered_set stdlibNamespaceList_ = { + "StdProcess", "taskpool", "functions", "containers", "Intl", "GC", + "jsonx", "proxy", "unsafeMemory", "reflect", "StdDebug", "arktest"}; const std::set extensions_ = {".sts", ".ets", ".ts", ".js"}; std::stringstream outputDts_; -- Gitee From 48367b0f566deda446ce79408975a744af5a8eff Mon Sep 17 00:00:00 2001 From: Yuwei Chen <3220103689@zju.edu.cn> Date: Mon, 21 Jul 2025 16:58:26 +0800 Subject: [PATCH 4/6] Generate declarations of indirect dependencies Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICN7W7 Signed-off-by: Yuwei Chen <3220103689@zju.edu.cn> Change-Id: I2c86a848a11781c3dd799d72e0d2c317ea8575e1 --- ets2panda/declgen_ets2ts/declgenEts2Ts.cpp | 87 +++++++++++++++---- ets2panda/declgen_ets2ts/declgenEts2Ts.h | 5 ++ .../declgen-ets2ts-runtime-ignored.txt | 6 +- 3 files changed, 79 insertions(+), 19 deletions(-) diff --git a/ets2panda/declgen_ets2ts/declgenEts2Ts.cpp b/ets2panda/declgen_ets2ts/declgenEts2Ts.cpp index 315307935d..1e9abf56c5 100644 --- a/ets2panda/declgen_ets2ts/declgenEts2Ts.cpp +++ b/ets2panda/declgen_ets2ts/declgenEts2Ts.cpp @@ -34,6 +34,7 @@ #include "ir/ts/tsInterfaceBody.h" #include "ir/ts/tsTypeAliasDeclaration.h" #include "ir/ts/tsTypeParameter.h" +#include "compiler/lowering/util.h" #define DEBUG_PRINT 0 @@ -82,6 +83,8 @@ void TSDeclGen::CollectIndirectExportDependencies() ProcessTypeAliasDependencies(stmt->AsTSTypeAliasDeclaration()); } else if (stmt->IsClassDeclaration()) { ProcessClassDependencies(stmt->AsClassDeclaration()); + } else if (stmt->IsTSInterfaceDeclaration()) { + ProcessInterfaceDependencies(stmt->AsTSInterfaceDeclaration()); } } } @@ -153,11 +156,12 @@ void TSDeclGen::ProcessClassDependencies(const ir::ClassDeclaration *classDecl) if (!classDef->IsExported() && !classDef->IsDefaultExported()) { return; } - state_.super = classDef->Super(); + state_.super = classDef->Super(); if (state_.super != nullptr) { AddSuperType(state_.super); } + if (classDef->TsType() != nullptr && classDef->TsType()->IsETSObjectType()) { ProcessInterfacesDependencies(classDef->TsType()->AsETSObjectType()->Interfaces()); } @@ -217,6 +221,57 @@ void TSDeclGen::ProcessClassMethodDependencies(const ir::MethodDefinition *metho AddSuperType(sig->ReturnType()); } +void TSDeclGen::ProcessInterfaceDependencies(const ir::TSInterfaceDeclaration *interfaceDecl) +{ + if (interfaceDecl->Id()->Name().Mutf8().find('#') != std::string::npos) { + return; + } + + if (!interfaceDecl->IsExported() && !interfaceDecl->IsExportedType()) { + return; + } + + if (interfaceDecl->TsType() != nullptr && interfaceDecl->TsType()->IsETSObjectType()) { + ProcessInterfacesDependencies(interfaceDecl->TsType()->AsETSObjectType()->Interfaces()); + } + + if (interfaceDecl->TypeParams() != nullptr) { + GenSeparated( + interfaceDecl->TypeParams()->Params(), + [this](ir::TSTypeParameter *param) { + if (param->Constraint() == nullptr) { + return; + } + AddSuperType(param->Constraint()); + }, + ""); + } + + ProcessInterfacePropDependencies(interfaceDecl); +} + +void TSDeclGen::ProcessInterfacePropDependencies(const ir::TSInterfaceDeclaration *interfaceDecl) +{ + for (const auto *prop : interfaceDecl->Body()->Body()) { + if (prop->IsMethodDefinition()) { + ProcessInterfaceMethodDependencies(prop->AsMethodDefinition()); + } + } +} + +void TSDeclGen::ProcessInterfaceMethodDependencies(const ir::MethodDefinition *methodDef) +{ + auto methDefFunc = methodDef->Function(); + if (methDefFunc == nullptr) { + return; + } + auto sig = methDefFunc->Signature(); + GenSeparated( + sig->Params(), [this](varbinder::LocalVariable *param) { AddSuperType(param->TsType()); }, ""); + + AddSuperType(sig->ReturnType()); +} + void TSDeclGen::AddSuperType(const ir::Expression *super) { if (super->TsType() == nullptr) { @@ -268,7 +323,10 @@ void TSDeclGen::GenDeclarations() for (auto *globalStatement : program_->Ast()->Statements()) { ResetState(); ResetClassNode(); - if (globalStatement->IsClassDeclaration()) { + const auto jsdoc = compiler::JsdocStringFromDeclaration(globalStatement); + if (jsdoc.Utf8().find(NON_INTEROP_FLAG) != std::string_view::npos) { + continue; + } else if (globalStatement->IsClassDeclaration()) { GenClassDeclaration(globalStatement->AsClassDeclaration()); } else if (globalStatement->IsTSInterfaceDeclaration()) { GenInterfaceDeclaration(globalStatement->AsTSInterfaceDeclaration()); @@ -370,14 +428,6 @@ void TSDeclGen::GenType(const checker::Type *checkerType) if (HandleBasicTypes(checkerType)) { return; } - if (checkerType->HasTypeFlag(checker::TypeFlag::ETS_CONVERTIBLE_TO_NUMERIC)) { - OutDts("number"); - return; - } - if (checkerType->IsETSStringEnumType()) { - OutDts("string"); - return; - } if (checkerType->IsETSFunctionType()) { HandleFunctionType(checkerType); @@ -1628,8 +1678,6 @@ void TSDeclGen::ProcessInterfaceBody(const ir::TSInterfaceBody *body) for (auto *prop : body->Body()) { if (prop->IsMethodDefinition()) { ProcessInterfaceMethodDefinition(prop->AsMethodDefinition()); - } else if (prop->IsClassProperty()) { - GenPropDeclaration(prop->AsClassProperty()); } } } @@ -1830,14 +1878,14 @@ void TSDeclGen::HandleClassDeclarationTypeInfo(const ir::ClassDefinition *classD HandleClassInherit(super); } - if (classDef->TsType() != nullptr && classDef->TsType()->IsETSObjectType() && - !classDef->TsType()->AsETSObjectType()->Interfaces().empty()) { + if (!classDef->Implements().empty()) { + OutDts(" implements "); + GenSeparated(classDef->Implements(), [this](ir::TSClassImplements *impl) { HandleClassInherit(impl->Expr()); }); + } else if (classDef->TsType() != nullptr && classDef->TsType()->IsETSObjectType() && + !classDef->TsType()->AsETSObjectType()->Interfaces().empty()) { OutDts(" implements "); const auto &interfaces = classDef->TsType()->AsETSObjectType()->Interfaces(); GenSeparated(interfaces, [this](checker::ETSObjectType *interface) { GenType(interface); }); - } else if (!classDef->Implements().empty()) { - OutDts(" implements "); - GenSeparated(classDef->Implements(), [this](ir::TSClassImplements *impl) { HandleClassInherit(impl->Expr()); }); } OutDts(" {"); @@ -1895,7 +1943,10 @@ void TSDeclGen::ProcessClassBody(const ir::ClassDefinition *classDef) state_.inClass = true; std::unordered_set processedMethods; for (const auto *prop : classDef->Body()) { - if (classDef->IsEnumTransformed()) { + const auto jsdoc = compiler::JsdocStringFromDeclaration(prop); + if (jsdoc.Utf8().find(NON_INTEROP_FLAG) != std::string_view::npos) { + continue; + } else if (classDef->IsEnumTransformed()) { if (prop->IsClassProperty()) { state_.inEnum = true; GenPropDeclaration(prop->AsClassProperty()); diff --git a/ets2panda/declgen_ets2ts/declgenEts2Ts.h b/ets2panda/declgen_ets2ts/declgenEts2Ts.h index 5c7b279096..c5da719aee 100644 --- a/ets2panda/declgen_ets2ts/declgenEts2Ts.h +++ b/ets2panda/declgen_ets2ts/declgenEts2Ts.h @@ -25,6 +25,8 @@ namespace ark::es2panda::declgen_ets2ts { +constexpr const char *NON_INTEROP_FLAG = "@noninterop"; + struct DeclgenOptions { bool exportAll = false; bool isIsolatedDeclgen = false; @@ -199,6 +201,9 @@ private: void ProcessClassDependencies(const ir::ClassDeclaration *classDecl); void ProcessClassPropDependencies(const ir::ClassDefinition *classDef); void ProcessClassMethodDependencies(const ir::MethodDefinition *methodDef); + void ProcessInterfaceDependencies(const ir::TSInterfaceDeclaration *interfaceDecl); + void ProcessInterfacePropDependencies(const ir::TSInterfaceDeclaration *interfaceDecl); + void ProcessInterfaceMethodDependencies(const ir::MethodDefinition *methodDef); void ProcessETSTypeReferenceDependencies(const ir::ETSTypeReference *typeReference); void AddSuperType(const ir::Expression *super); void AddSuperType(const checker::Type *tsType); diff --git a/ets2panda/test/test-lists/declgenets2ts/ets-runtime/declgen-ets2ts-runtime-ignored.txt b/ets2panda/test/test-lists/declgenets2ts/ets-runtime/declgen-ets2ts-runtime-ignored.txt index 11275c689b..c4c8b79e00 100644 --- a/ets2panda/test/test-lists/declgenets2ts/ets-runtime/declgen-ets2ts-runtime-ignored.txt +++ b/ets2panda/test/test-lists/declgenets2ts/ets-runtime/declgen-ets2ts-runtime-ignored.txt @@ -8,8 +8,12 @@ struct-identifier.ets struct-init2.ets type_param_in_union.ets #FailKind.ABORT_FAIL - 1 tests: +first_match/ctor_need_fix_bytecode.ets +overload_declaration/constructor_overload_sig_with_ref_type.ets +#FailKind.ABORT_FAIL - 2 tests: type_param_in_union.ets -#FailKind.TSC_FAIL - 14 tests: +mypackage/implicit_package_import_2.ets +#FailKind.TSC_FAIL - 21 tests: ClassNewInstance.ets Enum7.ets GenericBridges_01.ets -- Gitee From c9ee280463c7433e9e74487f6846e9728719eb10 Mon Sep 17 00:00:00 2001 From: dongchao Date: Mon, 7 Jul 2025 17:16:51 +0800 Subject: [PATCH 5/6] Achieve record scene in declgen_ets2ts Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICB1LU Signed-off-by: dongchao Change-Id: Ibf08d204b511bb4ec99206187b44a5e6b42c299e --- ets2panda/bindings/native/src/bridges.cpp | 8 +- .../bindings/src/Es2pandaNativeModule.ts | 3 +- ets2panda/bindings/src/driver_helper.ts | 4 +- ets2panda/bindings/src/lsp/lsp_helper.ts | 1314 +++++++++++++++++ ets2panda/declgen_ets2ts/declgenEts2Ts.cpp | 39 +- ets2panda/declgen_ets2ts/declgenEts2Ts.h | 3 +- .../build_system/src/build/base_mode.ts | 24 +- .../build_system/src/build/declgen_worker.ts | 33 +- .../driver/build_system/src/pre_define.ts | 10 +- ets2panda/driver/build_system/src/utils.ts | 19 + ets2panda/public/es2panda_lib.cpp | 4 +- ets2panda/public/es2panda_lib.h | 2 +- .../declgen/test_ets2ts_isolated_declgen.cpp | 63 + 13 files changed, 1501 insertions(+), 25 deletions(-) create mode 100644 ets2panda/bindings/src/lsp/lsp_helper.ts create mode 100644 ets2panda/test/unit/declgen/test_ets2ts_isolated_declgen.cpp diff --git a/ets2panda/bindings/native/src/bridges.cpp b/ets2panda/bindings/native/src/bridges.cpp index ebb915d98f..1b4b6d38f6 100644 --- a/ets2panda/bindings/native/src/bridges.cpp +++ b/ets2panda/bindings/native/src/bridges.cpp @@ -37,13 +37,13 @@ KNativePointer impl_CreateContextFromString(KNativePointer configPtr, KStringPtr TS_INTEROP_3(CreateContextFromString, KNativePointer, KNativePointer, KStringPtr, KStringPtr) KInt impl_GenerateTsDeclarationsFromContext(KNativePointer contextPtr, KStringPtr &outputDeclEts, KStringPtr &outputEts, - KBoolean exportAll) + KBoolean exportAll, KStringPtr &recordFile) { auto context = reinterpret_cast(contextPtr); - return static_cast(GetPublicImpl()->GenerateTsDeclarationsFromContext(context, outputDeclEts.data(), - outputEts.data(), exportAll != 0)); + return static_cast(GetPublicImpl()->GenerateTsDeclarationsFromContext( + context, outputDeclEts.data(), outputEts.data(), exportAll != 0, recordFile.data())); } -TS_INTEROP_4(GenerateTsDeclarationsFromContext, KInt, KNativePointer, KStringPtr, KStringPtr, KBoolean) +TS_INTEROP_5(GenerateTsDeclarationsFromContext, KInt, KNativePointer, KStringPtr, KStringPtr, KBoolean, KStringPtr) KNativePointer impl_CreateContextFromFile(KNativePointer configPtr, KStringPtr &filenamePtr) { diff --git a/ets2panda/bindings/src/Es2pandaNativeModule.ts b/ets2panda/bindings/src/Es2pandaNativeModule.ts index 9f7caa6ab6..2c20b7ffd1 100644 --- a/ets2panda/bindings/src/Es2pandaNativeModule.ts +++ b/ets2panda/bindings/src/Es2pandaNativeModule.ts @@ -69,7 +69,8 @@ export class Es2pandaNativeModule { config: KPtr, outputDeclEts: String, outputEts: String, - exportAll: KBoolean + exportAll: KBoolean, + recordFile: String ): KPtr { throw new Error('Not implemented'); } diff --git a/ets2panda/bindings/src/driver_helper.ts b/ets2panda/bindings/src/driver_helper.ts index 29f82f3d01..4283055d0e 100644 --- a/ets2panda/bindings/src/driver_helper.ts +++ b/ets2panda/bindings/src/driver_helper.ts @@ -76,9 +76,9 @@ export class DriverHelper { global.destroyCfg(); } - public generateTsDecl(declOutPath: string, etsOutPath: string, exportAll: boolean): void { + public generateTsDecl(declOutPath: string, etsOutPath: string, exportAll: boolean, recordFile: string): void { let exportAll_: KBoolean = exportAll ? 1 : 0; - global.es2panda._GenerateTsDeclarationsFromContext(this._cfg.peer, declOutPath, etsOutPath, exportAll_); + global.es2panda._GenerateTsDeclarationsFromContext(this._cfg.peer, declOutPath, etsOutPath, exportAll_, recordFile); } } diff --git a/ets2panda/bindings/src/lsp/lsp_helper.ts b/ets2panda/bindings/src/lsp/lsp_helper.ts new file mode 100644 index 0000000000..b3e2bcc237 --- /dev/null +++ b/ets2panda/bindings/src/lsp/lsp_helper.ts @@ -0,0 +1,1314 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LspDriverHelper } from '../common/driver_helper'; +import { global } from '../common/global'; +import { + LspDefinitionData, + LspDiagsNode, + LspReferences, + LspQuickInfo, + LspClassHierarchy, + LspCompletionEntryKind, + LspClassPropertyInfo, + LspClassHierarchies, + LspDocumentHighlightsReferences, + LspCompletionInfo, + LspReferenceLocationList, + LspLineAndCharacter, + LspReferenceData, + LspClassConstructorInfo, + ApplicableRefactorItemInfo, + LspApplicableRefactorInfo, + CompletionEntryDetails, + LspFileTextChanges, + LspSafeDeleteLocationInfo, + LspSafeDeleteLocation, + LspTypeHierarchiesInfo, + LspTextSpan, + LspInlayHint, + LspInlayHintList, + TextSpan, + LspSignatureHelpItems, + CodeFixActionInfo, + CodeFixActionInfoList, + LspRenameLocation, + LspRenameInfoType, + LspRenameInfoSuccess, + LspRenameInfoFailure +} from './lspNode'; +import { passStringArray, unpackString } from '../common/private'; +import { Es2pandaContextState } from '../generated/Es2pandaEnums'; +import { + BuildConfig, + Config, + FileDepsInfo, + Job, + JobInfo, + WorkerInfo, + ModuleInfo, + PathConfig, + TextDocumentChangeInfo +} from '../common/types'; +import { PluginDriver, PluginHook } from '../common/ui_plugins_driver'; +import { ModuleDescriptor, generateBuildConfigs } from './generateBuildConfig'; +import { generateArkTsConfigs, generateModuleInfo } from './generateArkTSConfig'; + +import * as fs from 'fs'; +import * as path from 'path'; +import { KInt, KNativePointer, KPointer } from '../common/InteropTypes'; +import { passPointerArray } from '../common/private'; +import { NativePtrDecoder } from '../common/Platform'; +import { Worker as ThreadWorker } from 'worker_threads'; +import { ensurePathExists } from '../common/utils'; +import * as child_process from 'child_process'; +import { DECL_ETS_SUFFIX, DEFAULT_CACHE_DIR, TS_SUFFIX } from '../common/preDefine'; +import * as crypto from 'crypto'; +import * as os from 'os'; +import { changeDeclgenFileExtension, getModuleNameAndPath } from '../common/utils'; + +const ets2pandaCmdPrefix = ['-', '--extension', 'ets', '--arktsconfig']; + +function initBuildEnv(): void { + const currentPath: string | undefined = process.env.PATH; + let pandaLibPath: string = process.env.PANDA_LIB_PATH + ? process.env.PANDA_LIB_PATH + : path.resolve(__dirname, '../../../ets2panda/lib'); + process.env.PATH = `${currentPath}${path.delimiter}${pandaLibPath}`; +} + +export class Lsp { + private pandaLibPath: string; + private pandaBinPath: string; + private getFileContent: (filePath: string) => string; + private filesMap: Map; // Map + private cacheDir: string; + private globalContextPtr?: KNativePointer; + private globalConfig?: Config; + private globalLspDriverHelper?: LspDriverHelper; + private entryArkTsConfig: string; + private fileDependencies: string; + private buildConfigs: Record; // Map + private moduleInfos: Record; // Map + private pathConfig: PathConfig; + private lspDriverHelper = new LspDriverHelper(); + + constructor(pathConfig: PathConfig, getContentCallback?: (filePath: string) => string, modules?: ModuleDescriptor[]) { + initBuildEnv(); + this.cacheDir = + pathConfig.cacheDir !== undefined ? pathConfig.cacheDir : path.join(pathConfig.projectPath, DEFAULT_CACHE_DIR); + this.fileDependencies = path.join(this.cacheDir, 'file_dependencies.json'); + this.entryArkTsConfig = path.join(this.cacheDir, 'entry', 'arktsconfig.json'); + this.pandaLibPath = process.env.PANDA_LIB_PATH + ? process.env.PANDA_LIB_PATH + : path.resolve(__dirname, '../../../ets2panda/lib'); + this.pandaBinPath = process.env.PANDA_BIN_PATH + ? process.env.PANDA_BIN_PATH + : path.resolve(__dirname, '../../../ets2panda/bin'); + this.filesMap = new Map(); + this.getFileContent = getContentCallback || ((path: string): string => fs.readFileSync(path, 'utf8')); + this.buildConfigs = generateBuildConfigs(pathConfig, modules); + this.moduleInfos = generateArkTsConfigs(this.buildConfigs); + this.pathConfig = pathConfig; + PluginDriver.getInstance().initPlugins(Object.values(this.buildConfigs)[0]); + this.generateDeclFile(); + } + + // Partially update for new file + updateModuleInfos(module: ModuleDescriptor, newFilePath: String): void { + let buildConfig = this.buildConfigs[module.name]; + buildConfig.compileFiles.push(newFilePath.valueOf()); + let moduleInfo = generateModuleInfo(this.buildConfigs, buildConfig); + this.moduleInfos[newFilePath.valueOf()] = moduleInfo; + } + + // Full update for `Sync Now` + update(modules: ModuleDescriptor[]): void { + this.buildConfigs = generateBuildConfigs(this.pathConfig, modules); + this.moduleInfos = generateArkTsConfigs(this.buildConfigs); + } + + modifyFilesMap(fileName: string, fileContent: TextDocumentChangeInfo): void { + this.filesMap.set(fileName, fileContent.newDoc); + } + + deleteFromFilesMap(fileName: string): void { + this.filesMap.delete(fileName); + } + + private getFileSource(filePath: string): string { + const getSource = this.filesMap.get(filePath) || this.getFileContent(filePath) || fs.readFileSync(filePath, 'utf8'); + if (getSource === undefined) { + throw new Error(`File content not found for path: ${filePath}`); + } + return getSource.replace(/\r\n/g, '\n'); + } + + private createContext(filename: String): [Config, KNativePointer] { + const filePath = path.resolve(filename.valueOf()); + const arktsconfig = this.moduleInfos[filePath]?.arktsConfigFile; + if (!arktsconfig) { + throw new Error(`Missing arktsconfig for ${filePath}`); + } + + const ets2pandaCmd = [...ets2pandaCmdPrefix, arktsconfig]; + const localCfg = this.lspDriverHelper.createCfg(ets2pandaCmd, filePath, this.pandaLibPath); + const source = this.getFileSource(filePath); + + const localCtx = this.lspDriverHelper.createCtx(source, filePath, localCfg, this.globalContextPtr); + try { + const packageName = this.moduleInfos[filePath].packageName; + const buildConfig = this.buildConfigs[packageName]; + const pluginContext = PluginDriver.getInstance().getPluginContext(); + pluginContext.setCodingFilePath(filePath); + pluginContext.setProjectConfig(buildConfig); + pluginContext.setContextPtr(localCtx); + + this.lspDriverHelper.proceedToState(localCtx, Es2pandaContextState.ES2PANDA_STATE_PARSED); + PluginDriver.getInstance().runPluginHook(PluginHook.PARSED); + + this.lspDriverHelper.proceedToState(localCtx, Es2pandaContextState.ES2PANDA_STATE_CHECKED); + return [localCfg, localCtx]; + } catch (error) { + this.lspDriverHelper.destroyContext(localCtx); + this.lspDriverHelper.destroyConfig(localCfg); + throw error; + } + } + + private destroyContext(config: Config, context: KNativePointer): void { + try { + PluginDriver.getInstance().runPluginHook(PluginHook.CLEAN); + } finally { + this.lspDriverHelper.destroyContext(context); + this.lspDriverHelper.destroyConfig(config); + } + } + + generateDeclFile(): void { + for (const [moduleName, buildConfig] of Object.entries(this.buildConfigs)) { + if (!buildConfig.enableDeclgenEts2Ts) { + continue; + } + if (!buildConfig.declgenOutDir || buildConfig.declgenOutDir === '') { + return; + } + buildConfig.compileFiles.forEach((compilefilePath: string) => { + if (!this.moduleInfos.hasOwnProperty(compilefilePath)) { + return; + } + const [cfg, ctx] = this.createContext(compilefilePath); + try { + // declgen file + let moduleInfo = this.moduleInfos[compilefilePath]; + let modulePath: string = path.relative(buildConfig.moduleRootPath, compilefilePath); + let declOut: string = ''; + let declBridgeOut: string = ''; + if (!moduleInfo.declgenV1OutPath) { + declOut = path.join(buildConfig.declgenOutDir, moduleName); + } + if (!moduleInfo.declgenBridgeCodePath) { + declBridgeOut = path.join(buildConfig.declgenOutDir, moduleName); + } + let declEtsOutputPath: string = changeDeclgenFileExtension( + path.join(moduleInfo.declgenV1OutPath ?? declOut, modulePath), + DECL_ETS_SUFFIX + ); + let etsOutputPath: string = changeDeclgenFileExtension( + path.join(moduleInfo.declgenBridgeCodePath ?? declBridgeOut, modulePath), + TS_SUFFIX + ); + ensurePathExists(declEtsOutputPath); + ensurePathExists(etsOutputPath); + global.es2pandaPublic._GenerateTsDeclarationsFromContext(ctx, declEtsOutputPath, etsOutputPath, 1, ''); + } finally { + this.destroyContext(cfg, ctx); + } + }); + } + } + + modifyDeclFile(modifyFilePath: string, arktsConfigFile?: string): void { + // source file + let sourceFilePath = path.resolve(modifyFilePath.valueOf()); + let moduleInfo: ModuleInfo; + if (this.moduleInfos.hasOwnProperty(sourceFilePath)) { + moduleInfo = this.moduleInfos[sourceFilePath]; + } else { + const [newModuleName, newModuleRootPath] = getModuleNameAndPath(modifyFilePath, this.pathConfig.projectPath); + if (newModuleName && newModuleName !== '' && newModuleRootPath && newModuleRootPath !== '') { + moduleInfo = { + packageName: newModuleName, + moduleRootPath: newModuleRootPath, + moduleType: '', + entryFile: '', + arktsConfigFile: arktsConfigFile ?? '', + compileFiles: [], + declgenV1OutPath: '', + declgenBridgeCodePath: '', + staticDepModuleInfos: [], + dynamicDepModuleInfos: [], + language: '' + }; + } else { + return; + } + } + const moduleName = moduleInfo.packageName; + const moduleRootPath = moduleInfo.moduleRootPath; + if (!this.buildConfigs.hasOwnProperty(moduleName)) { + return; + } + const buildConfig = this.buildConfigs[moduleName]; + if (!buildConfig.enableDeclgenEts2Ts) { + return; + } + const [cfg, ctx] = this.createContext(sourceFilePath); + try { + // declgen file + let declOut: string = ''; + let declBridgeOut: string = ''; + if (!moduleInfo.declgenV1OutPath) { + declOut = path.join(buildConfig.declgenOutDir, moduleName); + } + if (!moduleInfo.declgenBridgeCodePath) { + declBridgeOut = path.join(buildConfig.declgenOutDir, moduleName); + } + let filePathFromModuleRoot: string = path.relative(moduleRootPath, modifyFilePath); + let declEtsOutputPath: string = changeDeclgenFileExtension( + path.join(moduleInfo.declgenV1OutPath ?? declOut, filePathFromModuleRoot), + DECL_ETS_SUFFIX + ); + let etsOutputPath: string = changeDeclgenFileExtension( + path.join(moduleInfo.declgenBridgeCodePath ?? declBridgeOut, filePathFromModuleRoot), + TS_SUFFIX + ); + ensurePathExists(declEtsOutputPath); + ensurePathExists(etsOutputPath); + global.es2pandaPublic._GenerateTsDeclarationsFromContext(ctx, declEtsOutputPath, etsOutputPath, 1, ''); + } finally { + this.destroyContext(cfg, ctx); + } + } + + getOffsetByColAndLine(filename: String, line: number, column: number): number { + let ptr: KPointer; + const [cfg, ctx] = this.createContext(filename); + try { + ptr = global.es2panda._getOffsetByColAndLine(ctx, line, column); + } finally { + this.destroyContext(cfg, ctx); + } + return ptr; + } + + getDefinitionAtPosition(filename: String, offset: number): LspDefinitionData { + let ptr: KPointer; + const [cfg, ctx] = this.createContext(filename); + try { + ptr = global.es2panda._getDefinitionAtPosition(ctx, offset); + } finally { + this.destroyContext(cfg, ctx); + } + return new LspDefinitionData(ptr); + } + + getSemanticDiagnostics(filename: String): LspDiagsNode { + let ptr: KPointer; + const [cfg, ctx] = this.createContext(filename); + try { + ptr = global.es2panda._getSemanticDiagnostics(ctx); + } finally { + this.destroyContext(cfg, ctx); + } + return new LspDiagsNode(ptr); + } + + getCurrentTokenValue(filename: String, offset: number): string { + let ptr: KPointer; + const [cfg, ctx] = this.createContext(filename); + try { + ptr = global.es2panda._getCurrentTokenValue(ctx, offset); + } finally { + this.destroyContext(cfg, ctx); + } + return unpackString(ptr); + } + + getImplementationAtPosition(filename: String, offset: number): LspDefinitionData { + let ptr: KPointer; + const [cfg, ctx] = this.createContext(filename); + try { + ptr = global.es2panda._getImplementationAtPosition(ctx, offset); + } finally { + this.destroyContext(cfg, ctx); + } + return new LspDefinitionData(ptr); + } + + getFileReferences(filename: String): LspReferenceData[] { + let isPackageModule: boolean; + const [cfg, searchCtx] = this.createContext(filename); + try { + isPackageModule = global.es2panda._isPackageModule(searchCtx); + } finally { + this.destroyContext(cfg, searchCtx); + } + let result: LspReferenceData[] = []; + let compileFiles = this.moduleInfos[path.resolve(filename.valueOf())].compileFiles; + for (let i = 0; i < compileFiles.length; i++) { + let ptr: KPointer; + const [cfg, ctx] = this.createContext(compileFiles[i]); + try { + ptr = global.es2panda._getFileReferences(path.resolve(filename.valueOf()), ctx, isPackageModule); + } finally { + this.destroyContext(cfg, ctx); + } + let refs = new LspReferences(ptr); + for (let j = 0; j < refs.referenceInfos.length; j++) { + if (refs.referenceInfos[j].fileName !== '') { + result.push(refs.referenceInfos[j]); + } + } + } + return result; + } + + getReferencesAtPosition(filename: String, offset: number): LspReferenceData[] { + let declInfo: KPointer; + const [cfg, searchCtx] = this.createContext(filename); + try { + declInfo = global.es2panda._getDeclInfo(searchCtx, offset); + } finally { + this.destroyContext(cfg, searchCtx); + } + let result: LspReferenceData[] = []; + let compileFiles = this.moduleInfos[path.resolve(filename.valueOf())].compileFiles; + for (let i = 0; i < compileFiles.length; i++) { + let ptr: KPointer; + const [cfg, ctx] = this.createContext(compileFiles[i]); + try { + ptr = global.es2panda._getReferencesAtPosition(ctx, declInfo); + } finally { + this.destroyContext(cfg, ctx); + } + let refs = new LspReferences(ptr); + result.push(...refs.referenceInfos); + } + return Array.from(new Set(result)); + } + + getTypeHierarchies(filename: String, offset: number): LspTypeHierarchiesInfo | null { + let ptr: KPointer; + const [cfg, ctx] = this.createContext(filename); + ptr = global.es2panda._getTypeHierarchies(ctx, ctx, offset); + let ref = new LspTypeHierarchiesInfo(ptr); + if (ref.fileName === '') { + this.destroyContext(cfg, ctx); + return null; + } + let result: LspTypeHierarchiesInfo[] = []; + let compileFiles = this.moduleInfos[path.resolve(filename.valueOf())].compileFiles; + for (let i = 0; i < compileFiles.length; i++) { + let searchPtr: KPointer; + const [cfg, searchCtx] = this.createContext(compileFiles[i]); + try { + searchPtr = global.es2panda._getTypeHierarchies(searchCtx, ctx, offset); + } finally { + this.destroyContext(cfg, searchCtx); + } + let refs = new LspTypeHierarchiesInfo(searchPtr); + if (i > 0) { + result[0].subHierarchies.subOrSuper = result[0].subHierarchies.subOrSuper.concat( + refs.subHierarchies.subOrSuper + ); + } else { + result.push(refs); + } + } + for (let j = 0; j < result[0].subHierarchies.subOrSuper.length; j++) { + let res = this.getTypeHierarchies( + result[0].subHierarchies.subOrSuper[j].fileName, + result[0].subHierarchies.subOrSuper[j].pos + ); + if (res !== null) { + let subOrSuperTmp = result[0].subHierarchies.subOrSuper[j].subOrSuper.concat(res.subHierarchies.subOrSuper); + result[0].subHierarchies.subOrSuper[j].subOrSuper = Array.from( + new Map( + subOrSuperTmp.map((item) => [`${item.fileName}-${item.type}-${item.pos}-${item.name}`, item]) + ).values() + ); + } + } + return result[0]; + } + + getClassHierarchyInfo(filename: String, offset: number): LspClassHierarchy { + let ptr: KPointer; + const [cfg, ctx] = this.createContext(filename); + try { + ptr = global.es2panda._getClassHierarchyInfo(ctx, offset); + } finally { + this.destroyContext(cfg, ctx); + } + return new LspClassHierarchy(ptr); + } + + getAliasScriptElementKind(filename: String, offset: number): LspCompletionEntryKind { + let kind: KInt; + const [cfg, ctx] = this.createContext(filename); + try { + kind = global.es2panda._getAliasScriptElementKind(ctx, offset); + } finally { + this.destroyContext(cfg, ctx); + } + return kind; + } + + getClassHierarchies(filename: String, offset: number): LspClassHierarchies { + let contextList = []; + const [cfg, ctx] = this.createContext(filename); + contextList.push({ ctx: ctx, cfg: cfg }); + let nativeContextList = global.es2panda._pushBackToNativeContextVector(ctx, ctx, 1); + let compileFiles = this.moduleInfos[path.resolve(filename.valueOf())].compileFiles; + for (let i = 0; i < compileFiles.length; i++) { + let filePath = path.resolve(compileFiles[i]); + if (path.resolve(filename.valueOf()) === filePath) { + continue; + } + const [searchCfg, searchCtx] = this.createContext(filePath); + 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); + for (const { ctx, cfg } of contextList) { + this.destroyContext(cfg, ctx); + } + return new LspClassHierarchies(ptr); + } + + getClassPropertyInfo( + filename: String, + offset: number, + shouldCollectInherited: boolean = false + ): LspClassPropertyInfo { + let ptr: KPointer; + const [cfg, ctx] = this.createContext(filename); + try { + ptr = global.es2panda._getClassPropertyInfo(ctx, offset, shouldCollectInherited); + } finally { + this.destroyContext(cfg, ctx); + } + return new LspClassPropertyInfo(ptr); + } + + getOrganizeImports(filename: String): LspFileTextChanges { + let ptr: KPointer; + const [cfg, ctx] = this.createContext(filename); + try { + ptr = global.es2panda._organizeImports(ctx, filename); + PluginDriver.getInstance().runPluginHook(PluginHook.CLEAN); + } finally { + this.destroyContext(cfg, ctx); + } + return new LspFileTextChanges(ptr); + } + + findSafeDeleteLocation(filename: String, offset: number): LspSafeDeleteLocationInfo[] { + let declInfo: KPointer; + const [cfg, ctx] = this.createContext(filename); + try { + declInfo = global.es2panda._getDeclInfo(ctx, offset); + } finally { + this.destroyContext(cfg, ctx); + } + let result: LspSafeDeleteLocationInfo[] = []; + let compileFiles = this.moduleInfos[path.resolve(filename.valueOf())].compileFiles; + for (let i = 0; i < compileFiles.length; i++) { + let ptr: KPointer; + const [searchCfg, searchCtx] = this.createContext(compileFiles[i]); + try { + ptr = global.es2panda._findSafeDeleteLocation(searchCtx, declInfo); + } finally { + this.destroyContext(searchCfg, searchCtx); + } + let refs = new LspSafeDeleteLocation(ptr); + result.push(...refs.safeDeleteLocationInfos); + } + return Array.from(new Set(result)); + } + + getCompletionEntryDetails(filename: String, offset: number, entryName: String): CompletionEntryDetails { + let ptr: KPointer; + const [cfg, ctx] = this.createContext(filename); + try { + ptr = global.es2panda._getCompletionEntryDetails(entryName, filename, ctx, offset); + } finally { + this.destroyContext(cfg, ctx); + } + return new CompletionEntryDetails(ptr); + } + + getApplicableRefactors(filename: String, kind: String, offset: number): ApplicableRefactorItemInfo[] { + let ptr: KPointer; + let result: ApplicableRefactorItemInfo[] = []; + const [cfg, ctx] = this.createContext(filename); + try { + ptr = global.es2panda._getApplicableRefactors(ctx, kind, offset); + } finally { + this.destroyContext(cfg, ctx); + } + let refs = new LspApplicableRefactorInfo(ptr); + result.push(...refs.applicableRefactorInfo); + return Array.from(new Set(result)); + } + + getClassConstructorInfo(filename: String, offset: number, properties: string[]): LspClassConstructorInfo { + let ptr: KPointer; + const [cfg, ctx] = this.createContext(filename); + try { + ptr = global.es2panda._getClassConstructorInfo(ctx, offset, passStringArray(properties)); + } finally { + this.destroyContext(cfg, ctx); + } + return new LspClassConstructorInfo(ptr); + } + + getSyntacticDiagnostics(filename: String): LspDiagsNode { + let ptr: KPointer; + const [cfg, ctx] = this.createContext(filename); + try { + ptr = global.es2panda._getSyntacticDiagnostics(ctx); + } finally { + this.destroyContext(cfg, ctx); + } + return new LspDiagsNode(ptr); + } + + getSuggestionDiagnostics(filename: String): LspDiagsNode { + let ptr: KPointer; + const [cfg, ctx] = this.createContext(filename); + try { + ptr = global.es2panda._getSuggestionDiagnostics(ctx); + } finally { + this.destroyContext(cfg, ctx); + } + return new LspDiagsNode(ptr); + } + + getQuickInfoAtPosition(filename: String, offset: number): LspQuickInfo { + let ptr: KPointer; + const [cfg, ctx] = this.createContext(filename); + try { + ptr = global.es2panda._getQuickInfoAtPosition(filename, ctx, offset); + } finally { + this.destroyContext(cfg, ctx); + } + return new LspQuickInfo(ptr); + } + + getDocumentHighlights(filename: String, offset: number): LspDocumentHighlightsReferences { + let ptr: KPointer; + const [cfg, ctx] = this.createContext(filename); + try { + ptr = global.es2panda._getDocumentHighlights(ctx, offset); + } finally { + this.destroyContext(cfg, ctx); + } + return new LspDocumentHighlightsReferences(ptr); + } + + getCompletionAtPosition(filename: String, offset: number): LspCompletionInfo { + let lspDriverHelper = new LspDriverHelper(); + let filePath = path.resolve(filename.valueOf()); + let arktsconfig = this.moduleInfos[filePath].arktsConfigFile; + let ets2pandaCmd = ets2pandaCmdPrefix.concat(arktsconfig); + let localCfg = lspDriverHelper.createCfg(ets2pandaCmd, filePath, this.pandaLibPath); + let source = this.getFileSource(filePath); + // This is a temporary solution to support "obj." with wildcard for better solution in internal issue. + if (source[offset - 1] === '.') { + const wildcard = '_WILDCARD'; + if (offset < source.length + 1) { + source = source.slice(0, offset) + wildcard + source.slice(offset); + } else { + source += wildcard; + } + offset += wildcard.length; + } + let localCtx = lspDriverHelper.createCtx(source, filePath, localCfg, this.globalContextPtr); + const packageName = this.moduleInfos[filePath].packageName; + const buildConfig = this.buildConfigs[packageName]; + PluginDriver.getInstance().getPluginContext().setCodingFilePath(filePath); + PluginDriver.getInstance().getPluginContext().setProjectConfig(buildConfig); + 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._getCompletionAtPosition(localCtx, offset); + PluginDriver.getInstance().runPluginHook(PluginHook.CLEAN); + lspDriverHelper.destroyContext(localCtx); + lspDriverHelper.destroyConfig(localCfg); + return new LspCompletionInfo(ptr); + } + + toLineColumnOffset(filename: String, offset: number): LspLineAndCharacter { + let ptr: KPointer; + const [cfg, ctx] = this.createContext(filename); + try { + ptr = global.es2panda._toLineColumnOffset(ctx, offset); + } finally { + this.destroyContext(cfg, ctx); + } + return new LspLineAndCharacter(ptr); + } + + getSafeDeleteInfo(filename: String, position: number): boolean { + let result: boolean; + const [cfg, ctx] = this.createContext(filename); + try { + result = global.es2panda._getSafeDeleteInfo(ctx, position); + } finally { + this.destroyContext(cfg, ctx); + } + return result; + } + + findRenameLocations(filename: String, offset: number): LspRenameLocation[] { + let compileFiles = this.moduleInfos[path.resolve(filename.valueOf())].compileFiles; + const fileContexts: KPointer[] = []; + const fileConfigs: Config[] = []; + for (let i = 0; i < compileFiles.length; i++) { + const [compileFileCfg, compileFileCtx] = this.createContext(compileFiles[i]); + fileContexts.push(compileFileCtx); + fileConfigs.push(compileFileCfg); + } + const [cfg, ctx] = this.createContext(filename); + const ptr = global.es2panda._findRenameLocations(fileContexts.length, passPointerArray(fileContexts), ctx, offset); + const result: LspRenameLocation[] = new NativePtrDecoder().decode(ptr).map((elPeer: KPointer) => { + return new LspRenameLocation(elPeer); + }); + for (let i = 0; i < fileContexts.length; i++) { + this.destroyContext(fileConfigs[i], fileContexts[i]); + } + this.destroyContext(cfg, ctx); + return Array.from(new Set(result)); + } + + getRenameInfo(filename: String, offset: number): LspRenameInfoType { + let ptr: KPointer; + let res: LspRenameInfoType; + const [cfg, ctx] = this.createContext(filename); + try { + ptr = global.es2panda._getRenameInfo(ctx, offset, this.pandaLibPath); + const success = global.es2panda._getRenameInfoIsSuccess(ptr); + if (success) { + res = new LspRenameInfoSuccess(global.es2panda._getRenameInfoSuccess(ptr)); + } else { + res = new LspRenameInfoFailure(global.es2panda._getRenameInfoFailure(ptr)); + } + } finally { + this.destroyContext(cfg, ctx); + } + return res; + } + + getSpanOfEnclosingComment(filename: String, offset: number, onlyMultiLine: boolean): LspTextSpan { + let ptr: KPointer; + const [cfg, ctx] = this.createContext(filename); + try { + ptr = global.es2panda._getSpanOfEnclosingComment(ctx, offset, onlyMultiLine); + } finally { + this.destroyContext(cfg, ctx); + } + return new LspTextSpan(ptr); + } + + getCodeFixesAtPosition(filename: String, start: number, end: number, errorCodes: number[]): CodeFixActionInfo[] { + let ptr: KPointer; + const [cfg, ctx] = this.createContext(filename); + try { + ptr = global.es2panda._getCodeFixesAtPosition(ctx, start, end, new Int32Array(errorCodes), errorCodes.length); + } finally { + this.destroyContext(cfg, ctx); + } + const codeFixActionInfoList = new CodeFixActionInfoList(ptr); + const codeFixActionInfos: CodeFixActionInfo[] = []; + codeFixActionInfos.push(...codeFixActionInfoList.codeFixActionInfos); + return codeFixActionInfos; + } + + provideInlayHints(filename: String, span: TextSpan): LspInlayHint[] { + let ptr: KPointer; + const [cfg, ctx] = this.createContext(filename); + try { + const nativeSpan = global.es2panda._createTextSpan(span.start, span.length); + ptr = global.es2panda._getInlayHintList(ctx, nativeSpan); + } finally { + this.destroyContext(cfg, ctx); + } + const inlayHintList = new LspInlayHintList(ptr); + const inlayHints: LspInlayHint[] = []; + inlayHints.push(...inlayHintList.inlayHints); + return inlayHints; + } + + getSignatureHelpItems(filename: String, offset: number): LspSignatureHelpItems { + let ptr: KPointer; + const [cfg, ctx] = this.createContext(filename); + try { + ptr = global.es2panda._getSignatureHelpItems(ctx, offset); + } finally { + this.destroyContext(cfg, ctx); + } + return new LspSignatureHelpItems(ptr); + } + + // Use AST cache start + private getFileDependencies(inputs: string[], output: string): void { + let depInputContent = ''; + let outputFile: string = output; + let depAnalyzerPath: string = path.join(this.pandaBinPath, 'dependency_analyzer'); + let depInputFile = path.join(this.cacheDir, 'depInput.txt'); + inputs.forEach((file) => { + depInputContent += file + os.EOL; + }); + fs.writeFileSync(depInputFile, depInputContent); + ensurePathExists(outputFile); + const result = child_process.spawnSync( + depAnalyzerPath, + [`@${depInputFile}`, `--output=${output}`, `--arktsconfig=${this.entryArkTsConfig}`], + { + encoding: 'utf-8', + windowsHide: true + } + ); + if (result.error || result.status !== 0) { + console.error('getFileDependencies failed: ', result.stderr || result.error); + } + } + + // Collect circular dependencies, like: A→B→C→A + private findStronglyConnectedComponents(graph: FileDepsInfo): Map> { + const adjacencyList: Record = {}; + const reverseAdjacencyList: Record = {}; + const allNodes = new Set(); + + for (const node in graph.dependencies) { + allNodes.add(node); + graph.dependencies[node].forEach((dep) => allNodes.add(dep)); + } + for (const node in graph.dependants) { + allNodes.add(node); + graph.dependants[node].forEach((dep) => allNodes.add(dep)); + } + + Array.from(allNodes).forEach((node) => { + adjacencyList[node] = graph.dependencies[node] || []; + reverseAdjacencyList[node] = graph.dependants[node] || []; + }); + + const visited = new Set(); + const order: string[] = []; + + function dfs(node: string): void { + visited.add(node); + for (const neighbor of adjacencyList[node]) { + if (!visited.has(neighbor)) { + dfs(neighbor); + } + } + order.push(node); + } + + Array.from(allNodes).forEach((node) => { + if (!visited.has(node)) { + dfs(node); + } + }); + + visited.clear(); + const components = new Map>(); + + function reverseDfs(node: string, component: Set): void { + visited.add(node); + component.add(node); + for (const neighbor of reverseAdjacencyList[node]) { + if (!visited.has(neighbor)) { + reverseDfs(neighbor, component); + } + } + } + + for (let i = order.length - 1; i >= 0; i--) { + const node = order[i]; + if (!visited.has(node)) { + const component = new Set(); + reverseDfs(node, component); + if (component.size > 1) { + const sortedFiles = Array.from(component).sort(); + const hashKey = createHash(sortedFiles.join('|')); + components.set(hashKey, component); + } + } + } + + return components; + } + + private getJobDependencies(fileDeps: string[], cycleFiles: Map): Set { + let depJobList: Set = new Set(); + fileDeps.forEach((file) => { + if (!cycleFiles.has(file)) { + depJobList.add('0' + file); + } else { + cycleFiles.get(file)?.forEach((f) => { + depJobList.add(f); + }); + } + }); + + return depJobList; + } + + private getJobDependants(fileDeps: string[], cycleFiles: Map): Set { + let depJobList: Set = new Set(); + fileDeps.forEach((file) => { + if (!file.endsWith(DECL_ETS_SUFFIX)) { + depJobList.add('1' + file); + } + if (cycleFiles.has(file)) { + cycleFiles.get(file)?.forEach((f) => { + depJobList.add(f); + }); + } else { + depJobList.add('0' + file); + } + }); + + return depJobList; + } + + private collectCompileJobs(jobs: Record, isValid: boolean = false): void { + let entryFileList: string[] = Object.keys(this.moduleInfos); + this.getFileDependencies(entryFileList, this.fileDependencies); + const data = fs.readFileSync(this.fileDependencies, 'utf-8'); + let fileDepsInfo: FileDepsInfo = JSON.parse(data) as FileDepsInfo; + + Object.keys(fileDepsInfo.dependants).forEach((file) => { + if (!(file in fileDepsInfo.dependencies)) { + fileDepsInfo.dependencies[file] = []; + } + }); + + let cycleGroups = this.findStronglyConnectedComponents(fileDepsInfo); + + let cycleFiles: Map = new Map(); + cycleGroups.forEach((value: Set, key: string) => { + value.forEach((file) => { + cycleFiles.set(file, [key]); + }); + }); + + Object.entries(fileDepsInfo.dependencies).forEach(([key, value]) => { + let dependencies = this.getJobDependencies(value, cycleFiles); + if (cycleFiles.has(key)) { + const externalProgramJobIds = cycleFiles.get(key)!; + externalProgramJobIds.forEach((id) => { + let fileList: string[] = Array.from(cycleGroups.get(id)!); + this.createExternalProgramJob(id, fileList, jobs, dependencies, isValid, true); + }); + } else { + const id = '0' + key; + let fileList: string[] = [key]; + this.createExternalProgramJob(id, fileList, jobs, dependencies, isValid); + } + }); + + Object.entries(fileDepsInfo.dependants).forEach(([key, value]) => { + const dependants = this.getJobDependants(value, cycleFiles); + const jobIds = cycleFiles.has(key) ? cycleFiles.get(key)! : ['0' + key]; + + jobIds.forEach((jobId) => { + const currentDependants = new Set(dependants); + jobs[jobId].dependants.forEach((dep) => currentDependants.add(dep)); + currentDependants.delete(jobId); + jobs[jobId].dependants = Array.from(currentDependants); + }); + }); + } + + private createExternalProgramJob( + id: string, + fileList: string[], + jobs: Record, + dependencies: Set, + isValid: boolean, + isInCycle?: boolean + ): void { + if (dependencies.has(id)) { + dependencies.delete(id); + } + if (jobs[id]) { + const existingJob = jobs[id]; + const mergedFileList = [...new Set([...existingJob.fileList, ...fileList])]; + const mergedDependencies = [...new Set([...existingJob.dependencies, ...Array.from(dependencies)])]; + const mergedIsInCycle = existingJob.isInCycle || isInCycle; + + existingJob.fileList = mergedFileList; + existingJob.dependencies = mergedDependencies; + existingJob.isInCycle = mergedIsInCycle; + } else { + jobs[id] = { + id, + fileList, + isDeclFile: true, + isInCycle, + dependencies: Array.from(dependencies), + dependants: [], + isValid + }; + } + } + + private addJobToQueues(job: Job, queues: Job[]): void { + if (queues.some((j) => j.id === job.id)) { + return; + } + queues.push(job); + } + + private initCompileQueues(jobs: Record, queues: Job[], dependantJobs?: Record): void { + Object.values(jobs).forEach((job) => { + if (job.dependencies.length === 0) { + if (dependantJobs && job.id in dependantJobs) { + job.isValid = false; + this.invalidateFileCache(job.fileList); + } + this.addJobToQueues(job, queues); + } + }); + } + + private checkAllTasksDone(queues: Job[], workerPool: WorkerInfo[]): boolean { + if (queues.length === 0) { + for (let i = 0; i < workerPool.length; i++) { + if (!workerPool[i].isIdle) { + return false; + } + } + return true; + } + return false; + } + + private initGlobalContext(jobs: Record): void { + let files: string[] = []; + Object.entries(jobs).forEach(([key, job]) => { + for (let i = 0; i < job.fileList.length; i++) { + files.push(job.fileList[i]); + } + }); + + let ets2pandaCmd: string[] = [ + '_', + '--extension', + 'ets', + '--arktsconfig', + this.entryArkTsConfig, + Object.keys(this.moduleInfos)[0] + ]; + + this.globalLspDriverHelper = new LspDriverHelper(); + this.globalLspDriverHelper.memInitialize(this.pandaLibPath); + this.globalConfig = this.globalLspDriverHelper.createCfg(ets2pandaCmd, files[0], this.pandaLibPath); + this.globalContextPtr = this.globalLspDriverHelper.createGlobalContext(this.globalConfig.peer, files, files.length); + } + + private updateQueues( + jobs: Record, + queues: Job[], + jobId: string, + dependantJobs?: Record + ): void { + const completedJob = jobs[jobId]; + completedJob.dependants.forEach((depJobId) => { + const depJob = jobs[depJobId]; + if (!depJob) { + return; + } + const depIndex = depJob.dependencies.indexOf(jobId); + if (depIndex === -1) { + return; + } + depJob.dependencies.splice(depIndex, 1); + if (depJob.dependencies.length > 0) { + return; + } + this.processCompletedDependencies(depJob, queues, dependantJobs); + }); + } + + private processCompletedDependencies(depJob: Job, queues: Job[], dependantJobs?: Record): void { + if (dependantJobs && depJob.id in dependantJobs) { + depJob.isValid = false; + this.invalidateFileCache(depJob.fileList); + } + this.addJobToQueues(depJob, queues); + } + + private invalidateFileCache(fileList: string[]): void { + fileList.forEach((file) => { + global.es2pandaPublic._InvalidateFileCache(this.globalContextPtr!, file); + }); + } + + private async invokeWorkers( + jobs: Record, + queues: Job[], + processingJobs: Set, + workers: ThreadWorker[], + numWorkers: number, + dependantJobs?: Record + ): Promise { + return new Promise((resolve) => { + const workerPool = this.createWorkerPool(numWorkers, workers); + + workerPool.forEach((workerInfo) => { + this.setupWorkerListeners(workerInfo.worker, workerPool, jobs, queues, processingJobs, resolve, dependantJobs); + this.assignTaskToIdleWorker(workerInfo, queues, processingJobs); + }); + }); + } + + private createWorkerPool(numWorkers: number, workers: ThreadWorker[]): WorkerInfo[] { + const workerPool: WorkerInfo[] = []; + + for (let i = 0; i < numWorkers; i++) { + const worker = new ThreadWorker(path.resolve(__dirname, 'compile_thread_worker.js'), { + workerData: { workerId: i } + }); + workers.push(worker); + workerPool.push({ worker, isIdle: true }); + } + + return workerPool; + } + + private setupWorkerListeners( + worker: ThreadWorker, + workerPool: WorkerInfo[], + jobs: Record, + queues: Job[], + processingJobs: Set, + resolve: () => void, + dependantJobs?: Record + ): void { + worker.on('message', (msg) => { + if (msg.type !== 'TASK_FINISH') { + return; + } + + this.handleTaskCompletion(msg.jobId, worker, workerPool, jobs, queues, processingJobs, dependantJobs); + + if (this.checkAllTasksDone(queues, workerPool)) { + this.terminateWorkers(workerPool); + resolve(); + } + }); + } + + private handleTaskCompletion( + jobId: string, + worker: ThreadWorker, + workerPool: WorkerInfo[], + jobs: Record, + queues: Job[], + processingJobs: Set, + dependantJobs?: Record + ): void { + const workerInfo = workerPool.find((w) => w.worker === worker); + if (workerInfo) { + workerInfo.isIdle = true; + } + processingJobs.delete(jobId); + this.updateQueues(jobs, queues, jobId, dependantJobs); + workerPool.forEach((workerInfo) => { + if (workerInfo.isIdle) { + this.assignTaskToIdleWorker(workerInfo, queues, processingJobs); + } + }); + } + + private terminateWorkers(workerPool: WorkerInfo[]): void { + workerPool.forEach(({ worker }) => { + worker.postMessage({ type: 'EXIT' }); + }); + } + + private assignTaskToIdleWorker(workerInfo: WorkerInfo, queues: Job[], processingJobs: Set): void { + let job: Job | undefined; + let jobInfo: JobInfo | undefined; + + if (queues.length > 0) { + job = queues.shift()!; + jobInfo = { + id: job.id, + filePath: job.fileList[0], + arktsConfigFile: this.entryArkTsConfig, + globalContextPtr: this.globalContextPtr!, + buildConfig: Object.values(this.buildConfigs)[0], + isValid: job.isValid + }; + } + + if (job) { + processingJobs.add(job.id); + workerInfo.worker.postMessage({ type: 'ASSIGN_TASK', jobInfo }); + workerInfo.isIdle = false; + } + } + + // AST caching is not enabled by default. + // Call `initAstCache` before invoking the language service interface to enable AST cache + public async initAstCache(numWorkers: number = 1): Promise { + const jobs: Record = {}; + const queues: Job[] = []; + this.collectCompileJobs(jobs); + this.initGlobalContext(jobs); + this.initCompileQueues(jobs, queues); + + const processingJobs = new Set(); + const workers: ThreadWorker[] = []; + await this.invokeWorkers(jobs, queues, processingJobs, workers, numWorkers); + } + + private compileExternalProgram(jobInfo: JobInfo): void { + PluginDriver.getInstance().initPlugins(jobInfo.buildConfig); + let ets2pandaCmd = ['-', '--extension', 'ets', '--arktsconfig', jobInfo.arktsConfigFile]; + let lspDriverHelper = new LspDriverHelper(); + let config = lspDriverHelper.createCfg(ets2pandaCmd, jobInfo.filePath); + const source = fs.readFileSync(jobInfo.filePath, 'utf8').replace(/\r\n/g, '\n'); + let context = lspDriverHelper.createCtx(source, jobInfo.filePath, config, jobInfo.globalContextPtr, true); + PluginDriver.getInstance().getPluginContext().setContextPtr(context); + lspDriverHelper.proceedToState(context, Es2pandaContextState.ES2PANDA_STATE_PARSED); + PluginDriver.getInstance().runPluginHook(PluginHook.PARSED); + lspDriverHelper.proceedToState(context, Es2pandaContextState.ES2PANDA_STATE_LOWERED); + } + + public addFileCache(filename: String): void { + global.es2pandaPublic._AddFileCache(this.globalContextPtr!, filename); + let jobInfo = { + id: filename.valueOf(), + filePath: filename.valueOf(), + arktsConfigFile: this.entryArkTsConfig, + globalContextPtr: this.globalContextPtr!, + buildConfig: Object.values(this.buildConfigs)[0], + isValid: true + }; + this.compileExternalProgram(jobInfo); + } + + public removeFileCache(filename: String): void { + global.es2pandaPublic._RemoveFileCache(this.globalContextPtr!, filename); + } + + public async updateFileCache(filename: String, numWorkers: number = 1): Promise { + const queues: Job[] = []; + const jobs: Record = {}; + this.collectCompileJobs(jobs, true); + const dependantJobs = this.findJobDependants(jobs, filename.valueOf()); + this.initCompileQueues(jobs, queues, dependantJobs); + const processingJobs = new Set(); + const workers: ThreadWorker[] = []; + await this.invokeWorkers(jobs, queues, processingJobs, workers, numWorkers, dependantJobs); + } + + private findJobDependants(jobs: Record, filePath: string): Record { + const targetJobs = this.findTargetJobs(jobs, filePath); + const { visited, dependantJobs } = this.collectDependantJobs(jobs, targetJobs); + + return this.mergeJobs(targetJobs, dependantJobs); + } + + private findTargetJobs(jobs: Record, filePath: string): Job[] { + return Object.values(jobs).filter( + (job) => job.fileList.includes(filePath) || (job.isInCycle && job.fileList.some((f) => f === filePath)) + ); + } + + private collectDependantJobs( + jobs: Record, + targetJobs: Job[] + ): { visited: Set; dependantJobs: Map } { + const visited = new Set(); + const dependantJobs = new Map(); + const queue: Job[] = []; + + targetJobs.forEach((job) => { + if (!visited.has(job.id)) { + visited.add(job.id); + queue.push(job); + } + + while (queue.length) { + const current = queue.shift()!; + this.processDependants(jobs, current, visited, queue, dependantJobs); + } + }); + + return { visited, dependantJobs }; + } + + private processDependants( + jobs: Record, + current: Job, + visited: Set, + queue: Job[], + dependantJobs: Map + ): void { + current.dependants.forEach((dependantId) => { + const dependantJob = jobs[dependantId]; + if (dependantJob && !visited.has(dependantId)) { + visited.add(dependantId); + queue.push(dependantJob); + dependantJobs.set(dependantId, dependantJob); + } + }); + } + + private mergeJobs(targetJobs: Job[], dependantJobs: Map): Record { + return [...targetJobs, ...dependantJobs.values()].reduce( + (acc, job) => { + acc[job.id] = job; + return acc; + }, + {} as Record + ); + } + + public dispose(): void { + this.globalLspDriverHelper!.destroyGlobalContext(this.globalContextPtr!); + this.globalLspDriverHelper!.destroyConfig(this.globalConfig!); + this.globalLspDriverHelper!.memFinalize(); + } +} + +function createHash(str: string): string { + const hash = crypto.createHash('sha256'); + hash.update(str); + return hash.digest('hex'); +} +// Use AST cache end diff --git a/ets2panda/declgen_ets2ts/declgenEts2Ts.cpp b/ets2panda/declgen_ets2ts/declgenEts2Ts.cpp index 1e9abf56c5..d59a71b141 100644 --- a/ets2panda/declgen_ets2ts/declgenEts2Ts.cpp +++ b/ets2panda/declgen_ets2ts/declgenEts2Ts.cpp @@ -54,7 +54,6 @@ bool TSDeclGen::Generate() } CollectIndirectExportDependencies(); GenDeclarations(); - GenOtherDeclarations(); return true; } @@ -361,6 +360,11 @@ void TSDeclGen::GenImportDeclarations() } } +void TSDeclGen::GenImportRecordDeclarations(const std::string &source) +{ + OutDts("import type { Record } from \"", source, "\";\n"); +} + template void TSDeclGen::GenSeparated(const T &container, const CB &cb, const char *separator, bool isReExport, bool isDtsExport) { @@ -2374,9 +2378,35 @@ bool GenerateTsDeclarations(checker::ETSChecker *checker, const ark::es2panda::p std::string combineEts = importOutputEts + outputEts; std::string combinedDEts = importOutputDEts + outputDEts; - if (!declBuilder.GetDeclgenOptions().outputDeclEts.empty()) { - auto outDtsPath = declBuilder.GetDeclgenOptions().outputDeclEts; - if (!WriteToFile(outDtsPath, combinedDEts, checker)) { + if (!declBuilder.GetDeclgenOptions().recordFile.empty()) { + declBuilder.ResetDtsOutput(); + declBuilder.GenImportRecordDeclarations(declBuilder.GetDeclgenOptions().recordFile); + std::string recordImportOutputDEts = declBuilder.GetDtsOutput(); + } + + return WriteOutputFiles(declBuilder.GetDeclgenOptions(), combineEts, combinedDEts, checker); +} + +bool ValidateDeclgenOptions(const DeclgenOptions &options, checker::ETSChecker *checker) +{ + if ((options.outputDeclEts.empty() && !options.outputEts.empty()) || + (!options.outputDeclEts.empty() && options.outputEts.empty())) { + checker->DiagnosticEngine().LogDiagnostic(diagnostic::GENERATE_DYNAMIC_DECLARATIONS, + util::DiagnosticMessageParams {}); + return false; + } + if (options.outputDeclEts.empty() && options.outputEts.empty()) { + checker->DiagnosticEngine().LogDiagnostic(diagnostic::MISSING_OUTPUT_FILE, util::DiagnosticMessageParams {""}); + return false; + } + return true; +} + +bool WriteOutputFiles(const DeclgenOptions &options, const std::string &combinedEts, const std::string &combinedDEts, + checker::ETSChecker *checker) +{ + if (!options.outputDeclEts.empty()) { + if (!WriteToFile(options.outputDeclEts, combinedDEts, checker)) { return false; } } @@ -2385,7 +2415,6 @@ bool GenerateTsDeclarations(checker::ETSChecker *checker, const ark::es2panda::p auto outTsPath = declBuilder.GetDeclgenOptions().outputEts; if (!WriteToFile(outTsPath, combineEts, checker)) { return false; - } } return true; diff --git a/ets2panda/declgen_ets2ts/declgenEts2Ts.h b/ets2panda/declgen_ets2ts/declgenEts2Ts.h index c5da719aee..2a47d8b7aa 100644 --- a/ets2panda/declgen_ets2ts/declgenEts2Ts.h +++ b/ets2panda/declgen_ets2ts/declgenEts2Ts.h @@ -32,6 +32,7 @@ struct DeclgenOptions { bool isIsolatedDeclgen = false; std::string outputDeclEts; std::string outputEts; + std::string recordFile; }; // Consume program after checker stage and generate out_path typescript file with declarations @@ -64,6 +65,7 @@ public: bool Generate(); void GenImportDeclarations(); + void GenImportRecordDeclarations(const std::string &source); std::string GetDtsOutput() const { @@ -210,7 +212,6 @@ private: void ProcessInterfacesDependencies(const ArenaVector &interfaces); void AddObjectDependencies(const util::StringView &typeName, const std::string &alias = ""); void GenDeclarations(); - void GenOtherDeclarations(); void CloseClassBlock(const bool isDts); void EmitDeclarationPrefix(const ir::ClassDefinition *classDef, const std::string &typeName, diff --git a/ets2panda/driver/build_system/src/build/base_mode.ts b/ets2panda/driver/build_system/src/build/base_mode.ts index 98d8ff042e..530f580064 100644 --- a/ets2panda/driver/build_system/src/build/base_mode.ts +++ b/ets2panda/driver/build_system/src/build/base_mode.ts @@ -27,16 +27,21 @@ import { BUILD_MODE, DEFAULT_WOKER_NUMS, DECL_ETS_SUFFIX, + DECL_TS_SUFFIX, + DEPENDENCY_INPUT_FILE, DEPENDENCY_JSON_FILE, LANGUAGE_VERSION, LINKER_INPUT_FILE, MERGED_ABC_FILE, - TS_SUFFIX, - DEPENDENCY_INPUT_FILE, + MERGED_INTERMEDIATE_FILE, + STATIC_RECORD_FILE, + STATIC_RECORD_FILE_CONTENT, + TS_SUFFIX } from '../pre_define'; import { changeDeclgenFileExtension, changeFileExtension, + createFileIfNotExists, ensurePathExists, getFileHash, isMac @@ -148,6 +153,18 @@ export abstract class BaseMode { const arkts: ArkTS = this.buildConfig.arkts; let errorStatus = false; try { + const staticRecordPath = path.join( + moduleInfo.declgenV1OutPath as string, + STATIC_RECORD_FILE + ) + const declEtsOutputDir = path.dirname(declEtsOutputPath); + const staticRecordRelativePath = changeFileExtension( + path.relative(declEtsOutputDir, staticRecordPath).replaceAll(/\\/g, '\/'), + "", + DECL_TS_SUFFIX + ); + createFileIfNotExists(staticRecordPath, STATIC_RECORD_FILE_CONTENT); + arktsGlobal.filePath = fileInfo.filePath; arktsGlobal.config = arkts.Config.create([ '_', @@ -175,7 +192,8 @@ export abstract class BaseMode { arkts.generateTsDeclarationsFromContext( declEtsOutputPath, etsOutputPath, - false + false, + staticRecordRelativePath ); // Generate 1.0 declaration files & 1.0 glue code this.logger.printInfo('declaration files generated'); } catch (error) { diff --git a/ets2panda/driver/build_system/src/build/declgen_worker.ts b/ets2panda/driver/build_system/src/build/declgen_worker.ts index 135acab0dd..2e84ce4f79 100644 --- a/ets2panda/driver/build_system/src/build/declgen_worker.ts +++ b/ets2panda/driver/build_system/src/build/declgen_worker.ts @@ -18,11 +18,19 @@ import { BuildConfig } from '../types'; import { Logger } from '../logger'; import * as fs from 'fs'; import * as path from 'path'; -import { changeFileExtension, ensurePathExists } from '../utils'; -import { - DECL_ETS_SUFFIX, - TS_SUFFIX, - KOALA_WRAPPER_PATH_FROM_SDK +import { + changeDeclgenFileExtension, + changeFileExtension, + createFileIfNotExists, + ensurePathExists +} from '../utils'; +import { + DECL_ETS_SUFFIX, + DECL_TS_SUFFIX, + KOALA_WRAPPER_PATH_FROM_SDK, + STATIC_RECORD_FILE, + STATIC_RECORD_FILE_CONTENT, + TS_SUFFIX } from '../pre_define'; import { PluginDriver, PluginHook } from '../plugins/plugins_driver'; @@ -66,6 +74,18 @@ process.on('message', (message: { ensurePathExists(declEtsOutputPath); ensurePathExists(etsOutputPath); + const staticRecordPath = path.join( + moduleInfo.declgenV1OutPath as string, + STATIC_RECORD_FILE + ) + const declEtsOutputDir = path.dirname(declEtsOutputPath); + const staticRecordRelativePath = changeFileExtension( + path.relative(declEtsOutputDir, staticRecordPath).replaceAll(/\\/g, '\/'), + "", + DECL_TS_SUFFIX + ); + createFileIfNotExists(staticRecordPath, STATIC_RECORD_FILE_CONTENT); + arktsGlobal.filePath = fileInfo.filePath; arktsGlobal.config = arkts.Config.create([ '_', @@ -93,7 +113,8 @@ process.on('message', (message: { arkts.generateTsDeclarationsFromContext( declEtsOutputPath, etsOutputPath, - false + false, + staticRecordRelativePath ); // Generate 1.0 declaration files & 1.0 glue code logger.printInfo('declaration files generated'); diff --git a/ets2panda/driver/build_system/src/pre_define.ts b/ets2panda/driver/build_system/src/pre_define.ts index a51032f4a7..081cf4dc74 100644 --- a/ets2panda/driver/build_system/src/pre_define.ts +++ b/ets2panda/driver/build_system/src/pre_define.ts @@ -19,8 +19,10 @@ export const LINKER_INPUT_FILE: string = 'fileInfo.txt'; export const DEPENDENCY_INPUT_FILE: string = 'dependencyFileInfo.txt'; export const DEPENDENCY_JSON_FILE: string = 'dependency.json'; export const PROJECT_BUILD_CONFIG_FILE: string = 'projectionConfig.json'; +export const STATIC_RECORD_FILE: string = 'static.Record.d.ts'; export const DECL_ETS_SUFFIX: string = '.d.ets'; +export const DECL_TS_SUFFIX: string = '.d.ts'; export const ETS_SUFFIX: string = '.ets'; export const TS_SUFFIX: string = '.ts'; export const ABC_SUFFIX: string = '.abc'; @@ -44,4 +46,10 @@ export const KOALA_WRAPPER_PATH_FROM_SDK: string = './build-tools/koala-wrapper/ export const DEFAULT_WOKER_NUMS: number = 4; export const ETS_1_1 = 'ets1.1'; -export const ETS_1_1_INTEROP = 'ets1.1interop'; \ No newline at end of file +export const ETS_1_1_INTEROP = 'ets1.1interop'; + +export const STATIC_RECORD_FILE_CONTENT: string = `// generated for static Record +export type Record = { + [P in K]: T; +}; +`; diff --git a/ets2panda/driver/build_system/src/utils.ts b/ets2panda/driver/build_system/src/utils.ts index 45698b054b..4f6d923f43 100644 --- a/ets2panda/driver/build_system/src/utils.ts +++ b/ets2panda/driver/build_system/src/utils.ts @@ -88,3 +88,22 @@ export function isSubPathOf(targetPath: string, parentDir: string): boolean { const resolvedTarget = toUnixPath(path.resolve(targetPath)); return resolvedTarget === resolvedParent || resolvedTarget.startsWith(resolvedParent + '/'); } + +export function createFileIfNotExists(filePath: string, content: string): boolean { + try { + const normalizedPath = path.normalize(filePath); + if (fs.existsSync(normalizedPath)) { + return false; + } + + const dir = path.dirname(normalizedPath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + + fs.writeFileSync(normalizedPath, content, { encoding: 'utf-8' }); + return true; + } catch (error) { + return false; + } +} diff --git a/ets2panda/public/es2panda_lib.cpp b/ets2panda/public/es2panda_lib.cpp index 7d1405550e..7fa5e72b7a 100644 --- a/ets2panda/public/es2panda_lib.cpp +++ b/ets2panda/public/es2panda_lib.cpp @@ -1104,7 +1104,8 @@ extern "C" es2panda_AstNode **AllDeclarationsByNameFromProgram([[maybe_unused]] extern "C" __attribute__((unused)) int GenerateTsDeclarationsFromContext(es2panda_Context *ctx, const char *outputDeclEts, - const char *outputEts, bool exportAll) + const char *outputEts, bool exportAll, + const char *recordFile) { auto *ctxImpl = reinterpret_cast(ctx); auto *checker = reinterpret_cast(ctxImpl->checker); @@ -1113,6 +1114,7 @@ extern "C" __attribute__((unused)) int GenerateTsDeclarationsFromContext(es2pand declgenOptions.exportAll = exportAll; declgenOptions.outputDeclEts = outputDeclEts ? outputDeclEts : ""; declgenOptions.outputEts = outputEts ? outputEts : ""; + declgenOptions.recordFile = recordFile ? recordFile : ""; return ark::es2panda::declgen_ets2ts::GenerateTsDeclarations(checker, ctxImpl->parserProgram, declgenOptions) ? 0 : 1; diff --git a/ets2panda/public/es2panda_lib.h b/ets2panda/public/es2panda_lib.h index 84d44b022e..cdca113708 100644 --- a/ets2panda/public/es2panda_lib.h +++ b/ets2panda/public/es2panda_lib.h @@ -255,7 +255,7 @@ struct CAPI_EXPORT es2panda_Impl { const char *name, size_t *declsLen); int (*GenerateTsDeclarationsFromContext)(es2panda_Context *context, const char *outputDeclEts, - const char *outputEts, bool exportAll); + const char *outputEts, bool exportAll, const char *recordFile); void (*InsertETSImportDeclarationAndParse)(es2panda_Context *context, es2panda_Program *program, es2panda_AstNode *importDeclaration); int (*GenerateStaticDeclarationsFromContext)(es2panda_Context *context, const char *outputPath); diff --git a/ets2panda/test/unit/declgen/test_ets2ts_isolated_declgen.cpp b/ets2panda/test/unit/declgen/test_ets2ts_isolated_declgen.cpp new file mode 100644 index 0000000000..a2049a3800 --- /dev/null +++ b/ets2panda/test/unit/declgen/test_ets2ts_isolated_declgen.cpp @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include "os/library_loader.h" + +#include "public/es2panda_lib.h" +#include "./util.h" + +// NOLINTBEGIN + +static es2panda_Impl *impl = nullptr; + +int main(int argc, char **argv) +{ + if (argc < MIN_ARGC) { + return INVALID_ARGC_ERROR_CODE; + } + + if (GetImpl() == nullptr) { + return NULLPTR_IMPL_ERROR_CODE; + } + impl = GetImpl(); + std::cout << "LOAD SUCCESS" << std::endl; + + const char **args = const_cast(&(argv[1])); + auto config = impl->CreateConfig(argc - 1, args); + auto context = impl->CreateContextFromFile(config, argv[argc - 1]); + if (context == nullptr) { + std::cerr << "FAILED TO CREATE CONTEXT" << std::endl; + return NULLPTR_CONTEXT_ERROR_CODE; + } + + impl->ProceedToState(context, ES2PANDA_STATE_CHECKED); + CheckForErrors("CHECKED", context); + std::string declName = GetDeclPrefix(argv[argc - 1]) + ".d.ets"; + int result = impl->GenerateTsDeclarationsFromContext(context, declName.c_str(), "dump.ets", false, true, ""); + if (result != 0) { + std::cerr << "FAILED TO GENERATE DECLARATIONS" << std::endl; + return result; + } + + impl->DestroyConfig(config); + + return 0; +} + +// NOLINTEND -- Gitee From 51636540e5b6700b87d8f214f912da8ec15185d6 Mon Sep 17 00:00:00 2001 From: Amosov Alexey Date: Wed, 23 Jul 2025 13:07:23 +0300 Subject: [PATCH 6/6] Fix floating, integral, static accessor Issue: https://gitee.com/openharmony/arkcompiler_runtime_core/issues/ICOECJ Description: Replace Floating with number, Integral with number. Fix declgen static generic accessor Update ignore list Signed-off-by: Amosov Alexey Change-Id: Iad8cea38a63486f2ec7f502644fc6cb65ef32ded --- ets2panda/bindings/src/lsp/lsp_helper.ts | 1314 ----------------- ets2panda/declgen_ets2ts/declgenEts2Ts.cpp | 62 +- ets2panda/declgen_ets2ts/declgenEts2Ts.h | 5 +- .../build_system/src/build/base_mode.ts | 12 +- .../build_system/src/build/declgen_worker.ts | 2 +- ets2panda/driver/build_system/src/utils.ts | 10 +- .../declgen-ets2ts-runtime-ignored.txt | 2 + .../declgen/test_ets2ts_isolated_declgen.cpp | 63 - 8 files changed, 51 insertions(+), 1419 deletions(-) delete mode 100644 ets2panda/bindings/src/lsp/lsp_helper.ts delete mode 100644 ets2panda/test/unit/declgen/test_ets2ts_isolated_declgen.cpp diff --git a/ets2panda/bindings/src/lsp/lsp_helper.ts b/ets2panda/bindings/src/lsp/lsp_helper.ts deleted file mode 100644 index b3e2bcc237..0000000000 --- a/ets2panda/bindings/src/lsp/lsp_helper.ts +++ /dev/null @@ -1,1314 +0,0 @@ -/* - * Copyright (c) 2025 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { LspDriverHelper } from '../common/driver_helper'; -import { global } from '../common/global'; -import { - LspDefinitionData, - LspDiagsNode, - LspReferences, - LspQuickInfo, - LspClassHierarchy, - LspCompletionEntryKind, - LspClassPropertyInfo, - LspClassHierarchies, - LspDocumentHighlightsReferences, - LspCompletionInfo, - LspReferenceLocationList, - LspLineAndCharacter, - LspReferenceData, - LspClassConstructorInfo, - ApplicableRefactorItemInfo, - LspApplicableRefactorInfo, - CompletionEntryDetails, - LspFileTextChanges, - LspSafeDeleteLocationInfo, - LspSafeDeleteLocation, - LspTypeHierarchiesInfo, - LspTextSpan, - LspInlayHint, - LspInlayHintList, - TextSpan, - LspSignatureHelpItems, - CodeFixActionInfo, - CodeFixActionInfoList, - LspRenameLocation, - LspRenameInfoType, - LspRenameInfoSuccess, - LspRenameInfoFailure -} from './lspNode'; -import { passStringArray, unpackString } from '../common/private'; -import { Es2pandaContextState } from '../generated/Es2pandaEnums'; -import { - BuildConfig, - Config, - FileDepsInfo, - Job, - JobInfo, - WorkerInfo, - ModuleInfo, - PathConfig, - TextDocumentChangeInfo -} from '../common/types'; -import { PluginDriver, PluginHook } from '../common/ui_plugins_driver'; -import { ModuleDescriptor, generateBuildConfigs } from './generateBuildConfig'; -import { generateArkTsConfigs, generateModuleInfo } from './generateArkTSConfig'; - -import * as fs from 'fs'; -import * as path from 'path'; -import { KInt, KNativePointer, KPointer } from '../common/InteropTypes'; -import { passPointerArray } from '../common/private'; -import { NativePtrDecoder } from '../common/Platform'; -import { Worker as ThreadWorker } from 'worker_threads'; -import { ensurePathExists } from '../common/utils'; -import * as child_process from 'child_process'; -import { DECL_ETS_SUFFIX, DEFAULT_CACHE_DIR, TS_SUFFIX } from '../common/preDefine'; -import * as crypto from 'crypto'; -import * as os from 'os'; -import { changeDeclgenFileExtension, getModuleNameAndPath } from '../common/utils'; - -const ets2pandaCmdPrefix = ['-', '--extension', 'ets', '--arktsconfig']; - -function initBuildEnv(): void { - const currentPath: string | undefined = process.env.PATH; - let pandaLibPath: string = process.env.PANDA_LIB_PATH - ? process.env.PANDA_LIB_PATH - : path.resolve(__dirname, '../../../ets2panda/lib'); - process.env.PATH = `${currentPath}${path.delimiter}${pandaLibPath}`; -} - -export class Lsp { - private pandaLibPath: string; - private pandaBinPath: string; - private getFileContent: (filePath: string) => string; - private filesMap: Map; // Map - private cacheDir: string; - private globalContextPtr?: KNativePointer; - private globalConfig?: Config; - private globalLspDriverHelper?: LspDriverHelper; - private entryArkTsConfig: string; - private fileDependencies: string; - private buildConfigs: Record; // Map - private moduleInfos: Record; // Map - private pathConfig: PathConfig; - private lspDriverHelper = new LspDriverHelper(); - - constructor(pathConfig: PathConfig, getContentCallback?: (filePath: string) => string, modules?: ModuleDescriptor[]) { - initBuildEnv(); - this.cacheDir = - pathConfig.cacheDir !== undefined ? pathConfig.cacheDir : path.join(pathConfig.projectPath, DEFAULT_CACHE_DIR); - this.fileDependencies = path.join(this.cacheDir, 'file_dependencies.json'); - this.entryArkTsConfig = path.join(this.cacheDir, 'entry', 'arktsconfig.json'); - this.pandaLibPath = process.env.PANDA_LIB_PATH - ? process.env.PANDA_LIB_PATH - : path.resolve(__dirname, '../../../ets2panda/lib'); - this.pandaBinPath = process.env.PANDA_BIN_PATH - ? process.env.PANDA_BIN_PATH - : path.resolve(__dirname, '../../../ets2panda/bin'); - this.filesMap = new Map(); - this.getFileContent = getContentCallback || ((path: string): string => fs.readFileSync(path, 'utf8')); - this.buildConfigs = generateBuildConfigs(pathConfig, modules); - this.moduleInfos = generateArkTsConfigs(this.buildConfigs); - this.pathConfig = pathConfig; - PluginDriver.getInstance().initPlugins(Object.values(this.buildConfigs)[0]); - this.generateDeclFile(); - } - - // Partially update for new file - updateModuleInfos(module: ModuleDescriptor, newFilePath: String): void { - let buildConfig = this.buildConfigs[module.name]; - buildConfig.compileFiles.push(newFilePath.valueOf()); - let moduleInfo = generateModuleInfo(this.buildConfigs, buildConfig); - this.moduleInfos[newFilePath.valueOf()] = moduleInfo; - } - - // Full update for `Sync Now` - update(modules: ModuleDescriptor[]): void { - this.buildConfigs = generateBuildConfigs(this.pathConfig, modules); - this.moduleInfos = generateArkTsConfigs(this.buildConfigs); - } - - modifyFilesMap(fileName: string, fileContent: TextDocumentChangeInfo): void { - this.filesMap.set(fileName, fileContent.newDoc); - } - - deleteFromFilesMap(fileName: string): void { - this.filesMap.delete(fileName); - } - - private getFileSource(filePath: string): string { - const getSource = this.filesMap.get(filePath) || this.getFileContent(filePath) || fs.readFileSync(filePath, 'utf8'); - if (getSource === undefined) { - throw new Error(`File content not found for path: ${filePath}`); - } - return getSource.replace(/\r\n/g, '\n'); - } - - private createContext(filename: String): [Config, KNativePointer] { - const filePath = path.resolve(filename.valueOf()); - const arktsconfig = this.moduleInfos[filePath]?.arktsConfigFile; - if (!arktsconfig) { - throw new Error(`Missing arktsconfig for ${filePath}`); - } - - const ets2pandaCmd = [...ets2pandaCmdPrefix, arktsconfig]; - const localCfg = this.lspDriverHelper.createCfg(ets2pandaCmd, filePath, this.pandaLibPath); - const source = this.getFileSource(filePath); - - const localCtx = this.lspDriverHelper.createCtx(source, filePath, localCfg, this.globalContextPtr); - try { - const packageName = this.moduleInfos[filePath].packageName; - const buildConfig = this.buildConfigs[packageName]; - const pluginContext = PluginDriver.getInstance().getPluginContext(); - pluginContext.setCodingFilePath(filePath); - pluginContext.setProjectConfig(buildConfig); - pluginContext.setContextPtr(localCtx); - - this.lspDriverHelper.proceedToState(localCtx, Es2pandaContextState.ES2PANDA_STATE_PARSED); - PluginDriver.getInstance().runPluginHook(PluginHook.PARSED); - - this.lspDriverHelper.proceedToState(localCtx, Es2pandaContextState.ES2PANDA_STATE_CHECKED); - return [localCfg, localCtx]; - } catch (error) { - this.lspDriverHelper.destroyContext(localCtx); - this.lspDriverHelper.destroyConfig(localCfg); - throw error; - } - } - - private destroyContext(config: Config, context: KNativePointer): void { - try { - PluginDriver.getInstance().runPluginHook(PluginHook.CLEAN); - } finally { - this.lspDriverHelper.destroyContext(context); - this.lspDriverHelper.destroyConfig(config); - } - } - - generateDeclFile(): void { - for (const [moduleName, buildConfig] of Object.entries(this.buildConfigs)) { - if (!buildConfig.enableDeclgenEts2Ts) { - continue; - } - if (!buildConfig.declgenOutDir || buildConfig.declgenOutDir === '') { - return; - } - buildConfig.compileFiles.forEach((compilefilePath: string) => { - if (!this.moduleInfos.hasOwnProperty(compilefilePath)) { - return; - } - const [cfg, ctx] = this.createContext(compilefilePath); - try { - // declgen file - let moduleInfo = this.moduleInfos[compilefilePath]; - let modulePath: string = path.relative(buildConfig.moduleRootPath, compilefilePath); - let declOut: string = ''; - let declBridgeOut: string = ''; - if (!moduleInfo.declgenV1OutPath) { - declOut = path.join(buildConfig.declgenOutDir, moduleName); - } - if (!moduleInfo.declgenBridgeCodePath) { - declBridgeOut = path.join(buildConfig.declgenOutDir, moduleName); - } - let declEtsOutputPath: string = changeDeclgenFileExtension( - path.join(moduleInfo.declgenV1OutPath ?? declOut, modulePath), - DECL_ETS_SUFFIX - ); - let etsOutputPath: string = changeDeclgenFileExtension( - path.join(moduleInfo.declgenBridgeCodePath ?? declBridgeOut, modulePath), - TS_SUFFIX - ); - ensurePathExists(declEtsOutputPath); - ensurePathExists(etsOutputPath); - global.es2pandaPublic._GenerateTsDeclarationsFromContext(ctx, declEtsOutputPath, etsOutputPath, 1, ''); - } finally { - this.destroyContext(cfg, ctx); - } - }); - } - } - - modifyDeclFile(modifyFilePath: string, arktsConfigFile?: string): void { - // source file - let sourceFilePath = path.resolve(modifyFilePath.valueOf()); - let moduleInfo: ModuleInfo; - if (this.moduleInfos.hasOwnProperty(sourceFilePath)) { - moduleInfo = this.moduleInfos[sourceFilePath]; - } else { - const [newModuleName, newModuleRootPath] = getModuleNameAndPath(modifyFilePath, this.pathConfig.projectPath); - if (newModuleName && newModuleName !== '' && newModuleRootPath && newModuleRootPath !== '') { - moduleInfo = { - packageName: newModuleName, - moduleRootPath: newModuleRootPath, - moduleType: '', - entryFile: '', - arktsConfigFile: arktsConfigFile ?? '', - compileFiles: [], - declgenV1OutPath: '', - declgenBridgeCodePath: '', - staticDepModuleInfos: [], - dynamicDepModuleInfos: [], - language: '' - }; - } else { - return; - } - } - const moduleName = moduleInfo.packageName; - const moduleRootPath = moduleInfo.moduleRootPath; - if (!this.buildConfigs.hasOwnProperty(moduleName)) { - return; - } - const buildConfig = this.buildConfigs[moduleName]; - if (!buildConfig.enableDeclgenEts2Ts) { - return; - } - const [cfg, ctx] = this.createContext(sourceFilePath); - try { - // declgen file - let declOut: string = ''; - let declBridgeOut: string = ''; - if (!moduleInfo.declgenV1OutPath) { - declOut = path.join(buildConfig.declgenOutDir, moduleName); - } - if (!moduleInfo.declgenBridgeCodePath) { - declBridgeOut = path.join(buildConfig.declgenOutDir, moduleName); - } - let filePathFromModuleRoot: string = path.relative(moduleRootPath, modifyFilePath); - let declEtsOutputPath: string = changeDeclgenFileExtension( - path.join(moduleInfo.declgenV1OutPath ?? declOut, filePathFromModuleRoot), - DECL_ETS_SUFFIX - ); - let etsOutputPath: string = changeDeclgenFileExtension( - path.join(moduleInfo.declgenBridgeCodePath ?? declBridgeOut, filePathFromModuleRoot), - TS_SUFFIX - ); - ensurePathExists(declEtsOutputPath); - ensurePathExists(etsOutputPath); - global.es2pandaPublic._GenerateTsDeclarationsFromContext(ctx, declEtsOutputPath, etsOutputPath, 1, ''); - } finally { - this.destroyContext(cfg, ctx); - } - } - - getOffsetByColAndLine(filename: String, line: number, column: number): number { - let ptr: KPointer; - const [cfg, ctx] = this.createContext(filename); - try { - ptr = global.es2panda._getOffsetByColAndLine(ctx, line, column); - } finally { - this.destroyContext(cfg, ctx); - } - return ptr; - } - - getDefinitionAtPosition(filename: String, offset: number): LspDefinitionData { - let ptr: KPointer; - const [cfg, ctx] = this.createContext(filename); - try { - ptr = global.es2panda._getDefinitionAtPosition(ctx, offset); - } finally { - this.destroyContext(cfg, ctx); - } - return new LspDefinitionData(ptr); - } - - getSemanticDiagnostics(filename: String): LspDiagsNode { - let ptr: KPointer; - const [cfg, ctx] = this.createContext(filename); - try { - ptr = global.es2panda._getSemanticDiagnostics(ctx); - } finally { - this.destroyContext(cfg, ctx); - } - return new LspDiagsNode(ptr); - } - - getCurrentTokenValue(filename: String, offset: number): string { - let ptr: KPointer; - const [cfg, ctx] = this.createContext(filename); - try { - ptr = global.es2panda._getCurrentTokenValue(ctx, offset); - } finally { - this.destroyContext(cfg, ctx); - } - return unpackString(ptr); - } - - getImplementationAtPosition(filename: String, offset: number): LspDefinitionData { - let ptr: KPointer; - const [cfg, ctx] = this.createContext(filename); - try { - ptr = global.es2panda._getImplementationAtPosition(ctx, offset); - } finally { - this.destroyContext(cfg, ctx); - } - return new LspDefinitionData(ptr); - } - - getFileReferences(filename: String): LspReferenceData[] { - let isPackageModule: boolean; - const [cfg, searchCtx] = this.createContext(filename); - try { - isPackageModule = global.es2panda._isPackageModule(searchCtx); - } finally { - this.destroyContext(cfg, searchCtx); - } - let result: LspReferenceData[] = []; - let compileFiles = this.moduleInfos[path.resolve(filename.valueOf())].compileFiles; - for (let i = 0; i < compileFiles.length; i++) { - let ptr: KPointer; - const [cfg, ctx] = this.createContext(compileFiles[i]); - try { - ptr = global.es2panda._getFileReferences(path.resolve(filename.valueOf()), ctx, isPackageModule); - } finally { - this.destroyContext(cfg, ctx); - } - let refs = new LspReferences(ptr); - for (let j = 0; j < refs.referenceInfos.length; j++) { - if (refs.referenceInfos[j].fileName !== '') { - result.push(refs.referenceInfos[j]); - } - } - } - return result; - } - - getReferencesAtPosition(filename: String, offset: number): LspReferenceData[] { - let declInfo: KPointer; - const [cfg, searchCtx] = this.createContext(filename); - try { - declInfo = global.es2panda._getDeclInfo(searchCtx, offset); - } finally { - this.destroyContext(cfg, searchCtx); - } - let result: LspReferenceData[] = []; - let compileFiles = this.moduleInfos[path.resolve(filename.valueOf())].compileFiles; - for (let i = 0; i < compileFiles.length; i++) { - let ptr: KPointer; - const [cfg, ctx] = this.createContext(compileFiles[i]); - try { - ptr = global.es2panda._getReferencesAtPosition(ctx, declInfo); - } finally { - this.destroyContext(cfg, ctx); - } - let refs = new LspReferences(ptr); - result.push(...refs.referenceInfos); - } - return Array.from(new Set(result)); - } - - getTypeHierarchies(filename: String, offset: number): LspTypeHierarchiesInfo | null { - let ptr: KPointer; - const [cfg, ctx] = this.createContext(filename); - ptr = global.es2panda._getTypeHierarchies(ctx, ctx, offset); - let ref = new LspTypeHierarchiesInfo(ptr); - if (ref.fileName === '') { - this.destroyContext(cfg, ctx); - return null; - } - let result: LspTypeHierarchiesInfo[] = []; - let compileFiles = this.moduleInfos[path.resolve(filename.valueOf())].compileFiles; - for (let i = 0; i < compileFiles.length; i++) { - let searchPtr: KPointer; - const [cfg, searchCtx] = this.createContext(compileFiles[i]); - try { - searchPtr = global.es2panda._getTypeHierarchies(searchCtx, ctx, offset); - } finally { - this.destroyContext(cfg, searchCtx); - } - let refs = new LspTypeHierarchiesInfo(searchPtr); - if (i > 0) { - result[0].subHierarchies.subOrSuper = result[0].subHierarchies.subOrSuper.concat( - refs.subHierarchies.subOrSuper - ); - } else { - result.push(refs); - } - } - for (let j = 0; j < result[0].subHierarchies.subOrSuper.length; j++) { - let res = this.getTypeHierarchies( - result[0].subHierarchies.subOrSuper[j].fileName, - result[0].subHierarchies.subOrSuper[j].pos - ); - if (res !== null) { - let subOrSuperTmp = result[0].subHierarchies.subOrSuper[j].subOrSuper.concat(res.subHierarchies.subOrSuper); - result[0].subHierarchies.subOrSuper[j].subOrSuper = Array.from( - new Map( - subOrSuperTmp.map((item) => [`${item.fileName}-${item.type}-${item.pos}-${item.name}`, item]) - ).values() - ); - } - } - return result[0]; - } - - getClassHierarchyInfo(filename: String, offset: number): LspClassHierarchy { - let ptr: KPointer; - const [cfg, ctx] = this.createContext(filename); - try { - ptr = global.es2panda._getClassHierarchyInfo(ctx, offset); - } finally { - this.destroyContext(cfg, ctx); - } - return new LspClassHierarchy(ptr); - } - - getAliasScriptElementKind(filename: String, offset: number): LspCompletionEntryKind { - let kind: KInt; - const [cfg, ctx] = this.createContext(filename); - try { - kind = global.es2panda._getAliasScriptElementKind(ctx, offset); - } finally { - this.destroyContext(cfg, ctx); - } - return kind; - } - - getClassHierarchies(filename: String, offset: number): LspClassHierarchies { - let contextList = []; - const [cfg, ctx] = this.createContext(filename); - contextList.push({ ctx: ctx, cfg: cfg }); - let nativeContextList = global.es2panda._pushBackToNativeContextVector(ctx, ctx, 1); - let compileFiles = this.moduleInfos[path.resolve(filename.valueOf())].compileFiles; - for (let i = 0; i < compileFiles.length; i++) { - let filePath = path.resolve(compileFiles[i]); - if (path.resolve(filename.valueOf()) === filePath) { - continue; - } - const [searchCfg, searchCtx] = this.createContext(filePath); - 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); - for (const { ctx, cfg } of contextList) { - this.destroyContext(cfg, ctx); - } - return new LspClassHierarchies(ptr); - } - - getClassPropertyInfo( - filename: String, - offset: number, - shouldCollectInherited: boolean = false - ): LspClassPropertyInfo { - let ptr: KPointer; - const [cfg, ctx] = this.createContext(filename); - try { - ptr = global.es2panda._getClassPropertyInfo(ctx, offset, shouldCollectInherited); - } finally { - this.destroyContext(cfg, ctx); - } - return new LspClassPropertyInfo(ptr); - } - - getOrganizeImports(filename: String): LspFileTextChanges { - let ptr: KPointer; - const [cfg, ctx] = this.createContext(filename); - try { - ptr = global.es2panda._organizeImports(ctx, filename); - PluginDriver.getInstance().runPluginHook(PluginHook.CLEAN); - } finally { - this.destroyContext(cfg, ctx); - } - return new LspFileTextChanges(ptr); - } - - findSafeDeleteLocation(filename: String, offset: number): LspSafeDeleteLocationInfo[] { - let declInfo: KPointer; - const [cfg, ctx] = this.createContext(filename); - try { - declInfo = global.es2panda._getDeclInfo(ctx, offset); - } finally { - this.destroyContext(cfg, ctx); - } - let result: LspSafeDeleteLocationInfo[] = []; - let compileFiles = this.moduleInfos[path.resolve(filename.valueOf())].compileFiles; - for (let i = 0; i < compileFiles.length; i++) { - let ptr: KPointer; - const [searchCfg, searchCtx] = this.createContext(compileFiles[i]); - try { - ptr = global.es2panda._findSafeDeleteLocation(searchCtx, declInfo); - } finally { - this.destroyContext(searchCfg, searchCtx); - } - let refs = new LspSafeDeleteLocation(ptr); - result.push(...refs.safeDeleteLocationInfos); - } - return Array.from(new Set(result)); - } - - getCompletionEntryDetails(filename: String, offset: number, entryName: String): CompletionEntryDetails { - let ptr: KPointer; - const [cfg, ctx] = this.createContext(filename); - try { - ptr = global.es2panda._getCompletionEntryDetails(entryName, filename, ctx, offset); - } finally { - this.destroyContext(cfg, ctx); - } - return new CompletionEntryDetails(ptr); - } - - getApplicableRefactors(filename: String, kind: String, offset: number): ApplicableRefactorItemInfo[] { - let ptr: KPointer; - let result: ApplicableRefactorItemInfo[] = []; - const [cfg, ctx] = this.createContext(filename); - try { - ptr = global.es2panda._getApplicableRefactors(ctx, kind, offset); - } finally { - this.destroyContext(cfg, ctx); - } - let refs = new LspApplicableRefactorInfo(ptr); - result.push(...refs.applicableRefactorInfo); - return Array.from(new Set(result)); - } - - getClassConstructorInfo(filename: String, offset: number, properties: string[]): LspClassConstructorInfo { - let ptr: KPointer; - const [cfg, ctx] = this.createContext(filename); - try { - ptr = global.es2panda._getClassConstructorInfo(ctx, offset, passStringArray(properties)); - } finally { - this.destroyContext(cfg, ctx); - } - return new LspClassConstructorInfo(ptr); - } - - getSyntacticDiagnostics(filename: String): LspDiagsNode { - let ptr: KPointer; - const [cfg, ctx] = this.createContext(filename); - try { - ptr = global.es2panda._getSyntacticDiagnostics(ctx); - } finally { - this.destroyContext(cfg, ctx); - } - return new LspDiagsNode(ptr); - } - - getSuggestionDiagnostics(filename: String): LspDiagsNode { - let ptr: KPointer; - const [cfg, ctx] = this.createContext(filename); - try { - ptr = global.es2panda._getSuggestionDiagnostics(ctx); - } finally { - this.destroyContext(cfg, ctx); - } - return new LspDiagsNode(ptr); - } - - getQuickInfoAtPosition(filename: String, offset: number): LspQuickInfo { - let ptr: KPointer; - const [cfg, ctx] = this.createContext(filename); - try { - ptr = global.es2panda._getQuickInfoAtPosition(filename, ctx, offset); - } finally { - this.destroyContext(cfg, ctx); - } - return new LspQuickInfo(ptr); - } - - getDocumentHighlights(filename: String, offset: number): LspDocumentHighlightsReferences { - let ptr: KPointer; - const [cfg, ctx] = this.createContext(filename); - try { - ptr = global.es2panda._getDocumentHighlights(ctx, offset); - } finally { - this.destroyContext(cfg, ctx); - } - return new LspDocumentHighlightsReferences(ptr); - } - - getCompletionAtPosition(filename: String, offset: number): LspCompletionInfo { - let lspDriverHelper = new LspDriverHelper(); - let filePath = path.resolve(filename.valueOf()); - let arktsconfig = this.moduleInfos[filePath].arktsConfigFile; - let ets2pandaCmd = ets2pandaCmdPrefix.concat(arktsconfig); - let localCfg = lspDriverHelper.createCfg(ets2pandaCmd, filePath, this.pandaLibPath); - let source = this.getFileSource(filePath); - // This is a temporary solution to support "obj." with wildcard for better solution in internal issue. - if (source[offset - 1] === '.') { - const wildcard = '_WILDCARD'; - if (offset < source.length + 1) { - source = source.slice(0, offset) + wildcard + source.slice(offset); - } else { - source += wildcard; - } - offset += wildcard.length; - } - let localCtx = lspDriverHelper.createCtx(source, filePath, localCfg, this.globalContextPtr); - const packageName = this.moduleInfos[filePath].packageName; - const buildConfig = this.buildConfigs[packageName]; - PluginDriver.getInstance().getPluginContext().setCodingFilePath(filePath); - PluginDriver.getInstance().getPluginContext().setProjectConfig(buildConfig); - 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._getCompletionAtPosition(localCtx, offset); - PluginDriver.getInstance().runPluginHook(PluginHook.CLEAN); - lspDriverHelper.destroyContext(localCtx); - lspDriverHelper.destroyConfig(localCfg); - return new LspCompletionInfo(ptr); - } - - toLineColumnOffset(filename: String, offset: number): LspLineAndCharacter { - let ptr: KPointer; - const [cfg, ctx] = this.createContext(filename); - try { - ptr = global.es2panda._toLineColumnOffset(ctx, offset); - } finally { - this.destroyContext(cfg, ctx); - } - return new LspLineAndCharacter(ptr); - } - - getSafeDeleteInfo(filename: String, position: number): boolean { - let result: boolean; - const [cfg, ctx] = this.createContext(filename); - try { - result = global.es2panda._getSafeDeleteInfo(ctx, position); - } finally { - this.destroyContext(cfg, ctx); - } - return result; - } - - findRenameLocations(filename: String, offset: number): LspRenameLocation[] { - let compileFiles = this.moduleInfos[path.resolve(filename.valueOf())].compileFiles; - const fileContexts: KPointer[] = []; - const fileConfigs: Config[] = []; - for (let i = 0; i < compileFiles.length; i++) { - const [compileFileCfg, compileFileCtx] = this.createContext(compileFiles[i]); - fileContexts.push(compileFileCtx); - fileConfigs.push(compileFileCfg); - } - const [cfg, ctx] = this.createContext(filename); - const ptr = global.es2panda._findRenameLocations(fileContexts.length, passPointerArray(fileContexts), ctx, offset); - const result: LspRenameLocation[] = new NativePtrDecoder().decode(ptr).map((elPeer: KPointer) => { - return new LspRenameLocation(elPeer); - }); - for (let i = 0; i < fileContexts.length; i++) { - this.destroyContext(fileConfigs[i], fileContexts[i]); - } - this.destroyContext(cfg, ctx); - return Array.from(new Set(result)); - } - - getRenameInfo(filename: String, offset: number): LspRenameInfoType { - let ptr: KPointer; - let res: LspRenameInfoType; - const [cfg, ctx] = this.createContext(filename); - try { - ptr = global.es2panda._getRenameInfo(ctx, offset, this.pandaLibPath); - const success = global.es2panda._getRenameInfoIsSuccess(ptr); - if (success) { - res = new LspRenameInfoSuccess(global.es2panda._getRenameInfoSuccess(ptr)); - } else { - res = new LspRenameInfoFailure(global.es2panda._getRenameInfoFailure(ptr)); - } - } finally { - this.destroyContext(cfg, ctx); - } - return res; - } - - getSpanOfEnclosingComment(filename: String, offset: number, onlyMultiLine: boolean): LspTextSpan { - let ptr: KPointer; - const [cfg, ctx] = this.createContext(filename); - try { - ptr = global.es2panda._getSpanOfEnclosingComment(ctx, offset, onlyMultiLine); - } finally { - this.destroyContext(cfg, ctx); - } - return new LspTextSpan(ptr); - } - - getCodeFixesAtPosition(filename: String, start: number, end: number, errorCodes: number[]): CodeFixActionInfo[] { - let ptr: KPointer; - const [cfg, ctx] = this.createContext(filename); - try { - ptr = global.es2panda._getCodeFixesAtPosition(ctx, start, end, new Int32Array(errorCodes), errorCodes.length); - } finally { - this.destroyContext(cfg, ctx); - } - const codeFixActionInfoList = new CodeFixActionInfoList(ptr); - const codeFixActionInfos: CodeFixActionInfo[] = []; - codeFixActionInfos.push(...codeFixActionInfoList.codeFixActionInfos); - return codeFixActionInfos; - } - - provideInlayHints(filename: String, span: TextSpan): LspInlayHint[] { - let ptr: KPointer; - const [cfg, ctx] = this.createContext(filename); - try { - const nativeSpan = global.es2panda._createTextSpan(span.start, span.length); - ptr = global.es2panda._getInlayHintList(ctx, nativeSpan); - } finally { - this.destroyContext(cfg, ctx); - } - const inlayHintList = new LspInlayHintList(ptr); - const inlayHints: LspInlayHint[] = []; - inlayHints.push(...inlayHintList.inlayHints); - return inlayHints; - } - - getSignatureHelpItems(filename: String, offset: number): LspSignatureHelpItems { - let ptr: KPointer; - const [cfg, ctx] = this.createContext(filename); - try { - ptr = global.es2panda._getSignatureHelpItems(ctx, offset); - } finally { - this.destroyContext(cfg, ctx); - } - return new LspSignatureHelpItems(ptr); - } - - // Use AST cache start - private getFileDependencies(inputs: string[], output: string): void { - let depInputContent = ''; - let outputFile: string = output; - let depAnalyzerPath: string = path.join(this.pandaBinPath, 'dependency_analyzer'); - let depInputFile = path.join(this.cacheDir, 'depInput.txt'); - inputs.forEach((file) => { - depInputContent += file + os.EOL; - }); - fs.writeFileSync(depInputFile, depInputContent); - ensurePathExists(outputFile); - const result = child_process.spawnSync( - depAnalyzerPath, - [`@${depInputFile}`, `--output=${output}`, `--arktsconfig=${this.entryArkTsConfig}`], - { - encoding: 'utf-8', - windowsHide: true - } - ); - if (result.error || result.status !== 0) { - console.error('getFileDependencies failed: ', result.stderr || result.error); - } - } - - // Collect circular dependencies, like: A→B→C→A - private findStronglyConnectedComponents(graph: FileDepsInfo): Map> { - const adjacencyList: Record = {}; - const reverseAdjacencyList: Record = {}; - const allNodes = new Set(); - - for (const node in graph.dependencies) { - allNodes.add(node); - graph.dependencies[node].forEach((dep) => allNodes.add(dep)); - } - for (const node in graph.dependants) { - allNodes.add(node); - graph.dependants[node].forEach((dep) => allNodes.add(dep)); - } - - Array.from(allNodes).forEach((node) => { - adjacencyList[node] = graph.dependencies[node] || []; - reverseAdjacencyList[node] = graph.dependants[node] || []; - }); - - const visited = new Set(); - const order: string[] = []; - - function dfs(node: string): void { - visited.add(node); - for (const neighbor of adjacencyList[node]) { - if (!visited.has(neighbor)) { - dfs(neighbor); - } - } - order.push(node); - } - - Array.from(allNodes).forEach((node) => { - if (!visited.has(node)) { - dfs(node); - } - }); - - visited.clear(); - const components = new Map>(); - - function reverseDfs(node: string, component: Set): void { - visited.add(node); - component.add(node); - for (const neighbor of reverseAdjacencyList[node]) { - if (!visited.has(neighbor)) { - reverseDfs(neighbor, component); - } - } - } - - for (let i = order.length - 1; i >= 0; i--) { - const node = order[i]; - if (!visited.has(node)) { - const component = new Set(); - reverseDfs(node, component); - if (component.size > 1) { - const sortedFiles = Array.from(component).sort(); - const hashKey = createHash(sortedFiles.join('|')); - components.set(hashKey, component); - } - } - } - - return components; - } - - private getJobDependencies(fileDeps: string[], cycleFiles: Map): Set { - let depJobList: Set = new Set(); - fileDeps.forEach((file) => { - if (!cycleFiles.has(file)) { - depJobList.add('0' + file); - } else { - cycleFiles.get(file)?.forEach((f) => { - depJobList.add(f); - }); - } - }); - - return depJobList; - } - - private getJobDependants(fileDeps: string[], cycleFiles: Map): Set { - let depJobList: Set = new Set(); - fileDeps.forEach((file) => { - if (!file.endsWith(DECL_ETS_SUFFIX)) { - depJobList.add('1' + file); - } - if (cycleFiles.has(file)) { - cycleFiles.get(file)?.forEach((f) => { - depJobList.add(f); - }); - } else { - depJobList.add('0' + file); - } - }); - - return depJobList; - } - - private collectCompileJobs(jobs: Record, isValid: boolean = false): void { - let entryFileList: string[] = Object.keys(this.moduleInfos); - this.getFileDependencies(entryFileList, this.fileDependencies); - const data = fs.readFileSync(this.fileDependencies, 'utf-8'); - let fileDepsInfo: FileDepsInfo = JSON.parse(data) as FileDepsInfo; - - Object.keys(fileDepsInfo.dependants).forEach((file) => { - if (!(file in fileDepsInfo.dependencies)) { - fileDepsInfo.dependencies[file] = []; - } - }); - - let cycleGroups = this.findStronglyConnectedComponents(fileDepsInfo); - - let cycleFiles: Map = new Map(); - cycleGroups.forEach((value: Set, key: string) => { - value.forEach((file) => { - cycleFiles.set(file, [key]); - }); - }); - - Object.entries(fileDepsInfo.dependencies).forEach(([key, value]) => { - let dependencies = this.getJobDependencies(value, cycleFiles); - if (cycleFiles.has(key)) { - const externalProgramJobIds = cycleFiles.get(key)!; - externalProgramJobIds.forEach((id) => { - let fileList: string[] = Array.from(cycleGroups.get(id)!); - this.createExternalProgramJob(id, fileList, jobs, dependencies, isValid, true); - }); - } else { - const id = '0' + key; - let fileList: string[] = [key]; - this.createExternalProgramJob(id, fileList, jobs, dependencies, isValid); - } - }); - - Object.entries(fileDepsInfo.dependants).forEach(([key, value]) => { - const dependants = this.getJobDependants(value, cycleFiles); - const jobIds = cycleFiles.has(key) ? cycleFiles.get(key)! : ['0' + key]; - - jobIds.forEach((jobId) => { - const currentDependants = new Set(dependants); - jobs[jobId].dependants.forEach((dep) => currentDependants.add(dep)); - currentDependants.delete(jobId); - jobs[jobId].dependants = Array.from(currentDependants); - }); - }); - } - - private createExternalProgramJob( - id: string, - fileList: string[], - jobs: Record, - dependencies: Set, - isValid: boolean, - isInCycle?: boolean - ): void { - if (dependencies.has(id)) { - dependencies.delete(id); - } - if (jobs[id]) { - const existingJob = jobs[id]; - const mergedFileList = [...new Set([...existingJob.fileList, ...fileList])]; - const mergedDependencies = [...new Set([...existingJob.dependencies, ...Array.from(dependencies)])]; - const mergedIsInCycle = existingJob.isInCycle || isInCycle; - - existingJob.fileList = mergedFileList; - existingJob.dependencies = mergedDependencies; - existingJob.isInCycle = mergedIsInCycle; - } else { - jobs[id] = { - id, - fileList, - isDeclFile: true, - isInCycle, - dependencies: Array.from(dependencies), - dependants: [], - isValid - }; - } - } - - private addJobToQueues(job: Job, queues: Job[]): void { - if (queues.some((j) => j.id === job.id)) { - return; - } - queues.push(job); - } - - private initCompileQueues(jobs: Record, queues: Job[], dependantJobs?: Record): void { - Object.values(jobs).forEach((job) => { - if (job.dependencies.length === 0) { - if (dependantJobs && job.id in dependantJobs) { - job.isValid = false; - this.invalidateFileCache(job.fileList); - } - this.addJobToQueues(job, queues); - } - }); - } - - private checkAllTasksDone(queues: Job[], workerPool: WorkerInfo[]): boolean { - if (queues.length === 0) { - for (let i = 0; i < workerPool.length; i++) { - if (!workerPool[i].isIdle) { - return false; - } - } - return true; - } - return false; - } - - private initGlobalContext(jobs: Record): void { - let files: string[] = []; - Object.entries(jobs).forEach(([key, job]) => { - for (let i = 0; i < job.fileList.length; i++) { - files.push(job.fileList[i]); - } - }); - - let ets2pandaCmd: string[] = [ - '_', - '--extension', - 'ets', - '--arktsconfig', - this.entryArkTsConfig, - Object.keys(this.moduleInfos)[0] - ]; - - this.globalLspDriverHelper = new LspDriverHelper(); - this.globalLspDriverHelper.memInitialize(this.pandaLibPath); - this.globalConfig = this.globalLspDriverHelper.createCfg(ets2pandaCmd, files[0], this.pandaLibPath); - this.globalContextPtr = this.globalLspDriverHelper.createGlobalContext(this.globalConfig.peer, files, files.length); - } - - private updateQueues( - jobs: Record, - queues: Job[], - jobId: string, - dependantJobs?: Record - ): void { - const completedJob = jobs[jobId]; - completedJob.dependants.forEach((depJobId) => { - const depJob = jobs[depJobId]; - if (!depJob) { - return; - } - const depIndex = depJob.dependencies.indexOf(jobId); - if (depIndex === -1) { - return; - } - depJob.dependencies.splice(depIndex, 1); - if (depJob.dependencies.length > 0) { - return; - } - this.processCompletedDependencies(depJob, queues, dependantJobs); - }); - } - - private processCompletedDependencies(depJob: Job, queues: Job[], dependantJobs?: Record): void { - if (dependantJobs && depJob.id in dependantJobs) { - depJob.isValid = false; - this.invalidateFileCache(depJob.fileList); - } - this.addJobToQueues(depJob, queues); - } - - private invalidateFileCache(fileList: string[]): void { - fileList.forEach((file) => { - global.es2pandaPublic._InvalidateFileCache(this.globalContextPtr!, file); - }); - } - - private async invokeWorkers( - jobs: Record, - queues: Job[], - processingJobs: Set, - workers: ThreadWorker[], - numWorkers: number, - dependantJobs?: Record - ): Promise { - return new Promise((resolve) => { - const workerPool = this.createWorkerPool(numWorkers, workers); - - workerPool.forEach((workerInfo) => { - this.setupWorkerListeners(workerInfo.worker, workerPool, jobs, queues, processingJobs, resolve, dependantJobs); - this.assignTaskToIdleWorker(workerInfo, queues, processingJobs); - }); - }); - } - - private createWorkerPool(numWorkers: number, workers: ThreadWorker[]): WorkerInfo[] { - const workerPool: WorkerInfo[] = []; - - for (let i = 0; i < numWorkers; i++) { - const worker = new ThreadWorker(path.resolve(__dirname, 'compile_thread_worker.js'), { - workerData: { workerId: i } - }); - workers.push(worker); - workerPool.push({ worker, isIdle: true }); - } - - return workerPool; - } - - private setupWorkerListeners( - worker: ThreadWorker, - workerPool: WorkerInfo[], - jobs: Record, - queues: Job[], - processingJobs: Set, - resolve: () => void, - dependantJobs?: Record - ): void { - worker.on('message', (msg) => { - if (msg.type !== 'TASK_FINISH') { - return; - } - - this.handleTaskCompletion(msg.jobId, worker, workerPool, jobs, queues, processingJobs, dependantJobs); - - if (this.checkAllTasksDone(queues, workerPool)) { - this.terminateWorkers(workerPool); - resolve(); - } - }); - } - - private handleTaskCompletion( - jobId: string, - worker: ThreadWorker, - workerPool: WorkerInfo[], - jobs: Record, - queues: Job[], - processingJobs: Set, - dependantJobs?: Record - ): void { - const workerInfo = workerPool.find((w) => w.worker === worker); - if (workerInfo) { - workerInfo.isIdle = true; - } - processingJobs.delete(jobId); - this.updateQueues(jobs, queues, jobId, dependantJobs); - workerPool.forEach((workerInfo) => { - if (workerInfo.isIdle) { - this.assignTaskToIdleWorker(workerInfo, queues, processingJobs); - } - }); - } - - private terminateWorkers(workerPool: WorkerInfo[]): void { - workerPool.forEach(({ worker }) => { - worker.postMessage({ type: 'EXIT' }); - }); - } - - private assignTaskToIdleWorker(workerInfo: WorkerInfo, queues: Job[], processingJobs: Set): void { - let job: Job | undefined; - let jobInfo: JobInfo | undefined; - - if (queues.length > 0) { - job = queues.shift()!; - jobInfo = { - id: job.id, - filePath: job.fileList[0], - arktsConfigFile: this.entryArkTsConfig, - globalContextPtr: this.globalContextPtr!, - buildConfig: Object.values(this.buildConfigs)[0], - isValid: job.isValid - }; - } - - if (job) { - processingJobs.add(job.id); - workerInfo.worker.postMessage({ type: 'ASSIGN_TASK', jobInfo }); - workerInfo.isIdle = false; - } - } - - // AST caching is not enabled by default. - // Call `initAstCache` before invoking the language service interface to enable AST cache - public async initAstCache(numWorkers: number = 1): Promise { - const jobs: Record = {}; - const queues: Job[] = []; - this.collectCompileJobs(jobs); - this.initGlobalContext(jobs); - this.initCompileQueues(jobs, queues); - - const processingJobs = new Set(); - const workers: ThreadWorker[] = []; - await this.invokeWorkers(jobs, queues, processingJobs, workers, numWorkers); - } - - private compileExternalProgram(jobInfo: JobInfo): void { - PluginDriver.getInstance().initPlugins(jobInfo.buildConfig); - let ets2pandaCmd = ['-', '--extension', 'ets', '--arktsconfig', jobInfo.arktsConfigFile]; - let lspDriverHelper = new LspDriverHelper(); - let config = lspDriverHelper.createCfg(ets2pandaCmd, jobInfo.filePath); - const source = fs.readFileSync(jobInfo.filePath, 'utf8').replace(/\r\n/g, '\n'); - let context = lspDriverHelper.createCtx(source, jobInfo.filePath, config, jobInfo.globalContextPtr, true); - PluginDriver.getInstance().getPluginContext().setContextPtr(context); - lspDriverHelper.proceedToState(context, Es2pandaContextState.ES2PANDA_STATE_PARSED); - PluginDriver.getInstance().runPluginHook(PluginHook.PARSED); - lspDriverHelper.proceedToState(context, Es2pandaContextState.ES2PANDA_STATE_LOWERED); - } - - public addFileCache(filename: String): void { - global.es2pandaPublic._AddFileCache(this.globalContextPtr!, filename); - let jobInfo = { - id: filename.valueOf(), - filePath: filename.valueOf(), - arktsConfigFile: this.entryArkTsConfig, - globalContextPtr: this.globalContextPtr!, - buildConfig: Object.values(this.buildConfigs)[0], - isValid: true - }; - this.compileExternalProgram(jobInfo); - } - - public removeFileCache(filename: String): void { - global.es2pandaPublic._RemoveFileCache(this.globalContextPtr!, filename); - } - - public async updateFileCache(filename: String, numWorkers: number = 1): Promise { - const queues: Job[] = []; - const jobs: Record = {}; - this.collectCompileJobs(jobs, true); - const dependantJobs = this.findJobDependants(jobs, filename.valueOf()); - this.initCompileQueues(jobs, queues, dependantJobs); - const processingJobs = new Set(); - const workers: ThreadWorker[] = []; - await this.invokeWorkers(jobs, queues, processingJobs, workers, numWorkers, dependantJobs); - } - - private findJobDependants(jobs: Record, filePath: string): Record { - const targetJobs = this.findTargetJobs(jobs, filePath); - const { visited, dependantJobs } = this.collectDependantJobs(jobs, targetJobs); - - return this.mergeJobs(targetJobs, dependantJobs); - } - - private findTargetJobs(jobs: Record, filePath: string): Job[] { - return Object.values(jobs).filter( - (job) => job.fileList.includes(filePath) || (job.isInCycle && job.fileList.some((f) => f === filePath)) - ); - } - - private collectDependantJobs( - jobs: Record, - targetJobs: Job[] - ): { visited: Set; dependantJobs: Map } { - const visited = new Set(); - const dependantJobs = new Map(); - const queue: Job[] = []; - - targetJobs.forEach((job) => { - if (!visited.has(job.id)) { - visited.add(job.id); - queue.push(job); - } - - while (queue.length) { - const current = queue.shift()!; - this.processDependants(jobs, current, visited, queue, dependantJobs); - } - }); - - return { visited, dependantJobs }; - } - - private processDependants( - jobs: Record, - current: Job, - visited: Set, - queue: Job[], - dependantJobs: Map - ): void { - current.dependants.forEach((dependantId) => { - const dependantJob = jobs[dependantId]; - if (dependantJob && !visited.has(dependantId)) { - visited.add(dependantId); - queue.push(dependantJob); - dependantJobs.set(dependantId, dependantJob); - } - }); - } - - private mergeJobs(targetJobs: Job[], dependantJobs: Map): Record { - return [...targetJobs, ...dependantJobs.values()].reduce( - (acc, job) => { - acc[job.id] = job; - return acc; - }, - {} as Record - ); - } - - public dispose(): void { - this.globalLspDriverHelper!.destroyGlobalContext(this.globalContextPtr!); - this.globalLspDriverHelper!.destroyConfig(this.globalConfig!); - this.globalLspDriverHelper!.memFinalize(); - } -} - -function createHash(str: string): string { - const hash = crypto.createHash('sha256'); - hash.update(str); - return hash.digest('hex'); -} -// Use AST cache end diff --git a/ets2panda/declgen_ets2ts/declgenEts2Ts.cpp b/ets2panda/declgen_ets2ts/declgenEts2Ts.cpp index d59a71b141..5a6f153903 100644 --- a/ets2panda/declgen_ets2ts/declgenEts2Ts.cpp +++ b/ets2panda/declgen_ets2ts/declgenEts2Ts.cpp @@ -276,19 +276,20 @@ void TSDeclGen::AddSuperType(const ir::Expression *super) if (super->TsType() == nullptr) { return; } - const auto superType = checker::ETSChecker::ETSType(super->TsType()); - if (superType == checker::TypeFlag::ETS_OBJECT || superType == checker::TypeFlag::ETS_DYNAMIC_TYPE) { - auto objectType = super->TsType()->AsETSObjectType(); - AddObjectDependencies(objectType->Name()); - } + AddSuperType(super->TsType()); } void TSDeclGen::AddSuperType(const checker::Type *tsType) { const auto superType = checker::ETSChecker::ETSType(tsType); - if (superType == checker::TypeFlag::ETS_OBJECT || superType == checker::TypeFlag::ETS_DYNAMIC_TYPE) { + if (superType == checker::TypeFlag::ETS_OBJECT) { auto objectType = tsType->AsETSObjectType(); AddObjectDependencies(objectType->Name()); + } else if (superType == checker::TypeFlag::ETS_UNION) { + auto unionType = tsType->AsETSUnionType(); + std::vector filteredTypes = FilterUnionTypes(unionType->ConstituentTypes()); + GenSeparated( + filteredTypes, [this](checker::Type *type) { AddSuperType(type); }, ""); } } @@ -322,10 +323,7 @@ void TSDeclGen::GenDeclarations() for (auto *globalStatement : program_->Ast()->Statements()) { ResetState(); ResetClassNode(); - const auto jsdoc = compiler::JsdocStringFromDeclaration(globalStatement); - if (jsdoc.Utf8().find(NON_INTEROP_FLAG) != std::string_view::npos) { - continue; - } else if (globalStatement->IsClassDeclaration()) { + if (globalStatement->IsClassDeclaration()) { GenClassDeclaration(globalStatement->AsClassDeclaration()); } else if (globalStatement->IsTSInterfaceDeclaration()) { GenInterfaceDeclaration(globalStatement->AsTSInterfaceDeclaration()); @@ -337,20 +335,6 @@ void TSDeclGen::GenDeclarations() } } -void TSDeclGen::GenOtherDeclarations() -{ - const std::string recordKey = "Record"; - const std::string recordStr = R"( -// generated for static Record -type Record = { - [P in K]: T; -}; -)"; - if (indirectDependencyObjects_.find(recordKey) != indirectDependencyObjects_.end()) { - OutDts(recordStr); - } -} - void TSDeclGen::GenImportDeclarations() { for (auto *globalStatement : program_->Ast()->Statements()) { @@ -362,7 +346,10 @@ void TSDeclGen::GenImportDeclarations() void TSDeclGen::GenImportRecordDeclarations(const std::string &source) { - OutDts("import type { Record } from \"", source, "\";\n"); + const std::string recordKey = "Record"; + if (indirectDependencyObjects_.find(recordKey) != indirectDependencyObjects_.end()) { + OutDts("import type { Record } from \"", source, "\";\n"); + } } template @@ -522,6 +509,8 @@ bool TSDeclGen::HandleObjectType(const checker::Type *checkerType) OutDts("number"); } else if (typeStr == "BigInt") { OutDts("bigint"); + } else if (typeStr == "ESValue") { + OutDts("ESObject"); } else { GenObjectType(checkerType->AsETSObjectType()); } @@ -1837,7 +1826,7 @@ void TSDeclGen::GenPartName(std::string &partName) partName = "string"; } else if (numberTypes_.count(partName) != 0U) { partName = "number"; - } else if (partName == "ESObject") { + } else if (partName == "ESValue") { partName = "ESObject"; } else if (partName == "BigInt") { partName = "bigint"; @@ -1845,6 +1834,8 @@ void TSDeclGen::GenPartName(std::string &partName) partName = "Error"; } else if (partName == "Any") { partName = "ESObject"; + } else if (partName == "Floating" || partName == "Integral") { + partName = "number"; } } @@ -1947,10 +1938,7 @@ void TSDeclGen::ProcessClassBody(const ir::ClassDefinition *classDef) state_.inClass = true; std::unordered_set processedMethods; for (const auto *prop : classDef->Body()) { - const auto jsdoc = compiler::JsdocStringFromDeclaration(prop); - if (jsdoc.Utf8().find(NON_INTEROP_FLAG) != std::string_view::npos) { - continue; - } else if (classDef->IsEnumTransformed()) { + if (classDef->IsEnumTransformed()) { if (prop->IsClassProperty()) { state_.inEnum = true; GenPropDeclaration(prop->AsClassProperty()); @@ -2308,7 +2296,11 @@ void TSDeclGen::GenGlobalVarDeclaration(const ir::ClassProperty *globalVar) } const auto symbol = GetKeyIdent(globalVar->Key()); - const auto varName = symbol->Name().Mutf8(); + auto varName = symbol->Name().Mutf8(); + const std::string prefix = "gensym%%_"; + if (varName.rfind(prefix, 0) == 0) { + varName = varName.substr(prefix.size()); + } const bool isConst = globalVar->IsConst(); const bool isDefaultExported = globalVar->IsDefaultExported(); DebugPrint("GenGlobalVarDeclaration: " + varName); @@ -2382,6 +2374,7 @@ bool GenerateTsDeclarations(checker::ETSChecker *checker, const ark::es2panda::p declBuilder.ResetDtsOutput(); declBuilder.GenImportRecordDeclarations(declBuilder.GetDeclgenOptions().recordFile); std::string recordImportOutputDEts = declBuilder.GetDtsOutput(); + combinedDEts = recordImportOutputDEts + combinedDEts; } return WriteOutputFiles(declBuilder.GetDeclgenOptions(), combineEts, combinedDEts, checker); @@ -2411,12 +2404,11 @@ bool WriteOutputFiles(const DeclgenOptions &options, const std::string &combined } } - if (!declBuilder.GetDeclgenOptions().outputEts.empty()) { - auto outTsPath = declBuilder.GetDeclgenOptions().outputEts; - if (!WriteToFile(outTsPath, combineEts, checker)) { + if (!options.outputEts.empty()) { + if (!WriteToFile(options.outputEts, combinedEts, checker)) { return false; + } } - return true; } } // namespace ark::es2panda::declgen_ets2ts diff --git a/ets2panda/declgen_ets2ts/declgenEts2Ts.h b/ets2panda/declgen_ets2ts/declgenEts2Ts.h index 2a47d8b7aa..a8d1f5c148 100644 --- a/ets2panda/declgen_ets2ts/declgenEts2Ts.h +++ b/ets2panda/declgen_ets2ts/declgenEts2Ts.h @@ -25,8 +25,6 @@ namespace ark::es2panda::declgen_ets2ts { -constexpr const char *NON_INTEROP_FLAG = "@noninterop"; - struct DeclgenOptions { bool exportAll = false; bool isIsolatedDeclgen = false; @@ -38,6 +36,9 @@ struct DeclgenOptions { // Consume program after checker stage and generate out_path typescript file with declarations bool GenerateTsDeclarations(checker::ETSChecker *checker, const ark::es2panda::parser::Program *program, const DeclgenOptions &declgenOptions); +bool ValidateDeclgenOptions(const DeclgenOptions &options, checker::ETSChecker *checker); +bool WriteOutputFiles(const DeclgenOptions &options, const std::string &combinedEts, const std::string &combinedDEts, + checker::ETSChecker *checker); class TSDeclGen { public: diff --git a/ets2panda/driver/build_system/src/build/base_mode.ts b/ets2panda/driver/build_system/src/build/base_mode.ts index 530f580064..2d0723d4a0 100644 --- a/ets2panda/driver/build_system/src/build/base_mode.ts +++ b/ets2panda/driver/build_system/src/build/base_mode.ts @@ -33,7 +33,6 @@ import { LANGUAGE_VERSION, LINKER_INPUT_FILE, MERGED_ABC_FILE, - MERGED_INTERMEDIATE_FILE, STATIC_RECORD_FILE, STATIC_RECORD_FILE_CONTENT, TS_SUFFIX @@ -44,6 +43,7 @@ import { createFileIfNotExists, ensurePathExists, getFileHash, + isHybrid, isMac } from '../utils'; import { @@ -100,6 +100,7 @@ export abstract class BaseMode { public dependencyFileMap: DependencyFileConfig | null; public isBuildConfigModified: boolean | undefined; public byteCodeHar: boolean; + public isHybrid: boolean; constructor(buildConfig: BuildConfig) { this.buildConfig = buildConfig; @@ -133,6 +134,7 @@ export abstract class BaseMode { this.dependencyFileMap = null; this.isBuildConfigModified = buildConfig.isBuildConfigModified as boolean | undefined; this.byteCodeHar = buildConfig.byteCodeHar as boolean; + this.isHybrid = isHybrid(buildConfig); } public declgen(fileInfo: CompileFileInfo): void { @@ -160,7 +162,7 @@ export abstract class BaseMode { const declEtsOutputDir = path.dirname(declEtsOutputPath); const staticRecordRelativePath = changeFileExtension( path.relative(declEtsOutputDir, staticRecordPath).replaceAll(/\\/g, '\/'), - "", + '', DECL_TS_SUFFIX ); createFileIfNotExists(staticRecordPath, STATIC_RECORD_FILE_CONTENT); @@ -660,6 +662,10 @@ export abstract class BaseMode { } protected collectCompileFiles(): void { + if (!this.isBuildConfigModified && this.isCacheFileExists && !this.enableDeclgenEts2Ts && !this.isHybrid) { + this.collectDependentCompileFiles(); + return; + } this.entryFiles.forEach((file: string) => { for (const [_, moduleInfo] of this.moduleInfos) { const relativePath = path.relative(moduleInfo.moduleRootPath, file); @@ -748,7 +754,7 @@ export abstract class BaseMode { }; public generatedependencyFileMap(): void { - if (this.isBuildConfigModified || !this.isCacheFileExists || this.enableDeclgenEts2Ts) { + if (this.isBuildConfigModified || !this.isCacheFileExists || this.enableDeclgenEts2Ts || this.isHybrid) { return; } const dependencyInputFile: string = path.join(this.cacheDir, DEPENDENCY_INPUT_FILE); diff --git a/ets2panda/driver/build_system/src/build/declgen_worker.ts b/ets2panda/driver/build_system/src/build/declgen_worker.ts index 2e84ce4f79..cfac69be8f 100644 --- a/ets2panda/driver/build_system/src/build/declgen_worker.ts +++ b/ets2panda/driver/build_system/src/build/declgen_worker.ts @@ -81,7 +81,7 @@ process.on('message', (message: { const declEtsOutputDir = path.dirname(declEtsOutputPath); const staticRecordRelativePath = changeFileExtension( path.relative(declEtsOutputDir, staticRecordPath).replaceAll(/\\/g, '\/'), - "", + '', DECL_TS_SUFFIX ); createFileIfNotExists(staticRecordPath, STATIC_RECORD_FILE_CONTENT); diff --git a/ets2panda/driver/build_system/src/utils.ts b/ets2panda/driver/build_system/src/utils.ts index 4f6d923f43..d703660ab8 100644 --- a/ets2panda/driver/build_system/src/utils.ts +++ b/ets2panda/driver/build_system/src/utils.ts @@ -17,7 +17,8 @@ import * as crypto from 'crypto'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; -import { DECL_ETS_SUFFIX } from './pre_define'; +import { DECL_ETS_SUFFIX, LANGUAGE_VERSION } from './pre_define'; +import { BuildConfig } from './types'; const WINDOWS: string = 'Windows_NT'; const LINUX: string = 'Linux'; @@ -107,3 +108,10 @@ export function createFileIfNotExists(filePath: string, content: string): boolea return false; } } + +export function isHybrid(buildConfig: BuildConfig): boolean { + return buildConfig.dependentModuleList.some( + module => module.language === LANGUAGE_VERSION.ARKTS_1_1 || + module.language === LANGUAGE_VERSION.ARKTS_HYBRID + ); +} diff --git a/ets2panda/test/test-lists/declgenets2ts/ets-runtime/declgen-ets2ts-runtime-ignored.txt b/ets2panda/test/test-lists/declgenets2ts/ets-runtime/declgen-ets2ts-runtime-ignored.txt index c4c8b79e00..679b22ecfa 100644 --- a/ets2panda/test/test-lists/declgenets2ts/ets-runtime/declgen-ets2ts-runtime-ignored.txt +++ b/ets2panda/test/test-lists/declgenets2ts/ets-runtime/declgen-ets2ts-runtime-ignored.txt @@ -32,3 +32,5 @@ top_level_03.ets multisource_inheritance.ets as_string.ets function_type_with_receiver/extensionFunctionType_return_this.ets +forOfCustomIterator3.ets +too_many_async.ets diff --git a/ets2panda/test/unit/declgen/test_ets2ts_isolated_declgen.cpp b/ets2panda/test/unit/declgen/test_ets2ts_isolated_declgen.cpp deleted file mode 100644 index a2049a3800..0000000000 --- a/ets2panda/test/unit/declgen/test_ets2ts_isolated_declgen.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright (c) 2025 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include - -#include "os/library_loader.h" - -#include "public/es2panda_lib.h" -#include "./util.h" - -// NOLINTBEGIN - -static es2panda_Impl *impl = nullptr; - -int main(int argc, char **argv) -{ - if (argc < MIN_ARGC) { - return INVALID_ARGC_ERROR_CODE; - } - - if (GetImpl() == nullptr) { - return NULLPTR_IMPL_ERROR_CODE; - } - impl = GetImpl(); - std::cout << "LOAD SUCCESS" << std::endl; - - const char **args = const_cast(&(argv[1])); - auto config = impl->CreateConfig(argc - 1, args); - auto context = impl->CreateContextFromFile(config, argv[argc - 1]); - if (context == nullptr) { - std::cerr << "FAILED TO CREATE CONTEXT" << std::endl; - return NULLPTR_CONTEXT_ERROR_CODE; - } - - impl->ProceedToState(context, ES2PANDA_STATE_CHECKED); - CheckForErrors("CHECKED", context); - std::string declName = GetDeclPrefix(argv[argc - 1]) + ".d.ets"; - int result = impl->GenerateTsDeclarationsFromContext(context, declName.c_str(), "dump.ets", false, true, ""); - if (result != 0) { - std::cerr << "FAILED TO GENERATE DECLARATIONS" << std::endl; - return result; - } - - impl->DestroyConfig(config); - - return 0; -} - -// NOLINTEND -- Gitee