* Copyright (c) 2024-2026 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.
*/
#ifndef ES2PANDA_UTIL_IMPORT_PATH_MANAGER_H
#define ES2PANDA_UTIL_IMPORT_PATH_MANAGER_H
#include "language.h"
#if defined PANDA_TARGET_MOBILE
#define USE_UNIX_SYSCALL
#endif
#include "util/ustring.h"
#include "util/enumbitops.h"
#include "util/path.h"
#include "util/options.h"
#include "util/diagnosticEngine.h"
#include "parser/program/ImportCache.h"
namespace ark::panda_file {
class File;
}
namespace ark::es2panda {
class ArkTsConfig;
}
namespace ark::es2panda::util {
namespace gen::extension {
enum Enum : size_t;
}
using ENUMBITOPS_OPERATORS;
}
namespace ark::es2panda::ir {
class StringLiteral;
}
namespace ark::es2panda::util {
template <ModuleKind KIND>
constexpr parser::CacheType SelectCacheType()
{
return KIND == ModuleKind::METADATA_DECL ? parser::CacheType::METADATA : parser::CacheType::SOURCES;
}
}
namespace ark::es2panda::parser {
template <util::ModuleKind KIND>
class ProgramAdapter;
class Program;
using PackageProgram = ProgramAdapter<util::ModuleKind::PACKAGE>;
class NonPackageProgram;
using SourceProgram = ProgramAdapter<util::ModuleKind::MODULE>;
using SourceDeclarationProgram = ProgramAdapter<util::ModuleKind::SOURCE_DECL>;
using MetadataBasedProgram = ProgramAdapter<util::ModuleKind::METADATA_DECL>;
using LowDeclarationProgram = ProgramAdapter<util::ModuleKind::ETSCACHE_DECL>;
}
namespace ark::es2panda::util {
inline bool IsAbsolute(const std::string &path)
{
#ifndef ARKTSCONFIG_USE_FILESYSTEM
return !path.empty() && path[0] == '/';
#else
return fs::path(path).is_absolute();
#endif
}
struct ModuleInfo {
ArenaString moduleName {};
ArenaString modulePrefix {};
ModuleKind kind {};
bool isDeclForDynamicStaticInterop {};
Language lang = Language(Language::Id::ETS);
};
class ImportPathManager;
class ImportInfo : public parser::CacheReference<> {
private:
static constexpr std::string_view ABC_SUFFIX = ".abc";
public:
ImportInfo(const ImportPathManager &ipm, std::string_view resolvedSource, Language::Id lang = Language::Id::ETS,
bool isExternalModule = false);
ImportInfo() = default;
ImportInfo(const ImportInfo &other);
const ImportInfo &operator=(const ImportInfo &other);
NO_MOVE_SEMANTIC(ImportInfo);
~ImportInfo() = default;
static constexpr auto DUMMY_PATH = "dummy_path";
public:
std::string_view ResolvedSource() const
{
return resolvedSource_;
}
std::string_view ModuleName() const
{
return moduleName_;
}
std::string_view OhmUrl() const;
bool PointsToPackage() const
{
return !ResolvedPathIsVirtual() && ark::os::file::File::IsDirectory(std::string(resolvedSource_));
}
bool ReferencesABC() const
{
return (extModuleData_ != nullptr) && Helpers::EndsWith(extModuleData_->Path(), ABC_SUFFIX);
}
const std::string &AbcPath() const
{
ES2PANDA_ASSERT(ReferencesABC());
return extModuleData_->Path();
}
const parser::CacheReference<parser::AnyCacheType> &Data() const
{
return *this;
}
bool IsValid() const;
void UpdateTextForLSPIncremental(std::string textSource, std::string &&contents)
{
parser::ImportCache<parser::CacheType::SOURCES>::UpdateOnFileModification(Key(), std::move(textSource),
std::move(contents), Kind());
ClearKind();
parser::ImportCache<parser::CacheType::SOURCES>::GetFromCache(this);
}
private:
template <ModuleKind KIND, bool SHOULD_CACHE = true>
void SetTextFile(const std::string &file, util::DiagnosticEngine *de)
{
static_assert(KIND != ModuleKind::METADATA_DECL);
std::ifstream inputStream {file};
if (!inputStream) {
de->LogDiagnostic(diagnostic::OPEN_FAILED, util::DiagnosticMessageParams {file});
return;
}
std::stringstream ss {};
ss << inputStream.rdbuf();
auto text = std::move(ss).str();
if (text.empty()) {
de->LogDiagnostic(diagnostic::EMPTY_SOURCE_FILE, util::DiagnosticMessageParams {file});
}
SetData<KIND, SHOULD_CACHE>(file, std::move(text));
}
template <bool SHOULD_CACHE = true>
void SetBinFile(const panda_file::File &pf)
{
auto metadataSpan = GetMetadata(pf);
std::vector<uint8_t> metadata;
metadata.insert(metadata.begin(), metadataSpan.begin(), metadataSpan.end());
SetData<ModuleKind::METADATA_DECL, SHOULD_CACHE>(AbcPath(), std::move(metadata));
}
template <ModuleKind KIND, bool SHOULD_CACHE = true>
void SetData(std::string textSource, parser::SelectCacheDataType<SelectCacheType<KIND>(), true> contents)
{
ES2PANDA_ASSERT(Data().Kind() == ModuleKind::UNKNOWN);
Set(parser::ImportCache<SelectCacheType<KIND>()>::template StoreContents<KIND, SHOULD_CACHE>(
*this, textSource, std::move(contents)));
}
void LinkFractionInfoToPackage(const parser::PackageProgram &package);
bool ResolvedPathIsVirtual() const
{
ES2PANDA_ASSERT(!resolvedSource_.empty());
return !IsAbsolute(std::string(resolvedSource_));
}
inline static Span<const uint8_t> GetMetadata(const panda_file::File &pf);
private:
ArenaString resolvedSource_ {ERROR_LITERAL};
ArenaString moduleName_ {};
const ArkTsConfig::ExternalModuleData *extModuleData_ {};
Language::Id lang_ {Language::Id::ETS};
public:
std::string_view DeclPath() const;
bool HasSpecifiedDeclPath() const;
auto Lang() const
{
return lang_;
}
friend ImportPathManager;
};
class ImportPathManager {
public:
static constexpr std::string_view ANNOTATION_MODULE_DECLARATION =
"Larkruntime/annotation/ModuleDeclaration;";
static constexpr std::string_view ABC_SUFFIX = ImportInfo::ABC_SUFFIX;
static constexpr std::string_view ETS_SUFFIX = ".ets";
static constexpr std::string_view D_ETS_SUFFIX = ".d.ets";
static constexpr std::string_view CACHE_SUFFIX = ".etscache";
static constexpr std::string_view ETSSTDLIB_ABC_SUFFIX = "etsstdlib.abc";
static constexpr size_t extensionsSize = 9;
static constexpr std::array<std::string_view, extensionsSize> supportedExtensions = {
ETS_SUFFIX, D_ETS_SUFFIX, CACHE_SUFFIX, ".sts", ".d.sts", ".ts", ".d.ts", ".js"};
static constexpr std::array<std::string_view, extensionsSize> supportedExtensionsInversed = {
D_ETS_SUFFIX, ETS_SUFFIX, CACHE_SUFFIX, ".d.sts", ".sts", ".d.ts", ".ts", ".js"};
struct ParseInfo {
bool isParsed {};
parser::Program *program {nullptr};
};
using FileDependenciesMap = ArenaUnorderedMap<ArenaString, ArenaUnorderedSet<ArenaString>>;
using FileOutputMatching = ArenaUnorderedMap<ArenaString, ArenaString>;
explicit ImportPathManager(public_lib::Context *context);
NO_COPY_SEMANTIC(ImportPathManager);
NO_MOVE_SEMANTIC(ImportPathManager);
ImportPathManager() = delete;
~ImportPathManager() = default;
[[nodiscard]] const ArenaVector<ParseInfo> &GetParseQueue() const
{
return parseQueue_;
}
[[nodiscard]] ArenaVector<ParseInfo> &GetParseQueue()
{
return parseQueue_;
}
void ClearParseList()
{
parseQueue_.clear();
}
parser::Program *GatherImportInfo(parser::Program *importer, ir::StringLiteral *importPath);
parser::Program *EnsurePackageIsRegisteredByPackageFraction(parser::Program *fraction,
ir::ETSPackageDeclaration *packageDecl);
util::StringView ResolvePathAPI(parser::Program *importer, ir::StringLiteral *importPath) const;
const ArkTsConfig &ArkTSConfig() const;
void InitParseQueueForSimult();
void PrepareParseQueueForProgram(parser::Program *program);
void IntroduceMainProgramForSimult();
void SetupGlobalProgram();
parser::Program *IntroduceStdlibImportProgram(std::string &&contents);
parser::Program *SetupProgramForDebugInfoPlugin(std::string_view sourceFilePath, std::string_view moduleName);
parser::Program *SearchResolved(const ImportInfo &importInfo) const;
void RemoveProgramFromResolvedSources(ArenaString filename);
parser::Program *FindOrIntroduceProgramForIncremental(std::string_view absolutePath);
template <bool ATTACH_TO_GLOBAL_EXTERNAL_SOURCES = true>
parser::Program *FindOrIntroduceProgram(std::string_view absolutePath);
static std::string FormEtscacheFilePath(std::string moduleName, const std::string &cacheDir);
static void ExtractEtscacheToFile(const panda_file::File &pf, const std::string &abcPath,
const std::string &cacheDir);
std::string FormAbcFilePath(const ImportInfo &imd) const;
auto *Context() const
{
return &ctx_;
}
const auto &SrcPos() const
{
return srcPos_;
}
const auto &GetFileDependencies() const
{
return fileDependencies_;
}
auto &GetFileDependencies()
{
return fileDependencies_;
}
void AddFileDependencies(std::string_view file, std::string_view depFile)
{
fileDependencies_[ArenaString {file}].emplace(depFile);
}
const auto &GetOutputMatching() const
{
return outputMatching_;
}
void AddOutputMatching(std::string_view file, std::string_view outPath)
{
outputMatching_[ArenaString {file}] = outPath;
}
private:
template <typename VarBinderT, Language::Id LANG_ID>
void SetupGlobalProgram(public_lib::Context *ctx);
struct ResolvedPathRes {
std::string resolvedPath;
bool resolvedIsExternalModule {false};
};
ImportInfo ResolvePath(parser::Program *importer, std::string_view importPath) const;
ResolvedPathRes ResolveAbsolutePath(std::string_view importPathNode) const;
std::string DirOrDirWithIndexFile(std::string resolvedPathPrototype) const;
ResolvedPathRes AppendExtensionOrIndexFileIfOmitted(std::string resolvedPathPrototype) const;
std::string TryMatchDependencies(std::string_view fixedPath) const;
ResolvedPathRes TryResolvePath(std::string resolvedPathPrototype) const;
void TryMatchStaticResolvedPath(ResolvedPathRes *result) const;
void TryMatchDynamicResolvedPath(ResolvedPathRes *result) const;
bool DeclarationIsInCache(ImportInfo &importInfo);
template <ModuleKind KIND, typename VarBinderT = void>
parser::ProgramAdapter<KIND> *IntroduceProgram(const ImportInfo &importInfo);
parser::Program *IntroduceProgram(const ImportInfo &importInfo);
template <bool ATTACH_TO_GLOBAL_EXTERNAL_SOURCES = true>
parser::Program *LookupImportDataAndIntroduceProgram(ImportInfo *importInfo);
parser::Program *LookupProgramCaches(const ImportInfo &importInfo);
void LookupMemCache(ImportInfo *importInfo);
void LookupDiskData(ImportInfo *importInfo);
void LookupEtscacheFile(ImportInfo *importInfo);
void LookupSourceFile(ImportInfo *importInfo);
void RegisterSourceFile(const ImportInfo &importInfo);
void RegisterPackageFraction(parser::PackageProgram *package, ImportInfo *importInfo);
parser::PackageProgram *RegisterSourcesForPackageFromGlobbedDirectory(const ImportInfo &importInfo);
#ifdef USE_UNIX_SYSCALL
void UnixRegisterSourcesForPackageFromGlobbedDirectory(parser::ProgramAdapter<ModuleKind::PACKAGE> *pkg,
const ImportInfo &importInfo);
#endif
parser::PackageProgram *NewEmptyPackage(const ImportInfo &importInfo);
void RegisterProgram(parser::Program *program);
parser::Program *GetGlobalProgram() const;
DiagnosticEngine *DE() const;
private:
public_lib::Context &ctx_;
ArenaVector<ParseInfo> parseQueue_;
class ResolvedSources;
ResolvedSources &resolvedSources_;
std::string_view pathDelimiter_ {ark::os::file::File::GetPathDelim()};
mutable lexer::SourcePosition srcPos_ {};
bool isDynamic_ = false;
std::unordered_set<std::string> processedAbcFiles_;
FileDependenciesMap fileDependencies_;
FileOutputMatching outputMatching_;
};
}
#endif