* Copyright (c) 2021-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.
*/
#include "program.h"
#include "libarkbase/macros.h"
#include "public/public.h"
#include "compiler/core/CFG.h"
#include "generated/signatures.h"
#include "varbinder/varbinder.h"
#include "varbinder/ETSBinder.h"
#include "ir/astDump.h"
#include "ir/base/classDefinition.h"
#include "ir/statements/blockStatement.h"
#include "compiler/lowering/phase.h"
#include "util/helpers.h"
#include "util/importPathManager.h"
#include <memory>
namespace ark::es2panda::parser {
Program::Program(const util::ImportInfo &importInfo, ArenaAllocator *allocator, varbinder::VarBinder *varbinder)
: importInfo_(importInfo),
allocator_(allocator),
sourceFile_(util::Path {allocator}),
extension_(varbinder != nullptr ? varbinder->Extension() : ScriptExtension::INVALID),
cfg_(allocator_->New<compiler::CFG>(allocator_))
{
PushVarBinder(varbinder);
std::string_view textView {};
switch (importInfo_.Data().Kind()) {
case util::ModuleKind::PACKAGE:
case util::ModuleKind::UNKNOWN:
case util::ModuleKind::METADATA_DECL:
textView = "";
break;
default:
textView = importInfo_.Data().DataFor<CacheType::SOURCES>();
}
bool isDynamic = importInfo_.Lang() != Language::Id::ETS;
es2panda::SourceFile sf {importInfo_.TextSource(), textView, importInfo_.ResolvedSource(), false, isDynamic};
SetSource(sf);
}
std::string Program::RelativeFilePath(const public_lib::Context *context) const
{
if (importInfo_.Lang() != Language::Id::ETS) {
return std::string {importInfo_.TextSource()};
}
if (!Is<util::ModuleKind::MODULE>()) {
return std::string {ModuleName()};
}
auto relPath = util::Helpers::RelPathByStrippingPrefix(
sourceFile_.GetAbsolutePath().Mutf8(),
ark::os::GetAbsolutePath(context->config->options->ArkTSConfig().RootDir()));
return relPath.empty() ? std::string {sourceFile_.GetFileNameWithExtension().Mutf8()} : relPath;
}
std::optional<std::string> Program::TryRelativeFilePathViaArkTsPaths(const public_lib::Context *context) const
{
if (importInfo_.Lang() != Language::Id::ETS) {
return std::nullopt;
}
if (!Is<util::ModuleKind::MODULE>()) {
return std::nullopt;
}
auto normAbsPath = AbsoluteName().Mutf8();
std::replace(normAbsPath.begin(), normAbsPath.end(), '\\', '/');
const auto &path = context->config->options->ArkTSConfig().Paths();
for (const auto &[key, values] : path) {
for (const auto &value : values) {
auto normValue = value;
std::replace(normValue.begin(), normValue.end(), '\\', '/');
if (normAbsPath == normValue) {
auto dotPos = normAbsPath.rfind('.');
auto ext = (dotPos != std::string::npos) ? normAbsPath.substr(dotPos) : std::string();
return key + ext;
}
if (normValue.back() != '/') {
normValue += '/';
}
if (util::Helpers::StartsWith(normAbsPath, normValue)) {
return key + "/" + normAbsPath.substr(normValue.size());
}
}
}
return std::nullopt;
}
std::string_view Program::GetCachedRelativeFilePath(const public_lib::Context *context) const
{
if (cachedRelativePath_ != nullptr) {
return *cachedRelativePath_;
}
std::string result;
if (auto viaPaths = TryRelativeFilePathViaArkTsPaths(context)) {
result = std::move(*viaPaths);
} else {
result = RelativeFilePath(context);
}
auto *arenaStr = allocator_->New<ArenaString>(result.data(), result.size(), allocator_->Adapter());
cachedRelativePath_ = arenaStr;
return *arenaStr;
}
const lexer::LineIndex &Program::GetLineIndex() const
{
if (lineIndex_ == nullptr) {
lineIndex_ = std::make_unique<lexer::LineIndex>(sourceCode_);
}
return *lineIndex_;
}
void Program::PushVarBinder(varbinder::VarBinder *varbinder)
{
varbinders_.insert_or_assign(compiler::GetPhaseManager()->GetCurrentMajor(), varbinder);
}
const varbinder::VarBinder *Program::VarBinder() const
{
return varbinders_.at(compiler::GetPhaseManager()->GetCurrentMajor());
}
varbinder::VarBinder *Program::VarBinder()
{
return varbinders_.at(compiler::GetPhaseManager()->GetCurrentMajor());
}
checker::Checker *Program::Checker()
{
return checkers_.at(compiler::GetPhaseManager()->GetCurrentMajor());
}
void Program::PushChecker(checker::Checker *checker)
{
if (checkers_.size() > static_cast<size_t>(compiler::GetPhaseManager()->GetCurrentMajor())) {
checkers_.at(compiler::GetPhaseManager()->GetCurrentMajor()) = checker;
return;
}
checkers_.push_back(checker);
}
const checker::Checker *Program::Checker() const
{
return checkers_.at(compiler::GetPhaseManager()->GetCurrentMajor());
}
bool Program::IsBuiltSimultaneously() const
{
if (isBuiltSimultaneously_) {
[[maybe_unused]] auto ctx = compiler::GetPhaseManager()->Context();
ES2PANDA_ASSERT(ctx->config->options->IsSimultaneous());
ES2PANDA_ASSERT(ctx->config->options->GetCompilationMode() >= CompilationMode::SIMULTANEOUS);
return true;
}
return false;
}
std::string Program::Dump() const
{
ir::AstDumper dumper {ast_, SourceCode()};
return dumper.Str();
}
void Program::DumpSilent() const
{
[[maybe_unused]] ir::AstDumper dumper {ast_, SourceCode()};
ES2PANDA_ASSERT(!dumper.Str().empty());
}
varbinder::ClassScope *Program::GlobalClassScope()
{
ES2PANDA_ASSERT(GlobalClass() != nullptr);
ES2PANDA_ASSERT(GlobalClass()->Scope() != nullptr);
return GlobalClass()->Scope()->AsClassScope();
}
const varbinder::ClassScope *Program::GlobalClassScope() const
{
ES2PANDA_ASSERT(GlobalClass() != nullptr);
ES2PANDA_ASSERT(GlobalClass()->Scope() != nullptr);
return GlobalClass()->Scope()->AsClassScope();
}
varbinder::GlobalScope *Program::GlobalScope()
{
ES2PANDA_ASSERT(ast_->Scope()->IsGlobalScope() || ast_->Scope()->IsModuleScope());
return static_cast<varbinder::GlobalScope *>(ast_->Scope());
}
const varbinder::GlobalScope *Program::GlobalScope() const
{
ES2PANDA_ASSERT(ast_->Scope()->IsGlobalScope() || ast_->Scope()->IsModuleScope());
return static_cast<const varbinder::GlobalScope *>(ast_->Scope());
}
void Program::SetPackageInfo(std::string_view mname, util::ModuleKind kind)
{
ES2PANDA_ASSERT((importInfo_.ModuleName() == mname) || mname.empty());
moduleInfo_.moduleName = std::string(mname);
moduleInfo_.modulePrefix = mname.empty() ? "" : std::string(mname).append(compiler::Signatures::METHOD_SEPARATOR);
moduleInfo_.kind = kind;
}
void Program::VerifyDeclarationModule()
{
ES2PANDA_ASSERT(ast_ != nullptr);
if (!IsDeclarationModule()) {
return;
}
ES2PANDA_ASSERT(!Is<util::ModuleKind::PACKAGE>());
for (auto stmt : Ast()->Statements()) {
if (stmt->IsExpressionStatement()) {
ES2PANDA_ASSERT(stmt->AsExpressionStatement()->GetExpression()->AsStringLiteral()->Str() ==
compiler::Signatures::STATIC_PROGRAM_FLAG);
continue;
}
if (stmt->IsDeclare() || stmt->IsTSTypeAliasDeclaration() || stmt->IsETSImportDeclaration() ||
stmt->IsExportNamedDeclaration() || stmt->IsETSReExportDeclaration() || stmt->IsTSInterfaceDeclaration()) {
continue;
}
stmt->AddModifier(ir::ModifierFlags::DECLARE);
}
}
void Program::AddNodeToETSNolintCollection(const ir::AstNode *node, const std::set<ETSWarnings> &warningsCollection)
{
ArenaSet<ETSWarnings> tmp(allocator_->Adapter());
tmp.insert(warningsCollection.begin(), warningsCollection.end());
etsnolintCollection_.insert({node, tmp});
}
bool Program::NodeContainsETSNolint(const ir::AstNode *node, ETSWarnings warning)
{
auto nodeEtsnolints = etsnolintCollection_.find(node);
if (nodeEtsnolints == etsnolintCollection_.end()) {
return false;
}
return nodeEtsnolints->second.find(warning) != nodeEtsnolints->second.end();
}
void Program::SetASTChecked()
{
isAstChecked_ = true;
}
void Program::RemoveAstChecked()
{
isAstChecked_ = false;
}
bool Program::IsASTChecked()
{
return isAstChecked_;
}
void Program::Clear()
{
ResetAst();
RemoveASTLowered();
SetProgramModified(true);
VarBinder()->AsETSBinder()->CleanScopesAndRecordTables(this);
GetExternalDecls()->Direct().clear();
GetExternalDecls()->Get<ModuleKind::MODULE>().clear();
GetExternalDecls()->Get<ModuleKind::SOURCE_DECL>().clear();
}
void Program::PromoteToMainProgram(public_lib::Context *ctx)
{
auto *oldMain = ctx->parserProgram;
ES2PANDA_ASSERT(Is<util::ModuleKind::PACKAGE>());
ES2PANDA_ASSERT(GetExternalDecls()->Empty());
auto *packages = &oldMain->externalDecls_.Get<ModuleKind::PACKAGE>();
auto toRemove = std::find(packages->begin(), packages->end(), this);
if (toRemove != packages->end()) {
packages->erase(toRemove);
}
externalDecls_.transitiveExternals_ = std::move(oldMain->externalDecls_.transitiveExternals_);
externalDecls_.direct_ = std::move(oldMain->externalDecls_.direct_);
oldMain->externalDecls_ = ExternalDecls();
ctx->parserProgram = this;
}
Program::~Program()
{
#ifndef NDEBUG
poisonValue_ = 0;
#endif
}
compiler::CFG *Program::GetCFG()
{
return cfg_;
}
ir::ClassDefinition *Program::GlobalClass()
{
return ast_->AsETSModule()->GlobalClass();
}
const ir::ClassDefinition *Program::GlobalClass() const
{
return ast_->AsETSModule()->GlobalClass();
}
void Program::SetGlobalClass(ir::ClassDefinition *globalClass)
{
ast_->AsETSModule()->SetGlobalClass(globalClass);
}
const compiler::CFG *Program::GetCFG() const
{
return cfg_;
}
template <util::ModuleKind... KINDS>
void ExternalDeclsImpl<KINDS...>::Add(Program *progToInsert)
{
auto inserter = [progToInsert](auto &submap) {
constexpr auto submapKind = GetModuleKindFromSubmapType<decltype(submap)>();
if (progToInsert->Is<submapKind>()) {
submap.push_back(progToInsert->As<submapKind>());
}
};
VisitSubmaps(inserter);
}
using ModuleKind = util::ModuleKind;
template class ExternalDeclsImpl<ModuleKind::MODULE, ModuleKind::SOURCE_DECL, ModuleKind::PACKAGE,
ModuleKind::ETSCACHE_DECL, ModuleKind::METADATA_DECL>;
}