* 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.
*/
#include "srcDump.h"
#include "public/public.h"
#include "util/helpers.h"
#include "varbinder/ETSBinder.h"
namespace ark::es2panda::ir {
SrcDumper::SrcDumper(Declgen *dg) : dg_(dg) {}
SrcDumper::SrcDumper(const ir::AstNode *node, bool enableJsdocDump, Declgen *dg) : dg_(dg)
{
if (enableJsdocDump) {
jsdocGetter_ = std::make_unique<parser::JsdocHelper>(node);
}
}
void SrcDumper::IncrIndent()
{
indent_ += " ";
}
void SrcDumper::DecrIndent()
{
ES2PANDA_ASSERT(indent_.size() >= 2U);
indent_.resize(indent_.size() - 2U);
}
void SrcDumper::Endl([[maybe_unused]] size_t num)
{
ss_ << '\n';
ss_ << indent_;
}
static bool OnlySpaces(const std::string &s)
{
for (char c : s) {
if (std::isspace(c) == 0) {
return false;
}
}
return true;
}
static std::stringstream NormalizeStream(std::stringstream &iss)
{
std::stringstream oss;
std::string line;
bool lastWasEmpty = false;
while (std::getline(iss, line)) {
if (OnlySpaces(line)) {
if (!lastWasEmpty) {
oss << std::endl;
lastWasEmpty = true;
}
} else {
oss << line << std::endl;
lastWasEmpty = false;
}
}
return oss;
}
std::string SrcDumper::Str() const
{
if (IsDeclgen()) {
std::stringstream ss;
ss << ss_.rdbuf();
ss = NormalizeStream(ss);
return ss.str();
}
return ss_.str();
}
void SrcDumper::Add(std::string_view const str)
{
ss_ << str;
}
void SrcDumper::Add(char ch)
{
ss_ << ch;
}
void SrcDumper::Add(int8_t i)
{
ss_ << static_cast<int32_t>(i);
}
void SrcDumper::Add(int16_t i)
{
ss_ << i;
}
void SrcDumper::Add(int32_t i)
{
ss_ << i;
}
void SrcDumper::Add(int64_t l)
{
ss_ << l;
}
void SrcDumper::Add(float f)
{
ss_ << f;
}
void SrcDumper::Add(double d)
{
ss_ << d;
}
bool SrcDumper::IsDeclgen() const
{
return GetDeclgen() != nullptr;
}
Declgen *SrcDumper::GetDeclgen() const
{
return dg_;
}
void SrcDumper::DumpJsdocBeforeTargetNode(const ir::AstNode *inputNode)
{
if (!jsdocGetter_) {
return;
}
jsdocGetter_->InitNode(inputNode);
auto resJsdoc = jsdocGetter_->GetJsdocBackward();
if (!resJsdoc.Empty()) {
ss_ << resJsdoc.Mutf8();
ss_ << std::endl;
auto parent = inputNode->Parent();
if (inputNode->IsClassDefinition() || parent->IsClassDefinition() || parent->IsTSInterfaceBody() ||
parent->IsScriptFunction()) {
parent = parent->Parent();
}
while (!parent->IsETSModule() || (parent->IsETSModule() && parent->AsETSModule()->IsNamespace())) {
ss_ << " ";
parent = parent->Parent();
if (parent == nullptr) {
break;
}
}
}
}
void SrcDumper::SetDefaultExport() noexcept
{
hasDefaultExport_ = true;
}
bool SrcDumper::HasDefaultExport() const noexcept
{
return IsDeclgen() && hasDefaultExport_;
}
void SrcDumper::DumpExports()
{
if (dg_ == nullptr) {
return;
}
auto *varbinder = dg_->GetCtx()->GetChecker()->VarBinder();
if (!varbinder->IsETSBinder()) {
return;
}
auto const &exportMap = varbinder->AsETSBinder()->GetSelectiveExportAliasMultimap();
if (auto const it = exportMap.find(dg_->GetCtx()->sourceFile->filePath);
it != exportMap.cend() && !it->second.empty()) {
for (auto const &[_, data] : it->second) {
if (data.second->IsExportNamedDeclaration() &&
data.second->AsExportNamedDeclaration()->HasDumpData(HasDefaultExport())) {
ss_ << '\n';
data.second->Dump(this);
ss_ << ';';
}
}
}
}
struct PostDumper {
explicit PostDumper(SrcDumper *dumper) : dumper_ {dumper} {}
void operator()(const ir::AstNode *node)
{
auto g1 = dumper_->BuildAmbientContextGuard();
auto g2 = dumper_->GetDeclgen()->BuildPostDumpIndirectDepsPhaseLockGuard();
dumper_->GetDeclgen()->SetPostDumpIndirectDepsPhase();
auto namespacesCount = ReconstructNamespaces(node);
node->Dump(dumper_);
DestroyNamespaces(namespacesCount);
}
size_t ReconstructNamespaces(const ir::AstNode *node)
{
std::vector<const ir::ClassDefinition *> nsChain;
auto getImmediateNamespace = [](const ir::AstNode *n) {
auto classDef = ark::es2panda::util::Helpers::FindAncestorGivenByType(n, ir::AstNodeType::CLASS_DEFINITION);
if ((classDef != nullptr) && classDef->AsClassDefinition()->IsNamespaceTransformed()) {
return classDef;
}
return static_cast<const ir::AstNode *>(nullptr);
};
for (auto classDef = getImmediateNamespace(node); classDef != nullptr;
classDef = getImmediateNamespace(classDef)) {
ES2PANDA_ASSERT(classDef->AsClassDefinition()->IsNamespaceTransformed());
nsChain.push_back(classDef->AsClassDefinition());
}
for (auto it = nsChain.rbegin(); it != nsChain.rend(); ++it) {
auto ns = *it;
if (ns->IsExported()) {
dumper_->Add("export ");
}
dumper_->TryDeclareAmbientContext();
dumper_->Add("namespace " + ns->Ident()->Name().Mutf8() + " {");
}
dumper_->Add('\n');
return nsChain.size();
}
void DestroyNamespaces(size_t namespacesCount)
{
std::string nsClose(namespacesCount, '}');
nsClose += '\n';
dumper_->Add(nsClose);
}
void operator()([[maybe_unused]] std::monostate )
{
ES2PANDA_UNREACHABLE();
}
private:
SrcDumper *dumper_ {};
};
void Declgen::CollectImport(const ir::ImportDeclaration *import)
{
imports_.push_back(import);
}
static auto AstFromType(Declgen *dg, const checker::Type *type)
{
ES2PANDA_ASSERT(type != nullptr);
auto typeStr = type->ToString();
auto *parser = dg->GetCtx()->parser->AsETSParser();
return parser->CreateFormattedTypeAnnotation(typeStr);
}
static std::string DumpImplicitImportsOfSpecifier(Declgen *dg, ImportSpecifier *specifier)
{
auto type = specifier->Imported()->Variable()->TsType();
if (!type->IsETSFunctionType()) {
return "";
}
auto rettype = type->AsETSFunctionType()->CallSignatures()[0]->ReturnType();
if (!rettype->IsETSObjectType()) {
return "";
}
auto ast = AstFromType(dg, type->AsETSObjectType());
std::string str;
ast->IterateRecursively([&str](ir::AstNode *node) {
if (node->IsIdentifier()) {
str += ", ";
str += node->AsIdentifier()->Name().Utf8();
}
});
return str;
}
[[maybe_unused]] static std::string DumpImport(Declgen *dg, const ImportDeclaration *import)
{
std::string res = "import {";
bool needComma = false;
for (auto node : import->Specifiers()) {
if (needComma) {
res += ", ";
}
needComma = true;
res += node->DumpEtsSrc();
if (node->IsImportSpecifier()) {
res += DumpImplicitImportsOfSpecifier(dg, node->AsImportSpecifier());
}
}
res += "} from \"";
res += import->Source()->Str().Utf8();
res += "\";\n";
return res;
}
void Declgen::DumpImports(std::string &res)
{
if (!imports_.empty()) {
res += '\n';
for (auto const *import : imports_) {
res += import->DumpEtsSrc();
}
}
}
void Declgen::DumpNode(SrcDumper *dumper, const std::string &key)
{
auto it = unExportNode_.find(key);
if (it == unExportNode_.end()) {
return;
}
NodeVariant node = it->second;
unExportNode_.erase(it);
std::visit(PostDumper {dumper}, node);
}
void Declgen::Dump(ir::SrcDumper *dumper, const checker::Type *type)
{
AstFromType(this, type)->Dump(dumper);
}
void SrcDumper::TryDeclareAmbientContext()
{
if (!ambientDeclarationLock_.IsAcquired()) {
ambientDeclarationLock_.Acquire();
Add("declare ");
}
}
void Declgen::Run()
{
while (!taskQueue_.empty()) {
auto task = std::move(taskQueue_.front());
taskQueue_.pop();
task();
}
}
}