* Copyright (c) 2025-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 "JsdocHelper.h"
#include <ir/ets/etsModule.h>
#include "lexer/lexer.h"
#include "ir/ets/etsTuple.h"
#include "ir/statements/annotationDeclaration.h"
namespace ark::es2panda::parser {
static constexpr std::string_view JSDOC_END = "*/";
static constexpr size_t START_POS = 0;
static constexpr size_t COLLECT_CURRENT_POS = 1;
static constexpr std::string_view POTENTIAL_PREFIX[] = {
"@", "get", "set", "let", "const", "overload", "async", "readonly",
"abstract", "native", "static", "public", "private", "declare", "default", "export"};
static const std::unordered_set<ir::AstNodeType> ANNOTATION_ALLOWED_NODE = {
ir::AstNodeType::METHOD_DEFINITION, ir::AstNodeType::CLASS_DECLARATION,
ir::AstNodeType::STRUCT_DECLARATION, ir::AstNodeType::FUNCTION_DECLARATION,
ir::AstNodeType::TS_INTERFACE_DECLARATION, ir::AstNodeType::CLASS_PROPERTY,
ir::AstNodeType::VARIABLE_DECLARATION, ir::AstNodeType::TS_TYPE_ALIAS_DECLARATION,
ir::AstNodeType::ARROW_FUNCTION_EXPRESSION, ir::AstNodeType::ANNOTATION_DECLARATION};
static const ArenaVector<ir::AnnotationUsage *> &GetAstAnnotationUsage(const ir::AstNode *node)
{
switch (node->Type()) {
case ir::AstNodeType::METHOD_DEFINITION: {
auto *func = node->AsMethodDefinition()->Function();
ES2PANDA_ASSERT(func != nullptr);
return func->Annotations();
}
case ir::AstNodeType::CLASS_DECLARATION:
return node->AsClassDeclaration()->Definition()->Annotations();
case ir::AstNodeType::FUNCTION_DECLARATION:
return node->AsFunctionDeclaration()->Annotations();
case ir::AstNodeType::TS_INTERFACE_DECLARATION:
return node->AsTSInterfaceDeclaration()->Annotations();
case ir::AstNodeType::CLASS_PROPERTY:
return node->AsClassProperty()->Annotations();
case ir::AstNodeType::VARIABLE_DECLARATION:
return node->AsVariableDeclaration()->Annotations();
case ir::AstNodeType::TS_TYPE_ALIAS_DECLARATION:
return node->AsTSTypeAliasDeclaration()->Annotations();
case ir::AstNodeType::ETS_PARAMETER_EXPRESSION:
return node->AsETSParameterExpression()->Annotations();
case ir::AstNodeType::ARROW_FUNCTION_EXPRESSION:
return node->AsArrowFunctionExpression()->Annotations();
case ir::AstNodeType::ANNOTATION_DECLARATION:
return node->AsAnnotationDeclaration()->Annotations();
case ir::AstNodeType::STRUCT_DECLARATION:
return node->AsETSStructDeclaration()->Definition()->Annotations();
default:
ES2PANDA_UNREACHABLE();
}
}
static void HandlePotentialPrefix(parser::JsdocHelper *jsdocHelper)
{
jsdocHelper->Iterator().Reset(jsdocHelper->Node()->Start().index);
if (jsdocHelper->Iterator().Index() != START_POS) {
jsdocHelper->BackwardAndSkipSpace(1U);
}
for (auto prefix : POTENTIAL_PREFIX) {
auto currentSv = jsdocHelper->SourceView(START_POS, jsdocHelper->Iterator().Index() + COLLECT_CURRENT_POS);
if (currentSv.EndsWith(prefix)) {
jsdocHelper->BackwardAndSkipSpace(prefix.length());
}
}
}
static void HandlePotentialPrefixOrAnnotationUsage(parser::JsdocHelper *jsdocHelper)
{
if (ANNOTATION_ALLOWED_NODE.count(jsdocHelper->Node()->Type()) == 0) {
HandlePotentialPrefix(jsdocHelper);
return;
}
auto const &annoUsage = GetAstAnnotationUsage(jsdocHelper->Node());
if (annoUsage.empty()) {
HandlePotentialPrefix(jsdocHelper);
return;
}
const auto annoStartIndex = annoUsage[0]->Range().start.index;
if (annoStartIndex == START_POS) {
jsdocHelper->Iterator().Reset(START_POS);
return;
}
jsdocHelper->Iterator().Reset(annoStartIndex - 1U);
if (jsdocHelper->Iterator().Index() != START_POS) {
jsdocHelper->BackwardAndSkipSpace(1U);
}
}
void JsdocHelper::InitNode(const ir::AstNode *input)
{
auto root = input;
while (root->Parent() != nullptr) {
root = root->Parent();
}
root_ = root;
program_ = root_->AsETSModule()->Program();
sourceCode_ = program_->SourceCode();
iter_ = util::StringView::Iterator(sourceCode_);
if (input->IsClassDefinition()) {
node_ = input->Parent();
} else {
node_ = input;
}
}
bool JsdocHelper::BackWardUntilJsdocStart()
{
while (true) {
const char32_t cp = Iterator().Index() == START_POS ? util::StringView::Iterator::INVALID_CP : PeekBackWard();
switch (cp) {
case util::StringView::Iterator::INVALID_CP: {
break;
}
case lexer::LEX_CHAR_ASTERISK: {
Backward(1);
if (PeekBackWard() == lexer::LEX_CHAR_SLASH) {
return false;
}
if (PeekBackWard() != lexer::LEX_CHAR_ASTERISK) {
continue;
}
if (Iterator().Index() == START_POS) {
break;
}
Backward(1);
if (PeekBackWard() == lexer::LEX_CHAR_SLASH) {
return true;
}
continue;
}
default: {
SkipCpBackward();
continue;
}
}
return false;
}
}
util::StringView JsdocHelper::GetJsdocBackward()
{
HandlePotentialPrefixOrAnnotationUsage(this);
size_t jsdocEndPos = Iterator().Index() + COLLECT_CURRENT_POS;
size_t backwardPos = jsdocEndPos;
auto currentSourceView = SourceView(START_POS, jsdocEndPos);
while (currentSourceView.EndsWith(JSDOC_END)) {
BackwardAndSkipSpace(JSDOC_END.length());
if (!BackWardUntilJsdocStart()) {
break;
}
backwardPos = Iterator().Index();
BackwardAndSkipSpace(1);
currentSourceView = SourceView(START_POS, Iterator().Index() + COLLECT_CURRENT_POS);
}
if (backwardPos == jsdocEndPos) {
return "";
}
return SourceView(backwardPos, jsdocEndPos);
}
util::StringView JsdocHelper::GetLicenseStringFromStart()
{
Iterator().Reset(START_POS);
auto licenseStart = START_POS;
do {
const char32_t cp = Iterator().Peek();
switch (cp) {
case util::StringView::Iterator::INVALID_CP: {
break;
}
case lexer::LEX_CHAR_ASTERISK: {
Forward(1);
if (Iterator().Peek() == lexer::LEX_CHAR_SLASH) {
Forward(1);
break;
}
continue;
}
case lexer::LEX_CHAR_SLASH: {
Forward(1);
if (Iterator().Peek() == lexer::LEX_CHAR_ASTERISK) {
licenseStart = Iterator().Index() - 1;
}
continue;
}
default: {
Iterator().SkipCp();
continue;
}
}
break;
} while (true);
return SourceView(licenseStart, Iterator().Index());
}
}