diff --git a/ets2panda/ir/base/classElement.h b/ets2panda/ir/base/classElement.h index 45101a888b954d2572312277fcc91a058e44d166..37cf01e50c8de69acc575b98912b7bdb50c4a64c 100644 --- a/ets2panda/ir/base/classElement.h +++ b/ets2panda/ir/base/classElement.h @@ -16,6 +16,7 @@ #ifndef ES2PANDA_PARSER_INCLUDE_AST_CLASS_ELEMENT_H #define ES2PANDA_PARSER_INCLUDE_AST_CLASS_ELEMENT_H +#include #include "ir/statement.h" #include "ir/typed.h" @@ -91,6 +92,20 @@ public: void SetOrigEnumMember(ir::TSEnumMember *enumMember); + [[nodiscard]] const lexer::SourcePosition *AccessModifierSourcePos() const + { + if (accessModifierPos_.has_value()) { + return &(accessModifierPos_.value()); + } + + return nullptr; + } + + void SetAccessModifierSourcePos(std::optional accessModifierPos) noexcept + { + accessModifierPos_ = accessModifierPos; + } + [[nodiscard]] bool IsPrivateElement() const noexcept; [[nodiscard]] const ArenaVector &Decorators() const noexcept @@ -144,6 +159,7 @@ protected: ArenaVector decorators_; bool isComputed_; TSEnumMember *enumMember_ {}; + std::optional accessModifierPos_; // NOLINTEND(misc-non-private-member-variables-in-classes) }; } // namespace ark::es2panda::ir diff --git a/ets2panda/parser/ETSparser.h b/ets2panda/parser/ETSparser.h index b9be64d56c89daa7a37c2ad3e9a7951555c77af3..f6b4a482f8ecb39a1d1c2306a5dc141f9b964734 100644 --- a/ets2panda/parser/ETSparser.h +++ b/ets2panda/parser/ETSparser.h @@ -234,7 +234,7 @@ private: ir::OverloadDeclaration *ParseInterfaceOverload(ir::ModifierFlags modifiers); ir::MethodDefinition *ParseInterfaceMethod(ir::ModifierFlags flags, ir::MethodDefinitionKind methodKind); void ReportAccessModifierError(const lexer::Token &token); - std::tuple ParseClassMemberAccessModifiers(); + std::tuple, ir::ModifierFlags, bool, bool> ParseClassMemberAccessModifiers(); ir::ModifierFlags ParseClassFieldModifiers(bool seenStatic); ir::ModifierFlags ParseClassMethodModifierFlag(); ir::ModifierFlags ParseClassMethodModifiers(bool seenStatic); @@ -355,7 +355,8 @@ private: std::tuple elementFlag, std::tuple posInfo); ir::AstNode *ParseClassElement(const ArenaVector &properties, ir::ClassDefinitionModifiers modifiers, ir::ModifierFlags flags) override; - std::tuple HandleClassElementModifiers(ir::ModifierFlags &memberModifiers); + std::tuple, bool, bool, bool> HandleClassElementModifiers( + ir::ModifierFlags &memberModifiers); void UpdateMemberModifiers(ir::ModifierFlags &memberModifiers, bool &seenStatic); ir::ModifierFlags ParseMemberAccessModifiers(); template diff --git a/ets2panda/parser/ETSparserClasses.cpp b/ets2panda/parser/ETSparserClasses.cpp index 88070064890cef14d4ca56f8a5ceed1ce900edc9..78e6ae3a261f1135dddfc222bde0aecc3d46136f 100644 --- a/ets2panda/parser/ETSparserClasses.cpp +++ b/ets2panda/parser/ETSparserClasses.cpp @@ -15,6 +15,7 @@ #include "ETSparser.h" #include "ETSNolintParser.h" +#include #include #include #include "util/es2pandaMacros.h" @@ -186,15 +187,17 @@ void ETSParser::ReportAccessModifierError(const lexer::Token &token) } } -std::tuple ETSParser::ParseClassMemberAccessModifiers() +using ParseClassMemberAccessModifierResult = + std::tuple, ir::ModifierFlags, bool, bool>; +ParseClassMemberAccessModifierResult ETSParser::ParseClassMemberAccessModifiers() { if (!IsClassMemberAccessModifier(Lexer()->GetToken().Type())) { - return {ir::ModifierFlags::PUBLIC, false, true}; + return {std::nullopt, ir::ModifierFlags::PUBLIC, false, true}; } char32_t nextCp = Lexer()->Lookahead(); if (nextCp == lexer::LEX_CHAR_EQUALS || nextCp == lexer::LEX_CHAR_COLON || nextCp == lexer::LEX_CHAR_LEFT_PAREN) { - return {ir::ModifierFlags::NONE, false, false}; + return {std::nullopt, ir::ModifierFlags::NONE, false, false}; } ir::ModifierFlags accessFlag = ir::ModifierFlags::NONE; @@ -218,7 +221,7 @@ std::tuple ETSParser::ParseClassMemberAccessModif Lexer()->NextToken(lexer::NextTokenFlags::KEYWORD_TO_IDENT); if (Lexer()->GetToken().KeywordType() != lexer::TokenType::KEYW_PROTECTED) { ReportAccessModifierError(token); - return {ir::ModifierFlags::INTERNAL, true, false}; + return {std::nullopt, ir::ModifierFlags::INTERNAL, true, false}; } accessFlag = ir::ModifierFlags::INTERNAL_PROTECTED; break; @@ -227,6 +230,7 @@ std::tuple ETSParser::ParseClassMemberAccessModif ES2PANDA_UNREACHABLE(); } } + auto accessModifierPos = std::optional(token.Loc().start); ReportAccessModifierError(token); @@ -237,7 +241,7 @@ std::tuple ETSParser::ParseClassMemberAccessModif } Lexer()->NextToken(lexer::NextTokenFlags::KEYWORD_TO_IDENT); - return {accessFlag, true, false}; + return {accessModifierPos, accessFlag, true, false}; } static bool IsClassFieldModifier(lexer::TokenType type) @@ -661,15 +665,16 @@ void ETSParser::UpdateMemberModifiers(ir::ModifierFlags &memberModifiers, bool & } } -std::tuple ETSParser::HandleClassElementModifiers(ir::ModifierFlags &memberModifiers) +std::tuple, bool, bool, bool> ETSParser::HandleClassElementModifiers( + ir::ModifierFlags &memberModifiers) { - auto [modifierFlags, isStepToken, isDefault] = ParseClassMemberAccessModifiers(); + auto [accessModifierPos, modifierFlags, isStepToken, isDefault] = ParseClassMemberAccessModifiers(); memberModifiers |= modifierFlags; bool seenStatic = false; UpdateMemberModifiers(memberModifiers, seenStatic); - return {seenStatic, isStepToken, isDefault}; + return {accessModifierPos, seenStatic, isStepToken, isDefault}; } ir::AstNode *ETSParser::ParseClassElementHelper( @@ -715,6 +720,21 @@ ir::AstNode *ETSParser::ParseClassElementHelper( return result; } +void SetAccessModifierSourceLocClassElement(ir::AstNode *result, std::optional accessModifierPos) +{ + if (result == nullptr) { + return; + } + if (result->IsTSInterfaceBody()) { + for (auto *node : result->AsTSInterfaceBody()->Body()) { + SetAccessModifierSourceLocClassElement(node, accessModifierPos); + } + } else if (result->IsMethodDefinition() || result->IsClassProperty() || result->IsClassStaticBlock() || + result->IsOverloadDeclaration()) { + result->AsClassElement()->SetAccessModifierSourcePos(accessModifierPos); + } +} + ir::AstNode *ETSParser::ParseClassElement(const ArenaVector &properties, ir::ClassDefinitionModifiers modifiers, [[maybe_unused]] ir::ModifierFlags flags) @@ -743,11 +763,12 @@ ir::AstNode *ETSParser::ParseClassElement(const ArenaVector &prop return ParseClassStaticBlock(); } - auto [seenStatic, isStepToken, isDefault] = HandleClassElementModifiers(memberModifiers); + auto [accessModifierPos, seenStatic, isStepToken, isDefault] = HandleClassElementModifiers(memberModifiers); auto delcStartLoc = Lexer()->GetToken().Start(); ir::AstNode *result = ParseClassElementHelper(properties, std::make_tuple(modifiers, memberModifiers, flags), std::make_tuple(seenStatic, isStepToken, isDefault), std::make_tuple(startLoc, savedPos)); + SetAccessModifierSourceLocClassElement(result, accessModifierPos); ApplyJsDocInfoToClassElement(result, std::move(jsDocInformation)); ApplyAnnotationsToClassElement(result, std::move(annotations), delcStartLoc); return result; diff --git a/ets2panda/test/unit/plugin/CMakeLists.txt b/ets2panda/test/unit/plugin/CMakeLists.txt index 09b82624dd73418681d9ae148c463405de38862a..9bd859ae5ce791437518864daad14ec4adbc233b 100644 --- a/ets2panda/test/unit/plugin/CMakeLists.txt +++ b/ets2panda/test/unit/plugin/CMakeLists.txt @@ -110,6 +110,7 @@ set(PLUGIN_TESTS "plugin_proceed_to_state_test_interface_duplicate_setter compile.ets ${COMPILE_MODE} cpp ${EXECUTABLE_PLUGIN}" "plugin_proceed_to_state_test_case_block_dump compile.ets ${COMPILE_MODE} cpp ${EXECUTABLE_PLUGIN}" "plugin_proceed_to_state_function_dump compile.ets ${COMPILE_MODE} cpp ${EXECUTABLE_PLUGIN}" + "plugin_proceed_to_state_access_modifier_source_range compile.ets ${COMPILE_MODE} cpp ${EXECUTABLE_PLUGIN}" ) set(RUNTIME_ARGUMENTS diff --git a/ets2panda/test/unit/plugin/plugin_proceed_to_state_access_modifier_source_range.cpp b/ets2panda/test/unit/plugin/plugin_proceed_to_state_access_modifier_source_range.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7eb3e66ffb33995aa64008f390f23e066cb29fe3 --- /dev/null +++ b/ets2panda/test/unit/plugin/plugin_proceed_to_state_access_modifier_source_range.cpp @@ -0,0 +1,157 @@ +/** + * 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 +#include +#include +#include "util.h" +#include "util/es2pandaMacros.h" +#include "ir/statements/blockStatement.h" +#include "ir/statements/classDeclaration.h" +#include "ir/base/classElement.h" +#include "ir/base/classDefinition.h" +#include "public/es2panda_lib.h" + +// NOLINTBEGIN + +static es2panda_Impl *impl = nullptr; +static constexpr size_t FIRST_INDEX = 0; +static constexpr size_t SECOND_INDEX = 1; +static constexpr size_t THIRD_INDEX = 2; +static constexpr size_t FOURTH_INDEX = 3; +static constexpr size_t PUBLIC_SIZE = 6; +static constexpr size_t PROTECTED_SIZE = 9; +static constexpr size_t PRIVATE_SIZE = 7; +static auto source = std::string( + "class A {\n\ + B: int = 1;\n\ + public C: string = \"hello\";\n\ + protected D: boolean = true;\n\ + private E: number = 42;\n}"); + +static bool CheckAccessModifierSourcePos(const ark::es2panda::lexer::SourcePosition *accessModifierPos, + ark::es2panda::lexer::SourcePosition classElementPos, + size_t accessModifierSize) +{ + if (accessModifierPos == nullptr) { + return false; + } + constexpr size_t SPACE_SIZE = 1; + if (accessModifierPos->line != classElementPos.line) { + return false; + } + if (classElementPos.index != accessModifierPos->index + accessModifierSize + SPACE_SIZE) { + return false; + } + + return true; +} + +static ark::es2panda::lexer::SourcePosition *GetAccessModifierSourcePos(es2panda_Context *context, + ark::es2panda::ir::ClassElement *classElement) +{ + es2panda_AstNode *node = reinterpret_cast(classElement); + const es2panda_SourcePosition *sourcePosConst = impl->ClassElementAccessModifierSourcePosConst(context, node); + auto *sourcePos = const_cast(sourcePosConst); + return reinterpret_cast(sourcePos); +} + +static bool FullCheckAccessModifierSourcePos(es2panda_Context *context, ark::es2panda::ir::BlockStatement *rootAst) +{ + ark::es2panda::ir::ClassDefinition *classDef = nullptr; + for (auto &node : rootAst->Statements()) { + if (!node->IsClassDeclaration()) { + continue; + } + classDef = node->AsClassDeclaration()->Definition(); + if (classDef->Ident()->Name() != "A") { + continue; + } + } + auto &body = classDef->Body(); + if (!body[FIRST_INDEX]->AsClassElement()->IsPublic() || + !(GetAccessModifierSourcePos(context, body[FIRST_INDEX]->AsClassElement()) == nullptr)) { + return false; + } + if (!body[SECOND_INDEX]->AsClassElement()->IsPublic() || + (GetAccessModifierSourcePos(context, body[SECOND_INDEX]->AsClassElement()) == nullptr)) { + return false; + } + if (!CheckAccessModifierSourcePos(GetAccessModifierSourcePos(context, body[SECOND_INDEX]->AsClassElement()), + body[SECOND_INDEX]->AsClassElement()->Range().start, PUBLIC_SIZE)) { + return false; + } + if (!body[THIRD_INDEX]->AsClassElement()->IsProtected() || + (GetAccessModifierSourcePos(context, body[THIRD_INDEX]->AsClassElement()) == nullptr)) { + return false; + } + if (!CheckAccessModifierSourcePos(GetAccessModifierSourcePos(context, body[THIRD_INDEX]->AsClassElement()), + body[THIRD_INDEX]->AsClassElement()->Range().start, PROTECTED_SIZE)) { + return false; + } + if (!body[FOURTH_INDEX]->AsClassElement()->IsPrivate() || + (GetAccessModifierSourcePos(context, body[FOURTH_INDEX]->AsClassElement()) == nullptr)) { + return false; + } + if (!CheckAccessModifierSourcePos(GetAccessModifierSourcePos(context, body[FOURTH_INDEX]->AsClassElement()), + body[FOURTH_INDEX]->AsClassElement()->Range().start, PRIVATE_SIZE)) { + return false; + } + return true; +} + +int main(int argc, char **argv) +{ + if (argc < MIN_ARGC) { + return 1; + } + + if (GetImpl() == nullptr) { + return NULLPTR_IMPL_ERROR_CODE; + } + impl = GetImpl(); + const char **args = const_cast(&(argv[1])); + auto config = impl->CreateConfig(argc - 1, args); + auto context = impl->CreateContextFromString(config, source.data(), argv[argc - 1]); + if (context == nullptr) { + std::cerr << "FAILED TO CREATE CONTEXT" << std::endl; + impl->DestroyContext(context); + impl->DestroyConfig(config); + return NULLPTR_CONTEXT_ERROR_CODE; + } + + impl->ProceedToState(context, ES2PANDA_STATE_PARSED); + CheckForErrors("PARSE", context); + auto *program = impl->ContextProgram(context); + auto *rootAst = reinterpret_cast(impl->ProgramAst(context, program)); + if (!FullCheckAccessModifierSourcePos(context, rootAst)) { + impl->DestroyContext(context); + impl->DestroyConfig(config); + return PROCEED_ERROR_CODE; + } + + if (impl->ContextState(context) == ES2PANDA_STATE_ERROR) { + return PROCEED_ERROR_CODE; + } + impl->DestroyContext(context); + impl->DestroyConfig(config); + + return 0; +} + +// NOLINTEND \ No newline at end of file diff --git a/ets2panda/test/unit/sizeof_node_test.cpp b/ets2panda/test/unit/sizeof_node_test.cpp index 8cf6d3d2936dc10a6b5defc55b27b0e1563bd4a7..c7d6831c199d60f334f55c1e618c14f821910de5 100644 --- a/ets2panda/test/unit/sizeof_node_test.cpp +++ b/ets2panda/test/unit/sizeof_node_test.cpp @@ -190,7 +190,8 @@ size_t SizeOfNodeTest::SizeOf() sizeof(node->value_) + sizeof(node->decorators_) + Align(sizeof(node->isComputed_)) + - sizeof(node->enumMember_); + sizeof(node->enumMember_) + + sizeof(node->accessModifierPos_); // clang-format on }