From 8b4fa3a3e97925bc7faa254bb86bf8d15669b953 Mon Sep 17 00:00:00 2001 From: Kira Prokopenko Date: Wed, 13 Sep 2023 17:53:18 +0300 Subject: [PATCH] Add documentation from source code generator Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/I8F7YE Testing: All required pre-merge tests passes. Results are available in the ggwatcher ; cs: 11031 Signed-off-by: Kira Prokopenko --- ets2panda/BUILD.gn | 3 + ets2panda/CMakeLists.txt | 5 + ets2panda/checker/ets/enum.cpp | 13 +- ets2panda/checker/ets/function.cpp | 2 +- ets2panda/checker/types/type.cpp | 2 +- ets2panda/compiler/core/compilerContext.h | 5 + ets2panda/compiler/core/compilerImpl.cpp | 36 +- ets2panda/docgen-app/BUILD.gn | 55 + ets2panda/docgen-app/CMakeLists.txt | 23 + ets2panda/docgen-app/README.md | 28 + ets2panda/docgen-app/main.cpp | 259 +++++ ets2panda/docgen/ETSdocgen.cpp | 1095 ++++++++++++++++++++ ets2panda/docgen/ETSdocgen.h | 33 + ets2panda/docgen/NOdocgen.cpp | 29 + ets2panda/docgen/NOdocgen.h | 33 + ets2panda/docgen/docgen.h | 33 + ets2panda/docgen/json.cpp | 103 ++ ets2panda/docgen/json.h | 72 ++ ets2panda/docgen/render.rb | 737 +++++++++++++ ets2panda/es2panda.h | 7 + ets2panda/ir/astNode.h | 5 + ets2panda/ir/astNodeFlags.h | 3 +- ets2panda/ir/base/classElement.h | 11 + ets2panda/ir/ets/etsFunctionType.cpp | 2 +- ets2panda/ir/ets/etsFunctionType.h | 1 - ets2panda/ir/ets/etsPackageDeclaration.h | 5 + ets2panda/ir/hasDoc.h | 42 + ets2panda/ir/statements/classDeclaration.h | 11 + ets2panda/ir/ts/tsEnumDeclaration.h | 11 + ets2panda/ir/ts/tsEnumMember.h | 11 + ets2panda/ir/ts/tsInterfaceDeclaration.h | 11 + ets2panda/lexer/ETSLexer.cpp | 41 +- ets2panda/lexer/ETSLexer.h | 7 + ets2panda/parser/ETSparser.cpp | 36 +- ets2panda/parser/ETSparser.h | 3 + ets2panda/parser/parserImpl.cpp | 2 +- 36 files changed, 2751 insertions(+), 24 deletions(-) create mode 100644 ets2panda/docgen-app/BUILD.gn create mode 100644 ets2panda/docgen-app/CMakeLists.txt create mode 100644 ets2panda/docgen-app/README.md create mode 100644 ets2panda/docgen-app/main.cpp create mode 100644 ets2panda/docgen/ETSdocgen.cpp create mode 100644 ets2panda/docgen/ETSdocgen.h create mode 100644 ets2panda/docgen/NOdocgen.cpp create mode 100644 ets2panda/docgen/NOdocgen.h create mode 100644 ets2panda/docgen/docgen.h create mode 100644 ets2panda/docgen/json.cpp create mode 100644 ets2panda/docgen/json.h create mode 100755 ets2panda/docgen/render.rb create mode 100644 ets2panda/ir/hasDoc.h diff --git a/ets2panda/BUILD.gn b/ets2panda/BUILD.gn index 6a29659a5f..2fc5b008c0 100644 --- a/ets2panda/BUILD.gn +++ b/ets2panda/BUILD.gn @@ -159,6 +159,9 @@ libes2panda_sources = [ "compiler/lowering/ets/unionLowering.cpp", "compiler/lowering/phase.cpp", "compiler/lowering/util.cpp", + "docgen/ETSdocgen.cpp", + "docgen/NOdocgen.cpp", + "docgen/json.cpp", "es2panda.cpp", "ir/as/namedType.cpp", "ir/as/prefixAssertionExpression.cpp", diff --git a/ets2panda/CMakeLists.txt b/ets2panda/CMakeLists.txt index 463ac9740c..53e48a1790 100644 --- a/ets2panda/CMakeLists.txt +++ b/ets2panda/CMakeLists.txt @@ -407,6 +407,9 @@ set(ES2PANDA_LIB_SRC checker/types/ts/unknownType.cpp checker/types/ts/voidType.cpp util/arktsconfig.cpp + docgen/NOdocgen.cpp + docgen/ETSdocgen.cpp + docgen/json.cpp util/bitset.cpp util/declgenEts2Ts.cpp util/helpers.cpp @@ -490,6 +493,8 @@ panda_add_sanitizers(TARGET es2panda-public SANITIZERS add_subdirectory(aot) +add_subdirectory(docgen-app) + if(PANDA_WITH_TESTS) add_subdirectory(test) endif() diff --git a/ets2panda/checker/ets/enum.cpp b/ets2panda/checker/ets/enum.cpp index fa148b719b..f32a229c0b 100644 --- a/ets2panda/checker/ets/enum.cpp +++ b/ets2panda/checker/ets/enum.cpp @@ -94,9 +94,11 @@ template auto *const array_ident = MakeQualifiedIdentifier(checker->Allocator(), enum_type->GetDecl(), name); - auto *const array_class_prop = checker->Allocator()->New( - array_ident, array_expr, nullptr, - ir::ModifierFlags::STATIC | ir::ModifierFlags::PUBLIC | ir::ModifierFlags::CONST, checker->Allocator(), false); + auto *const array_class_prop = + checker->Allocator()->New(array_ident, array_expr, nullptr, + ir::ModifierFlags::STATIC | ir::ModifierFlags::PUBLIC | + ir::ModifierFlags::CONST | ir::ModifierFlags::AUTO_GENERATED, + checker->Allocator(), false); array_class_prop->SetTsType(array_expr->TsType()); array_class_prop->SetParent(varbinder->Program()->GlobalClass()); array_ident->SetTsType(array_class_prop->TsType()); @@ -151,7 +153,7 @@ template auto *const function = checker->Allocator()->New( function_scope, std::move(params), nullptr, body_block, return_type_annotation, ir::ScriptFunctionFlags::METHOD, - ir::ModifierFlags::PUBLIC, false, Language(Language::Id::ETS)); + ir::ModifierFlags::PUBLIC | ir::ModifierFlags::AUTO_GENERATED, false, Language(Language::Id::ETS)); varbinder->AsETSBinder()->BuildInternalName(function); varbinder->AsETSBinder()->AddCompilableFunction(function); @@ -168,7 +170,8 @@ void MakeMethodDef(ETSChecker *const checker, varbinder::ETSBinder *const varbin function->SetParent(function_expr); auto *const method_def = checker->Allocator()->New( - ir::MethodDefinitionKind::METHOD, ident, function_expr, ir::ModifierFlags::PUBLIC, checker->Allocator(), false); + ir::MethodDefinitionKind::METHOD, ident, function_expr, + ir::ModifierFlags::PUBLIC | ir::ModifierFlags::AUTO_GENERATED, checker->Allocator(), false); method_def->SetParent(varbinder->Program()->GlobalClass()); function_expr->SetParent(method_def); diff --git a/ets2panda/checker/ets/function.cpp b/ets2panda/checker/ets/function.cpp index ed412dc646..177fbd5997 100644 --- a/ets2panda/checker/ets/function.cpp +++ b/ets2panda/checker/ets/function.cpp @@ -1589,7 +1589,7 @@ ir::ModifierFlags ETSChecker::GetFlagsForProxyLambda(bool is_static) { // If every captured variable in the lambda is local variable, the proxy method can be 'static' since it doesn't // use any of the classes properties - ir::ModifierFlags flags = ir::ModifierFlags::PUBLIC; + ir::ModifierFlags flags = ir::ModifierFlags::PUBLIC | ir::ModifierFlags::AUTO_GENERATED; if (is_static) { flags |= ir::ModifierFlags::STATIC; diff --git a/ets2panda/checker/types/type.cpp b/ets2panda/checker/types/type.cpp index f50b36c8de..81703a114d 100644 --- a/ets2panda/checker/types/type.cpp +++ b/ets2panda/checker/types/type.cpp @@ -85,7 +85,7 @@ void Type::ToStringAsSrc(std::stringstream &ss) const void Type::Identical(TypeRelation *relation, Type *other) { - relation->Result(type_flags_ == other->TypeFlags()); + relation->Result(TypeFlags() == other->TypeFlags()); } bool Type::AssignmentSource([[maybe_unused]] TypeRelation *relation, [[maybe_unused]] Type *target) diff --git a/ets2panda/compiler/core/compilerContext.h b/ets2panda/compiler/core/compilerContext.h index 96e7144d45..427655d608 100644 --- a/ets2panda/compiler/core/compilerContext.h +++ b/ets2panda/compiler/core/compilerContext.h @@ -131,6 +131,11 @@ public: return options_.is_eval; } + const auto &GetMakeDoc() const + { + return options_.make_doc; + } + private: varbinder::VarBinder *varbinder_; checker::Checker *checker_; diff --git a/ets2panda/compiler/core/compilerImpl.cpp b/ets2panda/compiler/core/compilerImpl.cpp index 0d33af9dd5..d351d8dd4c 100644 --- a/ets2panda/compiler/core/compilerImpl.cpp +++ b/ets2panda/compiler/core/compilerImpl.cpp @@ -15,6 +15,8 @@ #include "compilerImpl.h" +#include "docgen/NOdocgen.h" +#include "docgen/ETSdocgen.h" #include "compiler/core/compilerContext.h" #include "compiler/core/compileQueue.h" #include "compiler/core/compilerImpl.h" @@ -90,7 +92,7 @@ using EmitCb = std::function; using PhaseListGetter = std::function()>; template + typename CodeGen, typename RegSpiller, typename FunctionEmitter, typename Emitter, typename Docgen> static pandasm::Program *CreateCompiler(const CompilationUnit &unit, const PhaseListGetter &get_phases, const EmitCb &emit_cb) { @@ -124,6 +126,22 @@ static pandasm::Program *CreateCompiler(const CompilationUnit &unit, const Phase emitter.GenAnnotation(); + if (auto &make_doc = unit.options.make_doc; make_doc) { + std::ostream *out; + std::ofstream fout; + if (make_doc.value().output_path.empty()) { + out = &std::cout; + } else { + fout.open(make_doc.value().output_path); + if (!fout.is_open()) { + throw Error {ErrorType::GENERIC, make_doc.value().output_path, "can't open file"}; + } + out = &fout; + } + *out << Docgen {}.Generate(&program) << std::endl; + return nullptr; + } + return emit_cb(&context); } @@ -135,26 +153,26 @@ pandasm::Program *CompilerImpl::Compile(const CompilationUnit &unit) case ScriptExtension::TS: { return CreateCompiler(unit, compiler::GetTrivialPhaseList, - emit_cb); + compiler::JSFunctionEmitter, compiler::JSEmitter, docgen::NODocgen>( + unit, compiler::GetTrivialPhaseList, emit_cb); } case ScriptExtension::AS: { return CreateCompiler(unit, compiler::GetTrivialPhaseList, - emit_cb); + compiler::JSFunctionEmitter, compiler::JSEmitter, docgen::NODocgen>( + unit, compiler::GetTrivialPhaseList, emit_cb); } case ScriptExtension::ETS: { return CreateCompiler(unit, compiler::GetETSPhaseList, - emit_cb); + compiler::ETSFunctionEmitter, compiler::ETSEmitter, docgen::ETSDocgen>( + unit, compiler::GetETSPhaseList, emit_cb); } case ScriptExtension::JS: { return CreateCompiler(unit, compiler::GetTrivialPhaseList, - emit_cb); + compiler::JSFunctionEmitter, compiler::JSEmitter, docgen::NODocgen>( + unit, compiler::GetTrivialPhaseList, emit_cb); } default: { UNREACHABLE(); diff --git a/ets2panda/docgen-app/BUILD.gn b/ets2panda/docgen-app/BUILD.gn new file mode 100644 index 0000000000..a50c9a9bd1 --- /dev/null +++ b/ets2panda/docgen-app/BUILD.gn @@ -0,0 +1,55 @@ +# Copyright (c) 2021-2022 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. + +import("//arkcompiler/runtime_core/static_core/ark_config.gni") +import("//build/ohos.gni") + +ohos_executable("es2panda") { + sources = [ "main.cpp" ] + + include_dirs = [ "$target_gen_dir" ] + + configs = [ + sdk_libc_secshared_config, + "$ark_root:ark_config", + "$ark_es2panda_root:libes2panda_public_config", + "$ark_root/assembler:arkassembler_public_config", + "$ark_root/libpandafile:arkfile_public_config", + "$ark_root/libpandabase:arkbase_public_config", + "$ark_root/bytecode_optimizer:bytecodeopt_public_config", + "$ark_root/compiler:arkcompiler_public_config", + "$ark_root/runtime:arkruntime_public_config", + ] + + deps = [ + "$ark_es2panda_root:libes2panda_frontend_static", + "$ark_es2panda_root:libes2panda_public_frontend_static", + ] + external_deps = [ + "runtime_core:libarktsassembler_package", + "runtime_core:libarktsbase_package", + "runtime_core:libarktsbytecodeopt_package", + "runtime_core:libarktscompiler_package", + "runtime_core:libarktsfile_package", + ] + + libs = platform_libs + ldflags = platform_ldflags + if (is_linux) { + libs += [ "stdc++fs" ] + } + + install_enable = true + part_name = "ets_frontend" + subsystem_name = "arkcompiler" +} diff --git a/ets2panda/docgen-app/CMakeLists.txt b/ets2panda/docgen-app/CMakeLists.txt new file mode 100644 index 0000000000..ccdeabe9c9 --- /dev/null +++ b/ets2panda/docgen-app/CMakeLists.txt @@ -0,0 +1,23 @@ +panda_add_executable(ets_docgen main.cpp) + +panda_target_link_libraries(ets_docgen es2panda-lib) + +panda_target_include_directories(ets_docgen PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) +panda_target_include_directories(ets_docgen PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/..) + +panda_target_compile_definitions(ets_docgen + PRIVATE "DEFAULT_ARKTSCONFIG=\"${GENERATED_DIR}/arktsconfig.json\"" +) + +panda_add_sanitizers(TARGET ets_docgen SANITIZERS ${PANDA_SANITIZERS_LIST}) + +add_custom_command( + COMMENT "Copying ets_doc2rst to ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ets_doc2rst.rb" + OUTPUT "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ets_doc2rst.rb" + COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}/../docgen/render.rb" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ets_doc2rst.rb" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/../docgen/render.rb" +) +add_custom_target(es2panda_copy_ets_doc2rst + DEPENDS "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ets_doc2rst.rb" +) +add_dependencies(ets_docgen es2panda_copy_ets_doc2rst) diff --git a/ets2panda/docgen-app/README.md b/ets2panda/docgen-app/README.md new file mode 100644 index 0000000000..fa5bc6c0bd --- /dev/null +++ b/ets2panda/docgen-app/README.md @@ -0,0 +1,28 @@ +# Ets documentation generator + +## Parts + +### ets_docgen +C++ file that uses es2panda library to generate documentation. It operates in few modes: file, project, stdlib; it builds documentation for corresponding es2panda target. As a result it produces `.json` file, that contains all packages that passed a filter and their entities. This file can be distributed with closed-source code. + +### ets_doc2rst.rb +Ruby program that transforms one or more `.json` files from `ets_docgen` into `.rst` files. Types are linked to their declaration. + +## Usage +Generate standard library documentation to stdout in the following way: +```sh +./ets_docgen --mode=stdlib "" / | ./ets_doc2rst.rb - +``` + +Or save to a few files: +```sh +ets_docgen --mode=stdlib "" / \ + | ets_doc2rst.rb \ + --output packages \ + --separate \ + --error-on-comment-absence \ + --error-behaviour=omit \ + --min-visibility=protected \ + - +``` +For more options, see `--help` of these executables. They support working with different file modes and provide few filtering options, including filtering by package name, visibility, and tags. diff --git a/ets2panda/docgen-app/main.cpp b/ets2panda/docgen-app/main.cpp new file mode 100644 index 0000000000..dd8fac35b0 --- /dev/null +++ b/ets2panda/docgen-app/main.cpp @@ -0,0 +1,259 @@ +/** + * Copyright (c) 2021-2022 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 "es2panda.h" +#include "mem/arena_allocator.h" +#include "mem/pool_manager.h" +#include "util/arktsconfig.h" +#include "utils/pandargs.h" + +namespace panda::es2panda::docgenapp { +using mem::MemConfig; + +class MemManager { +public: + explicit MemManager() + { + constexpr auto COMPILER_SIZE = 256_MB; + + MemConfig::Initialize(0, 0, COMPILER_SIZE, 0, 0, 0); + PoolManager::Initialize(PoolType::MMAP); + } + + NO_COPY_SEMANTIC(MemManager); + NO_MOVE_SEMANTIC(MemManager); + + ~MemManager() + { + PoolManager::Finalize(); + MemConfig::Finalize(); + } +}; + +class Options { +public: + Options() = default; + + bool ParseModule() const + { + return false; + } + + std::optional Parse(int argc, const char **argv) + { + panda::PandArg help("help", false, "print help message"); + + panda::PandArg std_lib("stdlib", "", "Path to standard library"); + panda::PandArg op_ets_module("ets-module", false, "Compile the input as ets-module"); + panda::PandArg arkts_config("arktsconfig", DEFAULT_ARKTSCONFIG, + "Path to arkts configuration file"); + panda::PandArg op_make_doc_secure("doc-secure", true, "Omit file paths from error traces"); + panda::PandArg op_make_doc_filter("doc-filter", ".*", + "Make documentation package name filter (regex)"); + panda::PandArg mode("mode", "file", "compilation mode: file|project|stdlib = file"); + + panda::PandArg output_file("output", "", "output json"); + panda::PandArg input_file("input", "", "input path"); + + panda::PandArgParser arg_parser; + + arg_parser.Add(&help); + arg_parser.Add(&std_lib); + arg_parser.Add(&op_ets_module); + arg_parser.Add(&arkts_config); + arg_parser.Add(&op_make_doc_secure); + arg_parser.Add(&op_make_doc_filter); + arg_parser.Add(&mode); + + arg_parser.EnableTail(); + arg_parser.PushBackTail(&output_file); + arg_parser.PushBackTail(&input_file); + + if (!arg_parser.Parse(argc, argv)) { + return arg_parser.GetErrorString() + arg_parser.GetHelpString(); + } + + CheckHelp(help.GetValue(), arg_parser); + + compiler_options_.std_lib = std_lib.GetValue(); + compiler_options_.arkts_config = std::make_shared(arkts_config.GetValue()); + + if (!compiler_options_.arkts_config->Parse()) { + return "can't parse arkts config " + arkts_config.GetValue(); + } + + if (auto err = ParseCompilationMode(compiler_options_.compilation_mode, mode.GetValue()); err) { + return err; + } + + compiler_options_.is_ets_module = op_ets_module.GetValue(); + compiler_options_.make_doc.emplace(); + compiler_options_.make_doc->secure = op_make_doc_secure.GetValue(); + compiler_options_.make_doc->filter = op_make_doc_filter.GetValue(); + compiler_options_.make_doc->output_path = output_file.GetValue(); + compiler_output_ = output_file.GetValue(); + source_file_ = input_file.GetValue(); + + return std::nullopt; + } + + const es2panda::CompilerOptions &CompilerOptions() const + { + return compiler_options_; + } + + const std::string &CompilerOutput() const + { + return compiler_output_; + } + + void SetCompilerOutput(const std::string &compiler_output) + { + compiler_output_ = compiler_output; + } + + const std::string &SourceFile() const + { + return source_file_; + } + +private: + es2panda::CompilerOptions compiler_options_ {}; + std::string compiler_output_; + std::string source_file_; + + static std::optional ParseCompilationMode(CompilationMode &mode, std::string_view str_mode) + { + if (str_mode == "stdlib") { + mode = CompilationMode::GEN_STD_LIB; + } else if (str_mode == "project") { + mode = CompilationMode::PROJECT; + } else if (str_mode == "file") { + mode = CompilationMode::SINGLE_FILE; + } else { + return "unknown mode " + std::string {str_mode}; + } + return std::nullopt; + } + + static void CheckHelp(bool print_help, PandArgParser &arg_parser) + { + if (print_help) { + std::cout << arg_parser.GetHelpString() << std::endl; + exit(0); + } + } +}; + +static int CompileFromSource(es2panda::Compiler &compiler, es2panda::SourceFile &input, Options *options) +{ + [[maybe_unused]] auto program = compiler.Compile(input, options->CompilerOptions()); + + ASSERT(program == nullptr); + + const auto &err = compiler.GetError(); + + // Intentional exit or --parse-only option usage. + if (err.Type() == ErrorType::INVALID) { + return 0; + } + + std::cout << err.TypeString() << ": " << err.Message(); + std::cout << " [" << (err.File().empty() ? "" : err.File()) << ":" << err.Line() << ":" << err.Col() << "]" + << std::endl; + + return err.ErrorCode(); +} + +static int CompileFromConfig(es2panda::Compiler &compiler, Options *options) +{ + auto compilation_list = FindProjectSources(options->CompilerOptions().arkts_config); + if (compilation_list.empty()) { + std::cerr << "Error: No files to compile" << std::endl; + return 1; + } + + unsigned overall_res = 0; + for (auto &[src, dst] : compilation_list) { + std::ifstream input_stream(src); + if (input_stream.fail()) { + std::cerr << "Error: Failed to open file: " << src << std::endl; + return 1; + } + + std::stringstream ss; + ss << input_stream.rdbuf(); + std::string parser_input = ss.str(); + input_stream.close(); + es2panda::SourceFile input(src, parser_input, options->ParseModule()); + options->SetCompilerOutput(dst); + + auto res = CompileFromSource(compiler, input, options); + if (res != 0) { + std::cout << "> ets_docgen: failed to compile from " << src << " to " << dst << std::endl; + overall_res |= static_cast(res); + } + } + + return overall_res; +} + +static int Run(int argc, const char **argv) +{ + auto options = std::make_unique(); + + if (auto err = options->Parse(argc, argv); err) { + std::cerr << err.value() << std::endl; + return 1; + } + + Logger::ComponentMask mask {}; + mask.set(Logger::Component::ES2PANDA); + Logger::InitializeStdLogging(Logger::Level::WARNING, mask); + + es2panda::Compiler compiler {ScriptExtension::ETS}; + + if (options->CompilerOptions().compilation_mode == CompilationMode::PROJECT) { + return CompileFromConfig(compiler, options.get()); + } + + std::string_view source_file; + std::string parser_input; + if (options->CompilerOptions().compilation_mode == CompilationMode::GEN_STD_LIB) { + source_file = "etsstdlib.ets"; + parser_input = ""; + } else { + source_file = options->SourceFile(); + std::ifstream fle {std::string {source_file}}; + if (!fle.is_open() || fle.bad()) { + std::cerr << "can't read " << source_file << std::endl; + return 1; + } + parser_input.assign(std::istreambuf_iterator(fle), std::istreambuf_iterator()); + } + es2panda::SourceFile input(source_file, parser_input, options->ParseModule()); + return CompileFromSource(compiler, input, options.get()); +} + +} // namespace panda::es2panda::docgenapp + +int main(int argc, const char **argv) +{ + panda::es2panda::docgenapp::MemManager mm; + return panda::es2panda::docgenapp::Run(argc, argv); +} diff --git a/ets2panda/docgen/ETSdocgen.cpp b/ets2panda/docgen/ETSdocgen.cpp new file mode 100644 index 0000000000..781ff1f52b --- /dev/null +++ b/ets2panda/docgen/ETSdocgen.cpp @@ -0,0 +1,1095 @@ +/** + * Copyright (c) 2021 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 "ETSdocgen.h" +#include "json.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "generated/signatures.h" + +#include "parser/program/program.h" + +#include "varbinder/varbinder.h" +#include "varbinder/variable.h" +#include "varbinder/variableFlags.h" + +#include "ir/astNode.h" +#include "ir/base/classProperty.h" +#include "ir/base/methodDefinition.h" +#include "ir/statements/blockStatement.h" +#include "ir/statements/functionDeclaration.h" +#include "ir/statements/classDeclaration.h" +#include "ir/ets/etsPackageDeclaration.h" +#include "ir/ts/tsQualifiedName.h" +#include "ir/base/scriptFunction.h" +#include "ir/expressions/identifier.h" +#include "ir/base/classDefinition.h" +#include "ir/ets/etsTypeReference.h" +#include "ir/ets/etsTypeReferencePart.h" +#include "ir/ets/etsParameterExpression.h" +#include "ir/hasDoc.h" +#include "ir/ts/tsTypeParameterInstantiation.h" +#include "ir/ts/tsClassImplements.h" +#include "ir/ts/tsInterfaceBody.h" +#include "ir/ts/tsInterfaceDeclaration.h" +#include "ir/ts/tsInterfaceHeritage.h" +#include "ir/ts/tsTypeParameter.h" +#include "ir/ets/etsScript.h" +#include "ir/ts/tsTypeAliasDeclaration.h" +#include "ir/ts/tsEnumDeclaration.h" +#include "ir/expressions/literals/numberLiteral.h" +#include "ir/expressions/literals/stringLiteral.h" +#include "ir/ts/tsEnumMember.h" +#include "ir/base/spreadElement.h" + +#include "checker/types/type.h" +#include "checker/types/ets/etsFunctionType.h" +#include "checker/types/ets/etsObjectType.h" +#include "checker/types/ets/etsArrayType.h" +#include "checker/checker.h" +#include "checker/ETSchecker.h" + +#include "compiler/core/compilerContext.h" +#include "es2panda.h" + +namespace panda::es2panda::docgen { + +namespace { + +template +T &ConstructDefaultGet(JObject &obj, std::string_view name) +{ + return std::get(obj.emplace(name, T {}).first->second); +} + +template +T &ConstructDefaultBackGet(JArray &obj) +{ + return std::get(obj.emplace_back(T {})); +} + +template +class ScopedChanger { +public: + ScopedChanger(T &to, T val) : storage_(to), old_value_(std::move(to)) + { + to = std::move(val); + } + + NO_COPY_SEMANTIC(ScopedChanger); + NO_MOVE_SEMANTIC(ScopedChanger); + + ~ScopedChanger() + { + storage_ = std::move(old_value_); + } + +private: + T &storage_; + T old_value_; +}; + +template +ScopedChanger(T &, D) -> ScopedChanger; + +class ErrorHandler { +public: + explicit ErrorHandler(JArray *errors) : errors_(errors) {} + + void PushTrace(JValue v) + { + trace_.emplace_back(std::move(v)); + } + + void PopTrace() + { + trace_.pop_back(); + } + + void Error(JValue text) + { + errors_->emplace_back(JObject {{"text", JValue {std::move(text)}}, {"trace", JValue {trace_}}}); + } + + template + void WithTrace(JValue trace, F action) + { + PushTrace(std::move(trace)); + auto fn = [](ErrorHandler *c) { c->PopTrace(); }; + auto clean = std::unique_ptr(this, fn); + action(); + } + +private: + JArray trace_; + JArray *errors_; +}; + +bool IsSpace(char a) +{ + return std::isspace(a) != 0; +} + +template +std::string_view SpanWhileTrue(std::string_view &str, F pred) +{ + size_t idx = 0; + while (idx < str.size() && pred(str[idx])) { + idx++; + } + auto ret = str.substr(0, idx); + str = str.substr(idx); + return ret; +} + +std::string_view SpanSpaces(std::string_view &str) +{ + return SpanWhileTrue(str, [](auto c) { return IsSpace(c); }); +} + +enum class DocMode { TEXT, CODE }; + +class ETSDocgenImpl; + +class DocParser { +protected: + template + T *InitializeField(const std::string &name) + { + return &std::get(result_.insert({name, JValue {T {}}}).first->second); + } + +private: + JArray *InOther(const std::string_view &name) + { + auto &obj = ConstructDefaultBackGet(*other_); + obj.emplace("kind", "other"); + obj.emplace("title", name); + return &std::get(obj.emplace("data", JArray {}).first->second); + } + +public: + using Text = JArray; + + DocParser(std::string_view &rest, ETSDocgenImpl *parent) : rest_(rest), parent_(parent) + { + brief_ = InitializeField("brief"); + description_ = InitializeField("description"); + deprecated_ = InitializeField("deprecated"); + other_ = InitializeField("other"); + tags_ = InitializeField("tags"); + } + + std::pair Start() + { + return {DocMode::TEXT, brief_}; + } + + virtual std::pair HandleNextTextCommand(std::string_view command, JArray *previous) + { + if (command == "brief") { + return {DocMode::TEXT, brief_}; + } + if (command == "description") { + return {DocMode::TEXT, description_}; + } + if (command == "deprecated") { + return {DocMode::TEXT, deprecated_}; + } + if (command == "example") { + return {DocMode::CODE, InOther(command)}; + } + if (command == "tparam") { + return HandleTParam(); + } + if (command == "link") { + return HandleTextLink(previous); + } + if (command == "tag") { + auto name = FetchId(); + tags_->push_back(JValue {JString {name}}); + return {DocMode::TEXT, previous}; + } + static const std::set DEFAULT_TEXT_COMMANDS = {"note", "remark", "info", "see"}; + if (DEFAULT_TEXT_COMMANDS.count(command) == 0) { + std::string txt = "unknown tag `"; + txt += command; + txt += "`"; + Error(JValue {txt}); + } + std::ignore = previous; + return {DocMode::TEXT, InOther(command)}; + } + + std::string_view FetchId() + { + SpanSpaces(rest_); + return SpanWhileTrue(rest_, [](auto c) { return std::isalnum(c) != 0 || c == '_'; }); + } + + JValue FetchType(); + + void Error(JValue val); + + JObject Finish() + { + ASSERT(rest_.empty()); + if (tags_->empty()) { + result_.erase("tags"); + } + return result_; + } + +private: + std::string_view &rest_; + ETSDocgenImpl *parent_; + + JObject result_ {}; + Text *brief_ {}; + Text *description_ {}; + Text *deprecated_ {}; + JArray *other_ {}; + JArray *tags_ {}; + + std::pair HandleTParam() + { + auto id = FetchId(); + auto &targs = ConstructDefaultGet(result_, "targs"); + auto &obj = ConstructDefaultBackGet(targs); + obj.emplace("name", id); + return {DocMode::TEXT, &std::get(obj.emplace("data", JArray {}).first->second)}; + } + + std::pair HandleTextLink(JArray *previous) + { + if (!previous->empty() && std::holds_alternative(previous->back())) { + auto &str = std::get(previous->back()); + if (!str.empty() && str.back() == '{') { + str.pop_back(); + } + } + if (!rest_.empty() && rest_.front() == '{') { + rest_ = rest_.substr(1); + } + JObject link; + link.emplace("kind", "link"); + SpanSpaces(rest_); + link.emplace("link", SpanWhileTrue(rest_, [](auto c) { return !IsSpace(c) && c != '}' && c != '|'; })); + SpanSpaces(rest_); + if (!rest_.empty() && rest_.front() == '|') { + rest_ = rest_.substr(1); + SpanSpaces(rest_); + link.emplace("text", SpanWhileTrue(rest_, [](auto c) { return c != '}'; })); + } + if (!rest_.empty() && rest_.front() == '}') { + rest_ = rest_.substr(1); + } + previous->emplace_back(std::move(link)); + return {DocMode::TEXT, previous}; + } +}; + +class MethodDocParser : public DocParser { +public: + using DocParser::DocParser; + + std::pair HandleNextTextCommand(std::string_view command, JArray *previous) override + { + if (command == "returns") { + return {DocMode::TEXT, returns_}; + } + if (command == "throws") { + auto type = FetchType(); + auto &obj = ConstructDefaultBackGet(*throws_); + obj.emplace("type", std::move(type)); + return {DocMode::TEXT, &std::get(obj.emplace("data", JArray {}).first->second)}; + } + if (command == "param") { + auto id = FetchId(); + auto &obj = ConstructDefaultBackGet(*args_); + obj.emplace("name", id); + return {DocMode::TEXT, &std::get(obj.emplace("data", JArray {}).first->second)}; + } + return DocParser::HandleNextTextCommand(command, previous); + } + +private: + JArray *returns_ = InitializeField("returns"); + JArray *args_ = InitializeField("params"); + JArray *throws_ = InitializeField("throws"); +}; + +class ETSDocgenImpl { +public: + std::string Str(); + + ETSDocgenImpl &Run(parser::Program *prog); + + [[nodiscard]] JValue DumpType(ir::ETSTypeReference *ref) + { + auto type = DumpType(ref->TsType()); + if (std::holds_alternative(type)) { + AddGenericInfo(std::get(type), ref->Part()->TypeParams()); + } + return type; + } + + [[nodiscard]] JValue DumpObjectType(checker::ETSObjectType *obj_type) + { + auto r = obj_type->HasObjectFlag(checker::ETSObjectFlags::FUNCTIONAL) ? DumpFunctionalType(obj_type) + : obj_type->HasObjectFlag(checker::ETSObjectFlags::TYPE_PARAMETER) ? DumpVarType(obj_type) + : DumpClassType(obj_type); + JArray alts = {std::move(r)}; + + auto nullable = false; + auto undefinable = false; + if (obj_type->HasObjectFlag(checker::ETSObjectFlags::TYPE_PARAMETER)) { + if (obj_type->GetBaseType() != nullptr) { + nullable = obj_type->ContainsNull(); + undefinable = obj_type->ContainsUndefined(); + } + } else { + nullable = obj_type->ContainsNull(); + undefinable = obj_type->ContainsUndefined(); + } + if (nullable) { + alts.emplace_back("null"); + } + if (undefinable) { + alts.emplace_back("undefined"); + } + + if (alts.size() == 1) { + auto ret = std::move(alts[0]); + return ret; + } + JObject union_type = {{"kind", JValue {"union"}}}; + union_type.emplace("alts", std::move(alts)); + return JValue {union_type}; + } + + [[nodiscard]] JValue DumpType(checker::Type *type) + { + if (type == nullptr) { + return JNULL; + } + + if (type->IsETSArrayType()) { + JObject r; + r.emplace("kind", "arr"); + r.emplace("elem", DumpType(type->AsETSArrayType()->ElementType())); + return JValue {r}; + } + if (type->IsETSObjectType()) { + return DumpObjectType(type->AsETSObjectType()); + } + ASSERT(!type->IsNonPrimitiveType()); + std::stringstream out; + type->ToString(out); + return JValue {out.str()}; + } + + ErrorHandler &Errors() + { + return errors_; + } + + compiler::CompilerContext *Context() const + { + return ctx_; + } + + varbinder::Scope *Scope() const + { + return scope_; + } + +private: + JValue result_ {JObject {}}; + JValue *insert_point_ {&result_}; + std::vector trace_; + ErrorHandler errors_ {&std::get(std::get(result_).emplace("errors", JArray {}).first->second)}; + compiler::CompilerContext *ctx_ {}; + varbinder::Scope *scope_ {}; + + template + class CommentParser { + public: + static_assert(std::is_base_of_v); + + CommentParser(std::string_view &comment, ETSDocgenImpl *parent) : comment_(comment), parser_(comment, parent) {} + + JValue Run() + { + while (!comment_.empty()) { + if (state_.first == DocMode::CODE) { + HandleCode(); + continue; + } + SkipIntro(true); + if (comment_.empty()) { + break; + } + // empty line + if (comment_.front() == '\n') { + if (!text_.empty() && text_.back() != '\n') { + text_ += '\n'; + } + continue; + } + + HandleTextLine(); + } + FlushText(); + return JValue {parser_.Finish()}; + } + + private: + std::string_view &comment_; + T parser_; + std::pair state_ = parser_.Start(); + std::string code_; + std::string text_; + + void SkipIntro(bool all) + { + while (!comment_.empty() && IsSpace(comment_.front())) { + comment_ = comment_.substr(1); + } + while (!comment_.empty() && comment_.front() == '*') { + comment_ = comment_.substr(1); + } + while (all && !comment_.empty() && IsSpace(comment_.front()) && comment_.front() != '\n') { + comment_ = comment_.substr(1); + } + } + + void HandleCode() + { + auto lines = ReadCode(); + JObject res {{"kind", JValue {"block-code"}}, {"data", JValue {PrepareCode(lines)}}}; + state_.second->emplace_back(std::move(res)); + state_.first = DocMode::TEXT; + } + + void HandleTextLine() + { + while (!comment_.empty()) { + auto fr = comment_.front(); + comment_ = comment_.substr(1); + if (fr == '\n') { + text_ += ' '; + break; + } + if (fr != '@') { + text_ += fr; + continue; + } + FlushText(); + auto command = parser_.FetchId(); + state_ = parser_.HandleNextTextCommand(command, state_.second); + break; + } + } + + void FlushText() + { + if (!text_.empty()) { + state_.second->emplace_back(std::move(text_)); + text_.clear(); + } + } + + std::vector ReadCode() + { + std::vector lines {}; + do { + auto pos = comment_.find('\n'); + if (pos == 0 && !lines.empty()) { + break; + } + lines.emplace_back(comment_.substr(0, pos)); + if (pos == std::string::npos) { + comment_ = ""; + } else { + comment_ = comment_.substr(pos + 1); + } + SkipIntro(false); + } while (!comment_.empty()); + return lines; + } + + std::string PrepareCode(std::vector &lines) + { + size_t max_common_space = lines.empty() ? 0 : std::numeric_limits::max(); + for (auto &l : lines) { + while (!l.empty() && IsSpace(l.back())) { + l = l.substr(0, l.size() - 1); + } + auto pos = l.find_first_not_of(" \t"); + if (pos != std::string::npos) { + max_common_space = std::min(max_common_space, pos); + } + } + std::string result; + for (auto &l : lines) { + if (max_common_space < l.size()) { + result += l.substr(max_common_space); + } + result += "\n"; + } + return result; + } + }; + + template + JValue ParseComment(std::string_view comment) + { + return CommentParser {comment, this}.Run(); + } + + void AppendProgram(util::StringView package_name, parser::Program *prog); + + void InsertStatement(ir::Statement *stmt); + + struct NullInserter { + JValue operator()() const + { + return JNULL; + } + }; + + struct ObjectInserter { + public: + JValue operator()() const + { + return JValue {JObject()}; + } + }; + + struct ArrayInserter { + public: + JValue operator()() const + { + return JValue {JArray {}}; + } + }; + + // This function is used for a tree zipper + template + void WithInserted(util::StringView name, F &&action) + { + ASSERT(std::holds_alternative(*insert_point_)); + auto *cur_obj = &std::get(*insert_point_); + auto [iter, inserted] = cur_obj->emplace(name, JNULL); + if constexpr (!ALLOW_DUPLICATE) { + ASSERT(inserted); + } + if (inserted) { + iter->second = Inserter {}(); + } + auto old_insert = insert_point_; + insert_point_ = &iter->second; + action(); + insert_point_ = old_insert; + } + + JValue DumpFunctionalType(checker::ETSObjectType *type) + { + auto fn = type->GetFunctionalInterfaceInvokeType(); + ASSERT(fn->CallSignatures().size() == 1); + auto *sig = fn->CallSignatures().front(); + + JObject ret; + ret.emplace("kind", "fn"); + ret.emplace("ret", DumpType(sig->ReturnType())); + auto &args = ConstructDefaultGet(ret, "args"); + for (auto &par : sig->Params()) { + JObject arg; + arg.emplace("name", par->Name()); + arg.emplace("type", DumpType(par->TsType())); + args.emplace_back(std::move(arg)); + } + return JValue {ret}; + } + + JValue DumpVarType(checker::ETSObjectType *type) + { + JObject ret; + ret.emplace("kind", "var"); + ret.emplace("name", type->Name()); + return JValue {ret}; + } + + JValue DumpClassType(checker::ETSObjectType *type) + { + if (type->IsETSVoidType() || type == this->ctx_->Checker()->AsETSChecker()->GlobalBuiltinVoidType()) { + return JValue {std::string_view {"void"}}; + } + JObject ret = {{"kind", JValue {"named"}}}; + auto decl = type->GetDeclNode(); + while (decl != nullptr && !decl->IsBlockStatement()) { + decl = decl->Parent(); + } + if (decl != nullptr) { + auto program = static_cast(decl)->Program(); + ret.emplace("pack", program->GetPackageName()); + } + ret.emplace("name", type->Name()); + + if (!type->TypeArguments().empty()) { + JArray targs; + std::transform(type->TypeArguments().begin(), type->TypeArguments().end(), std::back_inserter(targs), + [this](checker::Type *typ) -> JValue { return DumpType(typ); }); + ret.emplace("targs", std::move(targs)); + } + return JValue {ret}; + } + + void EmplaceToObject(std::string_view name, JValue v) + { + std::get(*insert_point_).emplace(name, std::move(v)); + } + + template + void EmplaceToArray(T &&v) + { + std::get(*insert_point_).emplace_back(std::forward(v)); + } + + void FillInVisibility(JObject &to, ir::AstNode *def, bool is_global) + { + to.emplace("exported", def->IsExported()); + if (is_global) { + return; + } + auto selected = def->IsPrivate() ? "private" + : def->IsProtected() ? "protected" + : def->IsInternal() ? "internal" + : def->IsPublic() ? "public" + : nullptr; + if (selected != nullptr) { + to.emplace("visibility", selected); + } + } + + void AddClassProperty(ir::ClassProperty *def, bool is_global) + { + JObject prop; + prop.emplace("name", def->Id()->Name()); + prop.emplace("static", def->IsStatic()); + prop.emplace("readonly", def->IsReadonly()); + prop.emplace("const", def->IsConst()); + FillInVisibility(prop, def, is_global); + prop.emplace("type", DumpType(def->TsType())); + AddDocComment(prop, def->Documentation()); + + EmplaceToArray(std::move(prop)); + } + + template + void AddDocComment(JObject &obj, ir::HasDoc &doc) + { + obj.emplace("comment", doc.GetDocComment().has_value() ? ParseComment(doc.GetDocComment().value()) : JNULL); + std::ignore = obj; + std::ignore = doc; + } + + void AddMethod(ir::MethodDefinition *def, bool is_global) + { + if (def->Function()->IsHidden()) { + return; + } + JObject meth = { + {"name", JValue {def->Id()->Name()}}, + {"final", JValue {def->IsFinal()}}, + {"static", JValue {def->IsStatic()}}, + {"override", JValue {def->IsOverride()}}, + {"throws", JValue {def->Function()->IsThrowing()}}, + }; + if (def->IsSetter()) { + meth.emplace("property", "set"); + } + if (def->Function() != nullptr && def->Function()->IsSetter()) { + meth.emplace("property", "set"); + } + if (def->Function() != nullptr && def->Function()->IsGetter()) { + meth.emplace("property", "get"); + } + errors_.WithTrace(JValue {def->Id()->Name()}, [this, &def, &meth, is_global]() { + AddGenericInfo(meth, def->Function()->TypeParams()); + FillInVisibility(meth, def, is_global); + AddDocComment(meth, def->Documentation()); + meth.emplace("ret", DumpType(def->Function()->Signature()->ReturnType())); + auto &jparams = ConstructDefaultGet(meth, "args"); + for (const auto ¶m : def->Function()->Params()) { + auto &jparam = ConstructDefaultBackGet(jparams); + ASSERT(param->IsETSParameterExpression()); + auto param_expr = param->AsETSParameterExpression(); + if (param_expr->IsRestParameter()) { + jparam.emplace("rest", true); + } + auto id = param_expr->Ident(); + jparam.emplace("name", id->Name()); + jparam.emplace("type", DumpType(id->Variable()->TsType())); + } + }); + + EmplaceToArray(std::move(meth)); + for (auto &over : def->Overloads()) { + AddMethod(over, is_global); + } + } + + void PopulateGlobal(ir::ClassDeclaration *global) + { + for (auto &el : global->Definition()->Body()) { + if (el->IsAutoGenerated()) { + continue; + } + switch (el->Type()) { + case ir::AstNodeType::METHOD_DEFINITION: { + WithInserted("methods", + [this, &el]() { AddMethod(el->AsMethodDefinition(), true); }); + break; + } + case ir::AstNodeType::CLASS_STATIC_BLOCK: { + break; + } + case ir::AstNodeType::CLASS_PROPERTY: { + WithInserted("props", + [this, &el]() { AddClassProperty(el->AsClassProperty(), true); }); + break; + } + default: + std::cerr << "unknown element type " << (int)el->Type() << std::endl; + UNREACHABLE(); + } + } + } + + void AddGenericInfo(JObject &type, const ir::TSTypeParameterInstantiation *targs) + { + if (targs == nullptr || targs->Params().empty()) { + return; + } + JArray res; + std::transform( + targs->Params().begin(), targs->Params().end(), std::back_inserter(res), + [this](ir::TypeNode *typ) { return DumpType(typ->GetType(static_cast(nullptr))); }); + type.emplace("targs", std::move(res)); + } + + void AddGenericInfo(JObject &type, const ir::TSTypeParameterDeclaration *targs) + { + if (targs == nullptr || targs->Params().empty()) { + return; + } + JArray res; + std::transform(targs->Params().begin(), targs->Params().end(), std::back_inserter(res), + [](ir::TSTypeParameter *typ) { + // NOTE(kprokopenko): dump typ->Constraint() + return JValue {typ->Name()->Name()}; + }); + [[maybe_unused]] auto inserted = type.emplace("targs", std::move(res)); + ASSERT(inserted.second); + } + + void PopulateInterface(ir::TSInterfaceDeclaration *cls) + { + FillInVisibility(std::get(*insert_point_), cls, true); + AddDocComment(std::get(*insert_point_), cls->Documentation()); + WithInserted("extends", [cls, this]() { + for (auto &impl : cls->Extends()) { + EmplaceToArray(DumpType(impl->Expr()->AsETSTypeReference())); + } + }); + AddGenericInfo(std::get(*insert_point_), cls->TypeParams()); + AddDocComment(std::get(*insert_point_), cls->Documentation()); + [[maybe_unused]] auto scope_changer = ScopedChanger(scope_, cls->Scope()); + for (auto &el : cls->Body()->Body()) { + switch (el->Type()) { + case ir::AstNodeType::METHOD_DEFINITION: { + WithInserted("methods", + [this, el]() { AddMethod(el->AsMethodDefinition(), false); }); + break; + } + case ir::AstNodeType::CLASS_STATIC_BLOCK: { + break; + } + case ir::AstNodeType::CLASS_PROPERTY: { + WithInserted("variables", + [this, el]() { AddClassProperty(el->AsClassProperty(), false); }); + break; + } + default: + std::cerr << "unknown element type " << (int)el->Type() << std::endl; + UNREACHABLE(); + } + } + } + + void AddTypeAlias(ir::TSTypeAliasDeclaration *alias) + { + JObject data; + AddGenericInfo(data, alias->TypeParams()); + data.emplace("type", DumpType(alias->TypeAnnotation()->GetType(ctx_->Checker()->AsETSChecker()))); + EmplaceToObject(alias->Id()->Name().Utf8(), JValue {std::move(data)}); + } + + JValue DumpLiteral(const ir::Expression *expr) + { + using AstNodeType = ir::AstNodeType; + switch (expr->Type()) { + case AstNodeType::NUMBER_LITERAL: { + auto num = expr->AsNumberLiteral()->Number(); + if (num.IsLong()) { + // num.Str() may be empty + return JValue { + JObject {{"kind", JValue {"num"}}, {"value", JValue {std::to_string(num.GetLong())}}}}; + } + return JValue {num.GetDouble()}; + } + case AstNodeType::STRING_LITERAL: + return JValue {expr->AsStringLiteral()->Str()}; + default: + UNREACHABLE(); + }; + } + + void AddEnum(ir::TSEnumDeclaration *enu) + { + JObject e; + AddDocComment(e, enu->Documentation()); + FillInVisibility(e, enu, true); + auto &members = ConstructDefaultGet(e, "members"); + for (auto &mem : enu->Members()) { + auto e_mem = mem->AsTSEnumMember(); + JValue value = DumpLiteral(e_mem->Init()); + auto obj = JObject { + {"name", JValue {e_mem->Key()->AsIdentifier()->Name()}}, + {"value", std::move(value)}, + }; + AddDocComment(obj, e_mem->Documentation()); + members.emplace_back(std::move(obj)); + } + EmplaceToObject(enu->Key()->Name().Utf8(), JValue {std::move(e)}); + } + + void PopulateClass(ir::ClassDeclaration *cls) + { + errors_.WithTrace(JValue {cls->Definition()->Ident()->Name()}, [this, cls]() { + auto &klass = std::get(*insert_point_); + FillInVisibility(std::get(*insert_point_), cls, true); + klass.emplace("final", cls->IsFinal()); + WithInserted("implements", [this, cls]() { + for (auto &impl : cls->Definition()->Implements()) { + EmplaceToArray(DumpType(impl->Expr()->AsETSTypeReference())); + } + }); + if (cls->Definition()->Super() != nullptr) { + EmplaceToObject("extends", DumpType(cls->Definition()->Super()->AsETSTypeReference())); + } + + AddGenericInfo(std::get(*insert_point_), cls->Definition()->TypeParams()); + AddDocComment(std::get(*insert_point_), cls->Documentation()); + [[maybe_unused]] auto scope_changer = ScopedChanger(scope_, cls->Definition()->Scope()); + for (auto &el : cls->Definition()->Body()) { + if (el->IsAutoGenerated()) { + continue; + } + switch (el->Type()) { + case ir::AstNodeType::METHOD_DEFINITION: { + WithInserted("methods", + [this, el]() { AddMethod(el->AsMethodDefinition(), false); }); + break; + } + case ir::AstNodeType::CLASS_STATIC_BLOCK: { + break; + } + case ir::AstNodeType::CLASS_PROPERTY: { + WithInserted( + "variables", [this, el]() { AddClassProperty(el->AsClassProperty(), false); }); + break; + } + default: + std::cerr << "unknown element type " << (int)el->Type() << std::endl; + UNREACHABLE(); + } + } + }); + } +}; + +JValue DocParser::FetchType() +{ + // NOTE(kprokopenko) need to start parser&lexer to be able to parse all types + // or integrate comments parsing into lexer itself + auto type = FetchId(); + auto errored_return = [this, &type]() { + parent_->Errors().Error(JValue {"can't resolve type " + JString {type}}); + return JValue {JString {type}}; + }; + auto scope = parent_->Scope(); + if (scope == nullptr) { + return errored_return(); + } + auto ctx = parent_->Context(); + auto checker = static_cast(ctx->Checker()); + auto allocator = checker->Allocator(); + auto id = allocator->New(type, allocator); + id->SetReference(); + auto res = scope->Find(type, varbinder::ResolveBindingOptions::TYPE_ALIASES | + varbinder::ResolveBindingOptions::DECLARATION); + if (res.variable == nullptr) { + return errored_return(); + } + id->SetVariable(res.variable); + auto part = allocator->New(id, nullptr, nullptr); + auto ref = allocator->New(part); + return parent_->DumpType(ref->Check(checker)); +} + +void DocParser::Error(JValue val) +{ + parent_->Errors().Error(std::move(val)); +} + +} // namespace + +std::string ETSDocgenImpl::Str() +{ + return JValue {std::move(result_)}.ToString(); +} + +void ETSDocgenImpl::InsertStatement(ir::Statement *stmt) +{ + using AstNodeType = ir::AstNodeType; + switch (stmt->Type()) { + case AstNodeType::ETS_IMPORT_DECLARATION: + case AstNodeType::ETS_PACKAGE_DECLARATION: { + break; + } + case AstNodeType::TS_INTERFACE_DECLARATION: { + auto inter = stmt->AsTSInterfaceDeclaration(); + WithInserted("interfaces", [this, inter]() { + WithInserted(inter->Id()->Name(), [this, inter]() { PopulateInterface(inter); }); + }); + break; + } + case AstNodeType::TS_TYPE_ALIAS_DECLARATION: { + WithInserted("aliases", [this, stmt]() { AddTypeAlias(stmt->AsTSTypeAliasDeclaration()); }); + break; + } + case AstNodeType::CLASS_DECLARATION: { + auto cls = stmt->AsClassDeclaration(); + if (cls->Definition()->Ident()->Name() == compiler::Signatures::ETS_GLOBAL) { + PopulateGlobal(cls); + } else { + WithInserted("classes", [this, cls]() { + WithInserted(cls->Definition()->Ident()->Name(), [this, cls]() { PopulateClass(cls); }); + }); + } + break; + } + case AstNodeType::TS_ENUM_DECLARATION: { + auto enu = stmt->AsTSEnumDeclaration(); + WithInserted("enums", [this, enu]() { AddEnum(enu); }); + break; + } + default: { + UNREACHABLE(); + } + } +} + +void ETSDocgenImpl::AppendProgram(util::StringView package_name, parser::Program *prog) +{ + [[maybe_unused]] std::string package_name_dots {package_name}; + std::replace(package_name_dots.begin(), package_name_dots.end(), '/', '.'); + ASSERT(prog->GetPackageName() == util::StringView(package_name_dots)); + auto trace_value = JValue {package_name}; + if (!prog->VarBinder()->GetCompilerContext()->GetMakeDoc()->secure) { + trace_value = JValue {JArray {JValue {prog->SourceFilePath()}, JValue {prog->ResolvedFilePath()}, trace_value}}; + } + errors_.WithTrace(std::move(trace_value), [this, prog]() { + [[maybe_unused]] auto scope_changer = ScopedChanger(scope_, prog->Ast()->Scope()); + for (const auto &stmt : prog->Ast()->Statements()) { + if (stmt->IsAutoGenerated()) { + continue; + } + InsertStatement(stmt); + } + }); +} + +ETSDocgenImpl &ETSDocgenImpl::Run(parser::Program *prog) +{ + ctx_ = prog->VarBinder()->GetCompilerContext(); + auto doc = prog->VarBinder()->GetCompilerContext()->GetMakeDoc(); + std::regex allowed {doc ? doc.value().filter : ".*"}; + WithInserted("packages", [&, this, prog]() { + if (std::regex_match(std::string {prog->GetPackageName()}, allowed)) { + JObject *ins {}; + WithInserted(prog->GetPackageName(), [this, prog, &ins]() { + AppendProgram(prog->GetPackageName(), prog); + ins = &std::get(*insert_point_); + }); + bool non_empty = false; + for (auto [k, v] : *ins) { + std::ignore = k; + if (std::holds_alternative(v) && !std::get(v).empty()) { + non_empty = true; + break; + } + if (std::holds_alternative(v) && !std::get(v).empty()) { + non_empty = true; + break; + } + } + if (!non_empty) { + std::get(*insert_point_).erase(JString {prog->GetPackageName()}); + } + } + for (const auto &name_progs : prog->ExternalSources()) { + if (!std::regex_match(std::string {name_progs.first.Utf8()}, allowed)) { + continue; + } + WithInserted(name_progs.first, [this, &name_progs]() { + for (auto &sub_prog : name_progs.second) { + AppendProgram(name_progs.first, sub_prog); + } + }); + } + }); + return *this; +} + +std::string ETSDocgen::Generate(parser::Program *prog) +{ + return ETSDocgenImpl {}.Run(prog).Str(); +} + +} // namespace panda::es2panda::docgen diff --git a/ets2panda/docgen/ETSdocgen.h b/ets2panda/docgen/ETSdocgen.h new file mode 100644 index 0000000000..3e9bb6f0ff --- /dev/null +++ b/ets2panda/docgen/ETSdocgen.h @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2021 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_DOCKGEN_ETS_DOCKGEN_H +#define ES2PANDA_DOCKGEN_ETS_DOCKGEN_H + +#include "docgen.h" + +namespace panda::es2panda::docgen { +class ETSDocgen : public Docgen { +public: + ETSDocgen() = default; + ~ETSDocgen() = default; + NO_COPY_SEMANTIC(ETSDocgen); + NO_MOVE_SEMANTIC(ETSDocgen); + + std::string Generate(parser::Program *prog) override; +}; +} // namespace panda::es2panda::docgen + +#endif diff --git a/ets2panda/docgen/NOdocgen.cpp b/ets2panda/docgen/NOdocgen.cpp new file mode 100644 index 0000000000..e8f8a10a78 --- /dev/null +++ b/ets2panda/docgen/NOdocgen.cpp @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2021 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 "NOdocgen.h" +#include "varbinder/varbinder.h" +#include "parser/program/program.h" + +namespace panda::es2panda::docgen { + +std::string NODocgen::Generate(parser::Program *prog) +{ + std::string msg = std::string("generating source code docs is not supported for extension "); + msg += ToLanguage(prog->VarBinder()->Extension()).ToString(); + throw Error {ErrorType::GENERIC, prog->VarBinder()->Program()->SourceFilePath().Utf8(), msg}; +} + +} // namespace panda::es2panda::docgen diff --git a/ets2panda/docgen/NOdocgen.h b/ets2panda/docgen/NOdocgen.h new file mode 100644 index 0000000000..2349b9ef00 --- /dev/null +++ b/ets2panda/docgen/NOdocgen.h @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2021 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_DOCKGEN_NO_DOCKGEN_H +#define ES2PANDA_DOCKGEN_NO_DOCKGEN_H + +#include "docgen.h" + +namespace panda::es2panda::docgen { +class NODocgen : public Docgen { +public: + NODocgen() = default; + NO_COPY_SEMANTIC(NODocgen); + NO_MOVE_SEMANTIC(NODocgen); + ~NODocgen() = default; + + std::string Generate(parser::Program *prog) override; +}; +} // namespace panda::es2panda::docgen + +#endif diff --git a/ets2panda/docgen/docgen.h b/ets2panda/docgen/docgen.h new file mode 100644 index 0000000000..20c20e4657 --- /dev/null +++ b/ets2panda/docgen/docgen.h @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2021 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_DOCKGEN_DOCKGEN_H +#define ES2PANDA_DOCKGEN_DOCKGEN_H + +#include "parser/program/program.h" + +namespace panda::es2panda::docgen { +class Docgen { +public: + Docgen() = default; + ~Docgen() = default; + NO_COPY_SEMANTIC(Docgen); + NO_MOVE_SEMANTIC(Docgen); + + [[nodiscard]] virtual std::string Generate(parser::Program *prog) = 0; +}; +} // namespace panda::es2panda::docgen + +#endif diff --git a/ets2panda/docgen/json.cpp b/ets2panda/docgen/json.cpp new file mode 100644 index 0000000000..628009da22 --- /dev/null +++ b/ets2panda/docgen/json.cpp @@ -0,0 +1,103 @@ +/** + * Copyright (c) 2021 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 "json.h" + +#include +#include + +namespace panda::es2panda::docgen { + +namespace { +auto DumpEscape(std::stringstream &out, std::string_view str) +{ + out << '"'; + for (auto c : str) { + uint8_t uc = c; + constexpr uint8_t NON_PRINTABLE = 0x1f; + if (uc == '"' || uc == '\\' || uc <= NON_PRINTABLE) { + constexpr int JSON_UNICODE_SIZE = 4; + out << "\\u" << std::setw(JSON_UNICODE_SIZE) << std::setfill('0') << std::hex << static_cast(uc); + } else { + out << c; + } + } + out << '"'; +} +} // namespace + +void JValue::DumpArray(std::stringstream &out, const JArray &arr) +{ + out << '['; + bool first = true; + for (const auto &e : arr) { + if (!first) { + out << ','; + } + first = false; + e.ToString(out); + } + out << ']'; +} + +void JValue::DumpObject(std::stringstream &out, const JObject &obj) +{ + out << '{'; + bool first = true; + for (const auto &[k, v] : obj) { + if (!first) { + out << ','; + } + first = false; + DumpEscape(out, k); + out << ":"; + v.ToString(out); + } + out << '}'; +} + +void JValue::ToString(std::stringstream &out) const +{ + std::visit( + [&out](auto &&el) -> void { + using T = std::decay_t; + if constexpr (std::is_same_v) { + std::ignore = el; + out << "null"; + } else if constexpr (std::is_same_v) { + out << (el ? "true" : "false"); + } else if constexpr (std::is_same_v) { + out << el; + } else if constexpr (std::is_same_v) { + DumpEscape(out, el); + } else if constexpr (std::is_same_v) { + DumpArray(out, el); + } else if constexpr (std::is_same_v) { + DumpObject(out, el); + } else { + UNREACHABLE(); + } + }, + *static_cast(this)); +} + +std::string JValue::ToString() const +{ + std::stringstream out; + ToString(out); + return out.str(); +} + +} // namespace panda::es2panda::docgen diff --git a/ets2panda/docgen/json.h b/ets2panda/docgen/json.h new file mode 100644 index 0000000000..8cf768c381 --- /dev/null +++ b/ets2panda/docgen/json.h @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2021 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_DOCKGEN_JSON_H +#define ES2PANDA_DOCKGEN_JSON_H + +#include +#include +#include +#include + +#include "util/ustring.h" + +namespace panda::es2panda::docgen { + +struct JValue; + +using JObject = std::map; +using JArray = std::vector; +using JString = std::string; + +namespace detail { +using JValueVar = std::variant; +} // namespace detail + +struct JValue : public detail::JValueVar { + explicit JValue() = default; + + explicit JValue(std::monostate m) noexcept : detail::JValueVar(m) {} + + explicit JValue(double d) : detail::JValueVar(d) {} + + explicit JValue(bool b) : detail::JValueVar(b) {} + + explicit JValue(util::StringView s) : JValue(s.Utf8()) {} + + explicit JValue(const char *s) : JValue(std::string(s)) {} + + explicit JValue(std::string_view s) : JValue(std::string(s)) {} + + explicit JValue(std::string s) : detail::JValueVar(std::move(s)) {} + + explicit JValue(JArray j) : detail::JValueVar(std::move(j)) {} + + explicit JValue(JObject j) : detail::JValueVar(std::move(j)) {} + + std::string ToString() const; + +private: + void ToString(std::stringstream &out) const; + + static void DumpArray(std::stringstream &out, const JArray &arr); + static void DumpObject(std::stringstream &out, const JObject &obj); +}; + +inline const JValue JNULL = JValue {std::monostate {}}; // NOLINT(fuchsia-statically-constructed-objects) + +} // namespace panda::es2panda::docgen + +#endif diff --git a/ets2panda/docgen/render.rb b/ets2panda/docgen/render.rb new file mode 100755 index 0000000000..fa657dd977 --- /dev/null +++ b/ets2panda/docgen/render.rb @@ -0,0 +1,737 @@ +#!/bin/env ruby + +# Copyright (c) 2021-2023 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. + +require 'json' +require 'stringio' +require 'uri' +require 'optparse' + +$options = options = {} +$errorCode = 0 +OptionParser.new do |opts| + opts.on '-o=FILE', '--output=FILE', 'Output file (or directory for --separate)' + opts.on '-s', '--separate', 'Dump each file to a single package' + opts.on '--prologue', 'Emit common prologue in every file' + opts.on '--min-visibility=VISIBILITY', 'minimum visibility to emit: private`_ " + when :url + assertSize.(2) + buf << "`#{a[1]} <#{a[1]}>`_" + when :ref + assertSize.(3) + buf << ":ref:`" + renderLineTextArray(a[1], buf: buf) + buf << "<#{a[2]}>`" + when :space + assertSize.(1) + buf << "|nbsp| " + when :kw + assertSize.(2) + buf << ":kw:`" << escape(a[1]) << "`" + else + mRaise "wrong line #{a} in #{arr}" + end + else + mRaise "unknown #{a} in #{arr}" + end + } + mRaise("wrong array #{arr}") if /\n/ =~ buf + buf + end + + def str() + buf = "" + prev = OpenStruct.new + @lines.each_with_index { |l, i| + prevDifferent = prev.kind != l[0] + if prevDifferent + buf << "\n" + end + case l[0] + when :title + sym = case l[1] + when 0..1 + "*" + when 2 + "=" + when 3 + "-" + when 4 + "~" + else 5 + "^" + end + dat = renderLineTextArray(l[2..-1]) + if dat == "" + dat = renderLineTextArray([keywordaize("anonymous")]) + end + if l[1] == 0 + buf << (sym * dat.size) << "\n" + end + buf << dat << "\n" + buf << (sym * dat.size) << "\n\n" + when :internal + buf << l[1] + when :line + buf << "| " << renderLineTextArray(l[1..-1]) + when :sig + if prevDifferent + buf << ".. container:: doccodeblock\n\n" + end + buf << " " << renderLineTextArray(l[1..-1]) + when :ol + buf << " - " << renderLineTextArray(l[1..-1]) + when :ul + buf << " - " << renderLineTextArray(l[1..-1]) + when :anchor + mRaise("wrong #{l}") if l.size != 2 + buf << ".. _#{l[1]}:" + when :code + mRaise("wrong code #{l}") if l.size != 2 + if prevDifferent + buf << ".. code-block::\n\n" + end + buf << " " << l[1] + when :hline + mRaise("wrong #{l}") if l.size != 1 + if i + 1 != @lines.size + buf << "------\n" + end + else + mRaise "wrong kind #{l[0]}" + end + buf << "\n" + prev.kind = l[0] + } + buf + end + + def title(name, allowEmpty: true) + makeLine(:title) << @titleDepth + if name.kind_of? Array + curLine.concat(name) + else + curLine << name + end + old = curLine + begin + @titleDepth += 1 + trace("#{name}") { + yield + } + ensure + @titleDepth -= 1 + end + if not allowEmpty and curLine == old + @lines.pop + end + end + + private def escape(s) + s.gsub(/[\*`":\\<>\[\]_]/) { |c| "\\#{c}" } + end + + def keywordaize(x) + [:kw, x] + end +end + +class Dumper < PrimitiveDumper + def skip(o) + tst = Proc.new { |x| + case x + when 'public' + 5 + when 'protected' + 4 + when nil, 'internal' + 3 + when 'private' + 2 + else + mRaise "unknown visibility #{x} #{o}" + end + } + myVis = [o["exported"] ? "public" : "internal", o["visibility"]].map { |x| tst.(x) }.max + return true if myVis < tst.($options[:"min-visibility"]) + tagsFilter = $options[:"tags-filter"] + if tagsFilter + tags = (o["comment"] || {"tags": nil})["tags"] || [] + return !eval(tagsFilter, binding) + end + return false + end + def dumpVisibility(o) + if o["exported"] + curLine << keywordaize("export") << " " + end + if o["visibility"] + curLine << keywordaize(o["visibility"]) << " " + end + end + + def dumpCommentBlock(blk, title: nil, inline: false) + if blk.size == 0 + return + end + newLine = method(:makeLine) + if inline + bad = [false] + newLine = Proc.new { |*args| + if args[0] == :line and not bad[0] + curLine << " " + next curLine + end + bad[0] = true + next makeLine(*args) + } + end + newLine.(:line) + if title + curLine.concat(title) + end + blk.each { |b| + if b.kind_of? String + curLines = b.split("\n").map{ |i| i.gsub(/\s+/, ' ') } + if not curLines.empty? + curLine << curLines[0] + curLines[1..-1].each { |l| + newLine.(:line) << l + } + end + elsif b["kind"] == "link" + if not b["text"] + if b["link"] =~ URI::DEFAULT_PARSER.regexp[:ABS_URI] + curLine << [:url, b["link"]] + else + curLine << [:mono, b["link"]] + end + else + curLine << [:link, [b["text"]], b["link"]] + end + elsif b["kind"] == "block-code" + b["data"].split(/\n/).each { |l| + newLine.(:code) << l + } + else + error("unknown #{b}") + curLine << b.to_s + end + } + if not inline + makeLine :line + end + end + + def dumpComment(comment, mode, dat: nil) + if comment == nil + if $options[:"error-on-comment-absence"] + error("comment is absent") + end + return + end + if comment["brief"] + dumpCommentBlock comment["brief"] + end + if comment["deprecated"] + dumpCommentBlock comment["deprecated"], title: [[:bold, 'DEPRECATED:'], " "] + end + if comment["description"] + dumpCommentBlock comment["description"], title: [[:bold, 'Description:'], " "] + end + if comment["targs"] + makeLine(:line) << [:bold, "Type Arguments:"] << " " + comment["targs"].each { |a| + makeLine(:ul) << a["name"] << " --- " + dumpCommentBlock a["data"], inline: true + } + end + if mode == :method + if comment["returns"] + dumpCommentBlock comment["returns"], title: [[:bold, 'Returns:'], ' '] + end + if comment["params"] and comment["params"].any? { |a| a["data"] } + args = Hash[dat["obj"]["args"].map { |a| [a["name"], {"type" => a["type"]}] }] + comment["params"].each { |a| + if not args.has_key?(a["name"]) + error "doesn't have param #{a["name"]}" + next + end + args[a["name"]]["comment"] = a["data"] + } + makeLine(:line) << [:bold, 'Arguments:'] + dat["obj"]["args"].each { |a| + makeLine(:ol) << a["name"] << ": " << [:nest, reprType(a["type"])] + if args[a["name"]]["comment"] + curLine << " --- " + dumpCommentBlock args[a["name"]]["comment"], inline: true + end + } + end + if dat and dat["obj"]["throws"] or comment["throws"] and comment["throws"].size > 0 + makeLine(:line) << [:bold, "Throws:"] + end + if comment["throws"] + comment["throws"].each { |t| + makeLine(:ul) << [:nest, reprType(t["type"])] << " " + dumpCommentBlock t["data"], inline: true + } + end + end + if comment["other"] + comment["other"].each { |o| + mRaise("wrong other #{o["kind"]}") unless o["kind"] == "other" + tit = o["title"] + tit[0] = tit[0].upcase + dumpCommentBlock o["data"], title: [[:bold, tit + ":"], " "] + } + end + end + + def dumpMethod(m, ignoreStatic: false) + return if skip m + trace(m["name"], log: m) { + makeLine :sig + dumpVisibility(m) + if m["final"] + curLine << keywordaize("final") << " " + end + if m["override"] + curLine << keywordaize("override") << " " + end + if m["static"] && !ignoreStatic + curLine << keywordaize("static") << " " + end + if m["property"] + curLine << keywordaize(m["property"]) << " " + end + + makeLine(:sig) << m["name"] << [:nest, reprTArgs(m, isDecl: true)] << "(" + needsNewLine = false + if m["args"].size > 2 or m["args"].any? { |a| a["type"]["kind"] == "fn" } + needsNewLine = true + makeLine :sig + end + m["args"].each_with_index{ |a, i| + if needsNewLine + curLine << [:space] + end + if a["rest"] + curLine << "..." + end + curLine << a["name"] << ": " << [:nest, reprType(a["type"])] + if i + 1 != m["args"].size + if needsNewLine + curLine << "," + makeLine :sig + else + curLine << ", " + end + end + } + if needsNewLine + makeLine :sig + end + curLine << "): " << [:nest, reprType(m["ret"])] + if m["throws"] + curLine << " " << keywordaize("throws") + end + + dumpComment m["comment"], :method, dat: { "obj" => m } + + makeLine :hline + } + end + + def dumpProp(p, ignoreStatic: false) + return if skip p + trace(p["name"]) { + makeLine :sig + dumpVisibility p + if p["static"] && !ignoreStatic + curLine << keywordaize("static") << " " + end + if p["readonly"] + curLine << keywordaize("readonly") << " " + end + if p["const"] + curLine << keywordaize("const") << " " + end + makeLine(:sig) << p["name"] << ": " << [:nest, reprType(p["type"])] + + dumpComment p["comment"], :prop + } + + makeLine(:hline) + end + + def normalize(d, n, ks: ["static", "name", Proc.new { |x| x["args"] ? x["args"].size : 0 }]) + d[n] ||= [] + d[n].sort_by!{ |a| + ks.map { |k| + if k.respond_to?(:call) + next k.(a) + end + vl = a[k] + if vl === true + 1 + elsif vl === false + 0 + else + vl + end + } + } + end + + def reprTArgs(t, isDecl: false) + dumper = method(:reprType) + if isDecl + dumper = Proc.new { |x| [x]} + end + res = [] + return res unless t["targs"] + res << "<" << [:nest, t["targs"].map { |a| dumper.(a) }.intercalate([", "])] << ">" + res + end + + def reprType(t) + if t.kind_of? String + return [keywordaize(t)] + end + case t["kind"] + when "var" + [t["name"]] + when "union" + t["alts"].map { |x| reprType(x) }.intercalate([' | ']) + when "arr" + reprType(t["elem"]) + ["[]"] + when "named" + p = t["pack"] || "" + if p == @curPack || p == "" + p = "" + else + p += "." + end + p += t["name"] + [[:ref, [p], namedTypeLabelStr("#{t["pack"]}.#{t["name"]}")]] + reprTArgs(t) + when "fn" + reprTArgs(t) + ["("] + t["args"].map { |a| [a["name"], ": "] + reprType(a["type"]) }.intercalate([", "]) + [") => "] + reprType(t["ret"]) + else + mRaise "unknown kind #{t["kind"]} in #{t}" + end + rescue + STDERR.puts(JSON.dump({ trace: @trace, type: t, log: @logs })) + raise + end + + def namedTypeLabelStr(name) + name = "#{name.gsub('/', '.')}" + name = name + '.' + name.chars.map { |c| c == c.upcase ? 'u' : 'l' }.join + name + end + + def dumpClassAnchor(pname, cname) + makeLine(:anchor) << "#{namedTypeLabelStr "#{pname}.#{cname}"}"; + end + + def prelude + return if not $options[:prologue] + makeLine(:internal) << ".. role:: kw" + makeLine(:internal) << " :class: dockeyword" + makeLine(:internal) << "\n" + makeLine(:internal) << ".. |nbsp| unicode:: U+00A0 .. NO-BREAK SPACE\n\n" + end + + def fillInClass(cname, cls) + dumpComment cls["comment"], :class + + normalize cls, "props" + normalize cls, "methods", ks: ["static", "name"] + title("Properties", allowEmpty: false) { + cls["props"].each { |p| + dumpProp p + } + } + title("Methods", allowEmpty: false) { + (cls["methods"] || []).each { |m| + dumpMethod m + } + } + end + + def trace(s, log: nil) + @trace.push s + if log != nil + @logs.push log + end + yield + ensure + if log != nil + @logs.pop + end + @trace.pop + end + + def error(e) + @errs.push({ "trace" => @trace.dup, "text" => e }) + end + + def classTitleRepr(name, targs) + res = [name] + if targs and targs.size > 0 + res << "<" << [:nest, targs.intersperse(", ")] << ">" + end + res + end + + def fillInPackage(pname, pack) + normalize pack, "props" + normalize pack, "methods" + title("Variables", allowEmpty: false) { + pack["props"].each { |p| + dumpProp p, ignoreStatic: true + } + } + title("Enums", allowEmpty: false) { + (pack["enums"] || []).each { |cname, cls| + next if skip cls + dumpClassAnchor(pname, cname) + title(classTitleRepr(cname, cls["targs"])) { + makeLine(:sig) + dumpVisibility(cls) + dumpComment cls["comment"], :enum + cls["members"].each { |mem| + val = mem["value"] + if val.kind_of?(Hash) + case val["kind"] + when "num" + val = val["value"] + else + mRaise("unknown kind #{val["kind"]}") + end + elsif val.kind_of? String + val = "\"#{val}\"" + else + val = "#{val}" + end + makeLine(:sig) << mem["name"] << " = " << val + dumpComment mem["comment"], :enumItem + makeLine(:hline) + } + } + } + } + title("Functions", allowEmpty: false) { + pack["methods"].each { |m| + dumpMethod m, ignoreStatic: true + } + } + title("Aliases", allowEmpty: false) { + (pack["aliases"] || []).each { |cname, cls| + next if skip cls + dumpClassAnchor(pname, cname) + title(classTitleRepr(cname, cls["targs"])) { + makeLine(:sig) << "= " << [:nest, reprType(cls["type"])] + } + } + } + title("Interfaces", allowEmpty: false) { + (pack["interfaces"] || []).each { |cname, cls| + next if skip cls + dumpClassAnchor(pname, cname) + title(classTitleRepr(cname, cls["targs"])) { + fillInClass cname, cls + } + } + } + title("Classes", allowEmpty: false) { + (pack["classes"] || []).each { |cname, cls| + next if skip cls + dumpClassAnchor(pname, cname) + title(classTitleRepr(cname, cls["targs"])) { + makeLine(:sig) + dumpVisibility(cls) + if cls["final"] + curLine << keywordaize("final") << " " + end + if cls["extends"] + makeLine(:sig) << keywordaize("extends") << " " << [:nest, reprType(cls["extends"])] + end + if cls["implements"] and (cls["implements"].size > 0) + makeLine(:sig) << keywordaize("implements") + cls["implements"].each { |i| + makeLine(:sig) << [:space] << [:nest, reprType(i)] << "," + } + end + fillInClass cname, cls + } + } + } + end + + def run(packages) + prelude + packages.each { |pname, pack| + @curPack = pname.gsub('/', '.') + trace(pname) { + title(pname) { + fillInPackage pname, pack + } + } + if $options[:separate] + save pname + end + } + @errs.concat($knownErrs) + if @errs.size > 0 && $options[:"error-behaviour"] == "fail" + $errorCode = 1 + end + if @errs.size > 0 && $options[:"error-behaviour"] != "omit" + title("Errors", allowEmpty: false) { + @errs.each { |e| + makeLine(:line) << e["text"] + if e["trace"] + curLine << ", trace:" + e["trace"].each { |t| + makeLine(:ul) << t + } + end + } + } + + save "_errors" + end + if not $options[:separate] + save "
" + end + end + + def save(pack) + if pack == "" + pack = "anonymous" + end + dump = str + @lines = [] + prelude + if $options[:output] == nil + puts dump + else + path = "#{$options[:output]}" + if $options[:separate] + path += "/#{pack.gsub(/\//, '.')}.rst" + end + puts "saved `#{pack}` -> #{path}" + File.write path, dump + end + end +end + +dumper = Dumper.new +dumper.run(packages) + +exit $errorCode diff --git a/ets2panda/es2panda.h b/ets2panda/es2panda.h index bd23a328c2..09881a7b55 100644 --- a/ets2panda/es2panda.h +++ b/ets2panda/es2panda.h @@ -85,6 +85,12 @@ struct SourceFile { // NOLINTEND(misc-non-private-member-variables-in-classes) }; +struct MakeDocOptions { + std::string filter {}; + std::string output_path {}; + bool secure = true; +}; + struct CompilerOptions { // NOLINTBEGIN(misc-non-private-member-variables-in-classes) bool is_debug {}; @@ -96,6 +102,7 @@ struct CompilerOptions { bool op_dump_ast_only_silent {}; bool dump_checked_ast {}; bool dump_asm {}; + std::optional make_doc {}; bool dump_debug_info {}; bool parse_only {}; std::string std_lib {}; diff --git a/ets2panda/ir/astNode.h b/ets2panda/ir/astNode.h index f164f1660c..6fd6b25af3 100644 --- a/ets2panda/ir/astNode.h +++ b/ets2panda/ir/astNode.h @@ -393,6 +393,11 @@ public: return (flags_ & ModifierFlags::SETTER) != 0; } + bool IsAutoGenerated() const noexcept + { + return (flags_ & ModifierFlags::AUTO_GENERATED) != 0; + } + void AddModifier(ModifierFlags const flags) noexcept { flags_ |= flags; diff --git a/ets2panda/ir/astNodeFlags.h b/ets2panda/ir/astNodeFlags.h index d22bc5d6e4..6dec040b05 100644 --- a/ets2panda/ir/astNodeFlags.h +++ b/ets2panda/ir/astNodeFlags.h @@ -52,6 +52,7 @@ enum class ModifierFlags : uint32_t { EXPORT = 1U << 22U, SETTER = 1U << 23U, DEFAULT_EXPORT = 1U << 24U, + AUTO_GENERATED = 1U << 25U, ACCESS = PUBLIC | PROTECTED | PRIVATE | INTERNAL, ALL = STATIC | ASYNC | ACCESS | DECLARE | READONLY | ABSTRACT, ALLOWED_IN_CTOR_PARAMETER = ACCESS | READONLY, @@ -114,4 +115,4 @@ enum class BoxingUnboxingFlags : uint32_t { }; } // namespace panda::es2panda::ir -#endif \ No newline at end of file +#endif diff --git a/ets2panda/ir/base/classElement.h b/ets2panda/ir/base/classElement.h index 034f63036d..97ddd2d027 100644 --- a/ets2panda/ir/base/classElement.h +++ b/ets2panda/ir/base/classElement.h @@ -17,6 +17,7 @@ #define ES2PANDA_PARSER_INCLUDE_AST_CLASS_ELEMENT_H #include "ir/statement.h" +#include "ir/hasDoc.h" namespace panda::es2panda::ir { class Expression; @@ -89,12 +90,22 @@ public: [[nodiscard]] virtual PrivateFieldKind ToPrivateFieldKind(bool is_static) const = 0; + HasDoc &Documentation() + { + return doc_comment_; + } + const HasDoc &Documentation() const + { + return doc_comment_; + } + protected: // NOLINTBEGIN(misc-non-private-member-variables-in-classes) Expression *key_; Expression *value_; ArenaVector decorators_; bool is_computed_; + HasDoc doc_comment_; // NOLINTEND(misc-non-private-member-variables-in-classes) }; } // namespace panda::es2panda::ir diff --git a/ets2panda/ir/ets/etsFunctionType.cpp b/ets2panda/ir/ets/etsFunctionType.cpp index 57e2248c56..84d3f9042b 100644 --- a/ets2panda/ir/ets/etsFunctionType.cpp +++ b/ets2panda/ir/ets/etsFunctionType.cpp @@ -139,7 +139,7 @@ checker::Type *ETSFunctionType::Check(checker::ETSChecker *checker) this_var->SetTsType(interface_type); checker->BuildFunctionalInterfaceName(this); - ts_type_ = interface_type; + SetTsType(interface_type); return interface_type; } diff --git a/ets2panda/ir/ets/etsFunctionType.h b/ets2panda/ir/ets/etsFunctionType.h index 62c59ffd9f..ab047fa225 100644 --- a/ets2panda/ir/ets/etsFunctionType.h +++ b/ets2panda/ir/ets/etsFunctionType.h @@ -100,7 +100,6 @@ private: TSTypeParameterDeclaration *type_params_; TypeNode *return_type_; ir::TSInterfaceDeclaration *functional_interface_ {}; - checker::Type *ts_type_ {}; ir::ScriptFunctionFlags func_flags_; }; } // namespace panda::es2panda::ir diff --git a/ets2panda/ir/ets/etsPackageDeclaration.h b/ets2panda/ir/ets/etsPackageDeclaration.h index 50249d8c68..f42d2572e3 100644 --- a/ets2panda/ir/ets/etsPackageDeclaration.h +++ b/ets2panda/ir/ets/etsPackageDeclaration.h @@ -46,6 +46,11 @@ public: checker::Type *Check([[maybe_unused]] checker::TSChecker *checker) override; checker::Type *Check([[maybe_unused]] checker::ETSChecker *checker) override; + ir::Expression *GetName() const + { + return name_; + } + private: ir::Expression *name_; }; diff --git a/ets2panda/ir/hasDoc.h b/ets2panda/ir/hasDoc.h new file mode 100644 index 0000000000..c58f4a12b6 --- /dev/null +++ b/ets2panda/ir/hasDoc.h @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2021-2022 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_IR_HAS_DOC_H +#define ES2PANDA_IR_HAS_DOC_H + +#include +#include + +#include "libpandabase/utils/arena_containers.h" + +namespace panda::es2panda::ir { +class HasDoc { +public: + std::optional GetDocComment() const + { + return doc_comment_; + } + + void SetDocComment(std::optional doc_comment) + { + doc_comment_ = std::move(doc_comment); + } + +private: + std::optional doc_comment_ {}; +}; +} // namespace panda::es2panda::ir + +#endif diff --git a/ets2panda/ir/statements/classDeclaration.h b/ets2panda/ir/statements/classDeclaration.h index 8984cf0651..52c4255e93 100644 --- a/ets2panda/ir/statements/classDeclaration.h +++ b/ets2panda/ir/statements/classDeclaration.h @@ -17,6 +17,7 @@ #define ES2PANDA_IR_STATEMENT_CLASS_DECLARATION_H #include "ir/statement.h" +#include "ir/hasDoc.h" namespace panda::es2panda::ir { class ClassDeclaration : public Statement { @@ -60,9 +61,19 @@ public: checker::Type *Check(checker::TSChecker *checker) override; checker::Type *Check(checker::ETSChecker *checker) override; + HasDoc &Documentation() + { + return doc_comment_; + } + const HasDoc &Documentation() const + { + return doc_comment_; + } + private: ClassDefinition *def_; ArenaVector decorators_; + HasDoc doc_comment_ {}; }; } // namespace panda::es2panda::ir diff --git a/ets2panda/ir/ts/tsEnumDeclaration.h b/ets2panda/ir/ts/tsEnumDeclaration.h index d6d318a62b..a8ba62b6bc 100644 --- a/ets2panda/ir/ts/tsEnumDeclaration.h +++ b/ets2panda/ir/ts/tsEnumDeclaration.h @@ -19,6 +19,7 @@ #include "varbinder/scope.h" #include "ir/statement.h" #include "varbinder/enumMemberResult.h" +#include "ir/hasDoc.h" namespace panda::es2panda::varbinder { class EnumVariable; @@ -109,6 +110,15 @@ public: checker::Type *Check(checker::TSChecker *checker) override; checker::Type *Check(checker::ETSChecker *checker) override; + HasDoc &Documentation() + { + return doc_comment_; + } + const HasDoc &Documentation() const + { + return doc_comment_; + } + private: varbinder::LocalScope *scope_; ArenaVector decorators_; @@ -116,6 +126,7 @@ private: ArenaVector members_; util::StringView internal_name_; bool is_const_; + HasDoc doc_comment_ {}; }; } // namespace panda::es2panda::ir diff --git a/ets2panda/ir/ts/tsEnumMember.h b/ets2panda/ir/ts/tsEnumMember.h index c169a782db..93685324e6 100644 --- a/ets2panda/ir/ts/tsEnumMember.h +++ b/ets2panda/ir/ts/tsEnumMember.h @@ -17,6 +17,7 @@ #define ES2PANDA_IR_TS_ENUM_MEMBER_H #include "ir/statement.h" +#include "ir/hasDoc.h" namespace panda::es2panda::ir { class Expression; @@ -48,9 +49,19 @@ public: checker::Type *Check(checker::TSChecker *checker) override; checker::Type *Check(checker::ETSChecker *checker) override; + HasDoc &Documentation() + { + return doc_comment_; + } + const HasDoc &Documentation() const + { + return doc_comment_; + } + private: Expression *key_; Expression *init_; + HasDoc doc_comment_ {}; }; } // namespace panda::es2panda::ir diff --git a/ets2panda/ir/ts/tsInterfaceDeclaration.h b/ets2panda/ir/ts/tsInterfaceDeclaration.h index e209324087..292e69bf3c 100644 --- a/ets2panda/ir/ts/tsInterfaceDeclaration.h +++ b/ets2panda/ir/ts/tsInterfaceDeclaration.h @@ -19,6 +19,7 @@ #include "varbinder/scope.h" #include "ir/statement.h" #include "util/language.h" +#include "ir/hasDoc.h" namespace panda::es2panda::varbinder { class Variable; @@ -144,6 +145,15 @@ public: checker::Type *Check([[maybe_unused]] checker::ETSChecker *checker) override; checker::Type *InferType(checker::TSChecker *checker, varbinder::Variable *binding_var) const; + HasDoc &Documentation() + { + return doc_comment_; + } + const HasDoc &Documentation() const + { + return doc_comment_; + } + private: ArenaVector decorators_; varbinder::LocalScope *scope_; @@ -154,6 +164,7 @@ private: util::StringView internal_name_ {}; bool is_static_; es2panda::Language lang_; + HasDoc doc_comment_ {}; }; } // namespace panda::es2panda::ir diff --git a/ets2panda/lexer/ETSLexer.cpp b/ets2panda/lexer/ETSLexer.cpp index aa5b41719e..ed32151c7e 100644 --- a/ets2panda/lexer/ETSLexer.cpp +++ b/ets2panda/lexer/ETSLexer.cpp @@ -15,6 +15,7 @@ #include "ETSLexer.h" #include "generated/keywords.h" +#include "libpandabase/utils/arena_containers.h" namespace panda::es2panda::lexer { // NOLINTNEXTLINE(google-default-arguments) @@ -79,16 +80,35 @@ void ETSLexer::CheckUtf16Compatible(char32_t cp) const } } +namespace { +bool IsDocCommentStart(util::StringView::Iterator it) +{ + if (it.Peek() != LEX_CHAR_ASTERISK) { + return false; + } + it.Forward(1); + return it.Peek() != LEX_CHAR_SLASH; +} +} // namespace + void ETSLexer::SkipMultiLineComment() { uint32_t depth = 1U; + bool is_doc_comment = IsDocCommentStart(Iterator()); + + auto start = Iterator().Save(); + // Just to reduce extra nested level(s) - auto const check_asterisk = [this, &depth]() -> bool { + auto const check_asterisk = [this, &depth, is_doc_comment, start]() -> bool { if (Iterator().Peek() == LEX_CHAR_SLASH) { Iterator().Forward(1); if (--depth == 0U) { + if (is_doc_comment) { + std::string_view view(start, Iterator().Save() - 1 - start); + doc_comment_.emplace(view, Allocator()->Adapter()); + } return false; } } @@ -127,6 +147,7 @@ void ETSLexer::SkipMultiLineComment() } } } + UNREACHABLE(); } void ETSLexer::ScanAsteriskPunctuator() @@ -211,4 +232,22 @@ bool ETSLexer::ScanDollarPunctuator() Iterator().Forward(1); return true; } + +std::optional ETSLexer::FetchDocComment() +{ + auto ret = std::move(doc_comment_); + ResetDocComment(); + return ret; +} + +void ETSLexer::SetDocComment(std::optional comment) +{ + doc_comment_ = std::move(comment); +} + +void ETSLexer::ResetDocComment() +{ + doc_comment_ = std::nullopt; +} + } // namespace panda::es2panda::lexer diff --git a/ets2panda/lexer/ETSLexer.h b/ets2panda/lexer/ETSLexer.h index 1ad9801099..d24b5e36a7 100644 --- a/ets2panda/lexer/ETSLexer.h +++ b/ets2panda/lexer/ETSLexer.h @@ -51,10 +51,17 @@ public: void CheckUtf16Compatible(char32_t cp) const; void ConvertNumber(const std::string &utf8, NumberFlags flags) override; + std::optional FetchDocComment(); + void SetDocComment(std::optional comment); + void ResetDocComment(); + protected: void ScanEqualsPunctuator() override; void ScanExclamationPunctuator() override; bool ScanDollarPunctuator() override; + +private: + std::optional doc_comment_ {}; }; } // namespace panda::es2panda::lexer diff --git a/ets2panda/parser/ETSparser.cpp b/ets2panda/parser/ETSparser.cpp index 1100af3be5..e18b6689d1 100644 --- a/ets2panda/parser/ETSparser.cpp +++ b/ets2panda/parser/ETSparser.cpp @@ -142,6 +142,16 @@ namespace fs = std::experimental::filesystem; namespace panda::es2panda::parser { using namespace std::literals::string_literals; +std::optional ETSParser::FetchDocComment() +{ + return static_cast(Lexer())->FetchDocComment(); +} + +void ETSParser::SetDocComment(std::optional comment) +{ + static_cast(Lexer())->SetDocComment(std::move(comment)); +} + std::unique_ptr ETSParser::InitLexer(const SourceFile &source_file) { GetProgram()->SetSource(source_file); @@ -749,6 +759,7 @@ ArenaVector ETSParser::ParseTopLevelStatements(ArenaVector(Lexer())->ResetDocComment(); } GetContext().Status() &= ~ParserStatus::IN_AMBIENT_CONTEXT; @@ -1260,6 +1271,7 @@ void ETSParser::ParseClassFieldDefiniton(ir::Identifier *field_name, ir::Modifie { lexer::SourcePosition start_loc = let_loc != nullptr ? *let_loc : Lexer()->GetToken().Start(); lexer::SourcePosition end_loc = start_loc; + auto doc_comment = FetchDocComment(); ir::TypeNode *type_annotation = nullptr; TypeAnnotationParsingOptions options = TypeAnnotationParsingOptions::THROW_ERROR; @@ -1319,6 +1331,7 @@ void ETSParser::ParseClassFieldDefiniton(ir::Identifier *field_name, ir::Modifie end_loc = type_annotation != nullptr ? type_annotation->End() : field_name->End(); } field->SetRange({start_loc, end_loc}); + field->Documentation().SetDocComment(std::move(doc_comment)); if ((modifiers & ir::ModifierFlags::CONST) != 0) { ASSERT(VarBinder()->GetScope()->Parent() != nullptr); @@ -1342,6 +1355,8 @@ void ETSParser::ParseClassFieldDefiniton(ir::Identifier *field_name, ir::Modifie ir::MethodDefinition *ETSParser::ParseClassMethodDefinition(ir::Identifier *method_name, ir::ModifierFlags modifiers, ir::Identifier *class_name, ir::Identifier *ident_node) { + auto doc_comment = FetchDocComment(); + auto *cur_scope = VarBinder()->GetScope(); auto res = cur_scope->Find(method_name->Name(), varbinder::ResolveBindingOptions::ALL); if (res.variable != nullptr && !res.variable->Declaration()->IsFunctionDecl() && res.scope == cur_scope) { @@ -1375,6 +1390,7 @@ ir::MethodDefinition *ETSParser::ParseClassMethodDefinition(ir::Identifier *meth func->AddFlag(ir::ScriptFunctionFlags::INSTANCE_EXTENSION_METHOD); } auto *method = AllocNode(method_kind, method_name, func_expr, modifiers, Allocator(), false); + method->Documentation().SetDocComment(std::move(doc_comment)); method->SetRange(func_expr->Range()); CreateClassFunctionDeclaration(method); @@ -1704,8 +1720,11 @@ ir::Statement *ETSParser::ParseTypeDeclaration(bool allow_static) modifiers |= ir::ClassDefinitionModifiers::INNER; } + auto doc_comment = FetchDocComment(); if (Lexer()->GetToken().Type() == lexer::TokenType::KEYW_CLASS) { - return ParseClassDeclaration(modifiers, flags); + auto decl = ParseClassDeclaration(modifiers, flags); + decl->Documentation().SetDocComment(std::move(doc_comment)); + return decl; } if (IsStructKeyword()) { @@ -1721,7 +1740,10 @@ ir::Statement *ETSParser::ParseTypeDeclaration(bool allow_static) return ParseInterfaceDeclaration(false); } case lexer::TokenType::KEYW_CLASS: { - return ParseClassDeclaration(modifiers); + auto doc_comment = FetchDocComment(); + auto decl = ParseClassDeclaration(modifiers); + decl->Documentation().SetDocComment(std::move(doc_comment)); + return decl; } case lexer::TokenType::KEYW_TYPE: { return ParseTypeAliasDeclaration(); @@ -1861,6 +1883,7 @@ ir::TSInterfaceDeclaration *ETSParser::ParseInterfaceBody(ir::Identifier *name, ir::Statement *ETSParser::ParseInterfaceDeclaration(bool is_static) { + auto doc_comment = FetchDocComment(); if ((GetContext().Status() & parser::ParserStatus::FUNCTION) != 0U) { ThrowSyntaxError("Local interface declaration support is not yet implemented."); } @@ -1877,6 +1900,7 @@ ir::Statement *ETSParser::ParseInterfaceDeclaration(bool is_static) VarBinder()->AddDecl(Lexer()->GetToken().Start(), Allocator(), ident, decl_node); decl->AsInterfaceDecl()->Add(decl_node); decl_node->SetRange({interface_start, Lexer()->GetToken().End()}); + decl_node->Documentation().SetDocComment(std::move(doc_comment)); return decl_node; } @@ -1889,6 +1913,8 @@ ir::Statement *ETSParser::ParseEnumDeclaration(bool is_const, bool is_static) ThrowSyntaxError("Local enum declaration support is not yet implemented."); } + auto doc_comment = FetchDocComment(); + lexer::SourcePosition enum_start = Lexer()->GetToken().Start(); Lexer()->NextToken(); // eat enum keyword @@ -1899,7 +1925,7 @@ ir::Statement *ETSParser::ParseEnumDeclaration(bool is_const, bool is_static) auto *decl = VarBinder()->AddDecl(Lexer()->GetToken().Start(), ident, decl_node, is_const); decl->BindScope(decl_node->Scope()); - + decl_node->Documentation().SetDocComment(std::move(doc_comment)); return decl_node; } @@ -4139,9 +4165,11 @@ ir::TSEnumDeclaration *ETSParser::ParseEnumMembers(ir::Identifier *const key, co }; // Get the underlying type of enum (number or string). It is defined from the first element ONLY! + auto comm = FetchDocComment(); auto const pos = Lexer()->Save(); auto const string_type_enum = is_string_enum(); Lexer()->Rewind(pos); + SetDocComment(std::move(comm)); ArenaVector members(Allocator()->Adapter()); const auto enum_ctx = varbinder::LexicalScope(VarBinder()); @@ -4167,6 +4195,7 @@ void ETSParser::ParseNumberEnum(ArenaVector &members) // Lambda to parse enum member (maybe with initializer) auto const parse_member = [this, &members, ¤t_value]() { + auto doc_comment = FetchDocComment(); auto *const ident = ExpectIdentifier(); auto [decl, var] = VarBinder()->NewVarDecl(ident->Start(), ident->Name()); var->SetScope(VarBinder()->GetScope()); @@ -4214,6 +4243,7 @@ void ETSParser::ParseNumberEnum(ArenaVector &members) auto *const member = AllocNode(ident, ordinal); member->SetRange({ident->Start(), end_loc}); + member->Documentation().SetDocComment(std::move(doc_comment)); decl->BindNode(member); members.emplace_back(member); diff --git a/ets2panda/parser/ETSparser.h b/ets2panda/parser/ETSparser.h index d1eb419aa8..e0138e7626 100644 --- a/ets2panda/parser/ETSparser.h +++ b/ets2panda/parser/ETSparser.h @@ -325,6 +325,9 @@ private: ir::TypeNode *CreateTypeAnnotation(TypeAnnotationParsingOptions *options, std::string_view source_code, std::string_view file_name = DEFAULT_SOURCE_FILE); + std::optional FetchDocComment(); + void SetDocComment(std::optional comment); + friend class ExternalSourceParser; friend class InnerSourceParser; diff --git a/ets2panda/parser/parserImpl.cpp b/ets2panda/parser/parserImpl.cpp index 4a19f88427..2b398b3ce5 100644 --- a/ets2panda/parser/parserImpl.cpp +++ b/ets2panda/parser/parserImpl.cpp @@ -692,7 +692,7 @@ ir::MethodDefinition *ParserImpl::BuildImplicitConstructor(ir::ClassDefinitionMo } auto *ctor = AllocNode(ir::MethodDefinitionKind::CONSTRUCTOR, key, func_expr, - ir::ModifierFlags::NONE, Allocator(), false); + ir::ModifierFlags::AUTO_GENERATED, Allocator(), false); ctor->SetRange({start_loc, lexer_->GetToken().End()}); -- Gitee