From b0c33ee807e6136ccaa5caf0592c8caf950dbbd3 Mon Sep 17 00:00:00 2001 From: wangyantian Date: Mon, 26 Sep 2022 20:57:19 +0800 Subject: [PATCH] Support parsing of namespace export declaration Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/I5TAHP Test: parser test, compiler test, test262 Signed-off-by: wangyantian Change-Id: I566cb5234b3e00218e43c59d18c3554cb5507a52 --- es2panda/BUILD.gn | 1 + es2panda/ir/astNodeMapping.h | 1 + .../ir/ts/tsNamespaceExportDeclaration.cpp | 45 ++++++ es2panda/ir/ts/tsNamespaceExportDeclaration.h | 55 +++++++ es2panda/parser/parserImpl.cpp | 15 +- es2panda/parser/parserImpl.h | 2 + es2panda/parser/statementParser.cpp | 40 ++++- ...-namespace-export-declaration-expected.txt | 145 ++++++++++++++++++ .../test-namespace-export-declaration.d.ts | 17 ++ es2panda/test/runner.py | 20 ++- es2panda/util/helpers.cpp | 14 ++ es2panda/util/helpers.h | 2 + 12 files changed, 343 insertions(+), 14 deletions(-) create mode 100644 es2panda/ir/ts/tsNamespaceExportDeclaration.cpp create mode 100644 es2panda/ir/ts/tsNamespaceExportDeclaration.h create mode 100644 es2panda/test/parser/ts/cases/declaration/test-namespace-export-declaration-expected.txt create mode 100644 es2panda/test/parser/ts/cases/declaration/test-namespace-export-declaration.d.ts diff --git a/es2panda/BUILD.gn b/es2panda/BUILD.gn index 075eedd3e8..70080d0361 100644 --- a/es2panda/BUILD.gn +++ b/es2panda/BUILD.gn @@ -154,6 +154,7 @@ es2panda_src = [ "ir/ts/tsModuleBlock.cpp", "ir/ts/tsModuleDeclaration.cpp", "ir/ts/tsNamedTupleMember.cpp", + "ir/ts/tsNamespaceExportDeclaration.cpp", "ir/ts/tsNeverKeyword.cpp", "ir/ts/tsNonNullExpression.cpp", "ir/ts/tsNullKeyword.cpp", diff --git a/es2panda/ir/astNodeMapping.h b/es2panda/ir/astNodeMapping.h index 0ae3acc429..5ca835452b 100644 --- a/es2panda/ir/astNodeMapping.h +++ b/es2panda/ir/astNodeMapping.h @@ -114,6 +114,7 @@ _(TS_PARAMETER_PROPERTY, TSParameterProperty) \ _(TS_MODULE_DECLARATION, TSModuleDeclaration) \ _(TS_IMPORT_EQUALS_DECLARATION, TSImportEqualsDeclaration) \ + _(TS_NAMESPACE_EXPORT_DECLARATION, TSNamespaceExportDeclaration) \ _(TS_FUNCTION_TYPE, TSFunctionType) \ _(TS_CONSTRUCTOR_TYPE, TSConstructorType) \ _(TS_TYPE_ALIAS_DECLARATION, TSTypeAliasDeclaration) \ diff --git a/es2panda/ir/ts/tsNamespaceExportDeclaration.cpp b/es2panda/ir/ts/tsNamespaceExportDeclaration.cpp new file mode 100644 index 0000000000..4ed6c17818 --- /dev/null +++ b/es2panda/ir/ts/tsNamespaceExportDeclaration.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 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 "tsNamespaceExportDeclaration.h" + +#include +#include + +namespace panda::es2panda::ir { + +void TSNamespaceExportDeclaration::Iterate(const NodeTraverser &cb) const +{ + cb(id_); +} + +void TSNamespaceExportDeclaration::Dump(ir::AstDumper *dumper) const +{ + dumper->Add({{"type", "TSNamespaceExportDeclaration"}, {"id", id_}}); +} + +void TSNamespaceExportDeclaration::Compile([[maybe_unused]] compiler::PandaGen *pg) const {} + +checker::Type *TSNamespaceExportDeclaration::Check([[maybe_unused]] checker::Checker *checker) const +{ + return nullptr; +} + +void TSNamespaceExportDeclaration::UpdateSelf(const NodeUpdater &cb, [[maybe_unused]] binder::Binder *binder) +{ + id_ = std::get(cb(id_))->AsIdentifier(); +} + +} // namespace panda::es2panda::ir \ No newline at end of file diff --git a/es2panda/ir/ts/tsNamespaceExportDeclaration.h b/es2panda/ir/ts/tsNamespaceExportDeclaration.h new file mode 100644 index 0000000000..24f2aa8e4c --- /dev/null +++ b/es2panda/ir/ts/tsNamespaceExportDeclaration.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 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_TS_NAMESPACE_EXPORT_DECLARATION_H +#define ES2PANDA_IR_TS_NAMESPACE_EXPORT_DECLARATION_H + +#include + +namespace panda::es2panda::compiler { +class PandaGen; +} // namespace panda::es2panda::compiler + +namespace panda::es2panda::checker { +class Checker; +class Type; +} // namespace panda::es2panda::checker + +namespace panda::es2panda::ir { + +class TSNamespaceExportDeclaration : public Statement { +public: + explicit TSNamespaceExportDeclaration(Identifier *id) + : Statement(AstNodeType::TS_NAMESPACE_EXPORT_DECLARATION), id_(id) + { + } + + const Identifier *Id() const + { + return id_; + } + + void Iterate(const NodeTraverser &cb) const override; + void Dump(ir::AstDumper *dumper) const override; + void Compile([[maybe_unused]] compiler::PandaGen *pg) const override; + checker::Type *Check([[maybe_unused]] checker::Checker *checker) const override; + void UpdateSelf(const NodeUpdater &cb, [[maybe_unused]] binder::Binder *binder) override; + +private: + Identifier *id_; +}; +} // namespace panda::es2panda::ir + +#endif \ No newline at end of file diff --git a/es2panda/parser/parserImpl.cpp b/es2panda/parser/parserImpl.cpp index bb76705438..09a79b3c26 100644 --- a/es2panda/parser/parserImpl.cpp +++ b/es2panda/parser/parserImpl.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -103,18 +104,11 @@ namespace panda::es2panda::parser { ParserImpl::ParserImpl(ScriptExtension extension) : program_(extension), context_(&program_) {} -template -bool IsSuffix(T const &filename, T const &suffix) -{ - return (filename.length() > suffix.length()) && - (filename.rfind(suffix) == (filename.length() - suffix.length())); -} - std::unique_ptr ParserImpl::InitLexer(const std::string &fileName, const std::string &source) { bool isDtsFile = false; if (Extension() == ScriptExtension::TS) { - isDtsFile = IsSuffix(fileName, std::string(".d.ts")); + isDtsFile = util::Helpers::FileExtensionIs(fileName, ".d.ts"); } program_.SetSource(source, fileName, isDtsFile); auto lexer = std::make_unique(&context_); @@ -3547,4 +3541,9 @@ void ParserImpl::AddHotfixHelper(util::Hotfix *hotfixHelper) program_.AddHotfixHelper(hotfixHelper); } +bool ParserImpl::IsDtsFile() const +{ + return program_.IsDtsFile(); +} + } // namespace panda::es2panda::parser diff --git a/es2panda/parser/parserImpl.h b/es2panda/parser/parserImpl.h index 7476ac673e..16d2929ce9 100644 --- a/es2panda/parser/parserImpl.h +++ b/es2panda/parser/parserImpl.h @@ -188,6 +188,7 @@ public: { return program_.Allocator(); } + bool IsDtsFile() const; private: bool IsStartOfMappedType() const; @@ -439,6 +440,7 @@ private: ir::TSImportEqualsDeclaration *ParseTsImportEqualsDeclaration(const lexer::SourcePosition &startLoc, bool isExport = false); + ir::TSNamespaceExportDeclaration *ParseTsNamespaceExportDeclaration(const lexer::SourcePosition &startLoc); ir::TSModuleBlock *ParseTsModuleBlock(); ir::BlockStatement *ParseFunctionBody(); ir::BlockStatement *ParseBlockStatement(); diff --git a/es2panda/parser/statementParser.cpp b/es2panda/parser/statementParser.cpp index b70db86492..fb0b61b1f8 100644 --- a/es2panda/parser/statementParser.cpp +++ b/es2panda/parser/statementParser.cpp @@ -66,6 +66,7 @@ #include #include #include +#include #include #include #include @@ -99,7 +100,7 @@ void ParserImpl::CheckDeclare() case lexer::TokenType::KEYW_CLASS: { break; } - case lexer::TokenType::LITERAL_IDENT: + case lexer::TokenType::LITERAL_IDENT: { if (lexer_->GetToken().KeywordType() == lexer::TokenType::KEYW_TYPE || lexer_->GetToken().KeywordType() == lexer::TokenType::KEYW_MODULE || lexer_->GetToken().KeywordType() == lexer::TokenType::KEYW_GLOBAL || @@ -111,6 +112,7 @@ void ParserImpl::CheckDeclare() } [[fallthrough]]; + } default: { ThrowSyntaxError("Unexpected token."); } @@ -132,6 +134,8 @@ bool ParserImpl::IsLabelFollowedByIterationStatement() lexer_->NextToken(); return IsLabelFollowedByIterationStatement(); } + + [[fallthrough]]; } default: return false; @@ -429,6 +433,33 @@ ir::TSImportEqualsDeclaration *ParserImpl::ParseTsImportEqualsDeclaration(const return importEqualsDecl; } +ir::TSNamespaceExportDeclaration *ParserImpl::ParseTsNamespaceExportDeclaration(const lexer::SourcePosition &startLoc) +{ + if (!IsDtsFile()) { + ThrowSyntaxError("namespace export declaration is only supported in TypeScript '.d.ts'"); + } + ASSERT(lexer_->GetToken().KeywordType() == lexer::TokenType::KEYW_AS); + lexer_->NextToken(); // eat as keyword + if (lexer_->GetToken().KeywordType() != lexer::TokenType::KEYW_NAMESPACE) { + ThrowSyntaxError("'namespace' expected"); + } + lexer_->NextToken(); // eat namespace keyword + if (lexer_->GetToken().Type() != lexer::TokenType::LITERAL_IDENT) { + ThrowSyntaxError("identifier expected"); + } + + auto *id = AllocNode(lexer_->GetToken().Ident(), Allocator()); + id->SetRange(lexer_->GetToken().Loc()); + lexer_->NextToken(); // eat identifier + + auto *namespaceExportDecl = AllocNode(id); + namespaceExportDecl->SetRange({startLoc, lexer_->GetToken().End()}); + + ConsumeSemicolon(namespaceExportDecl); + + return namespaceExportDecl; +} + ir::TSModuleBlock *ParserImpl::ParseTsModuleBlock() { if (lexer_->GetToken().Type() != lexer::TokenType::PUNCTUATOR_LEFT_BRACE) { @@ -2496,6 +2527,13 @@ ir::Statement *ParserImpl::ParseExportDeclaration(StatementParsingFlags flags, [[fallthrough]]; } + case lexer::TokenType::LITERAL_IDENT: { + if (Extension() == ScriptExtension::TS && lexer_->GetToken().KeywordType() == lexer::TokenType::KEYW_AS) { + return ParseTsNamespaceExportDeclaration(startLoc); + } + + [[fallthrough]]; + } default: { // export [var] id ir::ExportNamedDeclaration *exportDecl = ParseNamedExportDeclaration(startLoc, std::move(decorators)); diff --git a/es2panda/test/parser/ts/cases/declaration/test-namespace-export-declaration-expected.txt b/es2panda/test/parser/ts/cases/declaration/test-namespace-export-declaration-expected.txt new file mode 100644 index 0000000000..f6b2d8c17d --- /dev/null +++ b/es2panda/test/parser/ts/cases/declaration/test-namespace-export-declaration-expected.txt @@ -0,0 +1,145 @@ +{ + "type": "Program", + "statements": [ + { + "type": "ExportNamedDeclaration", + "declaration": { + "type": "TSDeclareFunction", + "function": { + "type": "ScriptFunction", + "id": { + "type": "Identifier", + "name": "isPrime", + "decorators": [], + "loc": { + "start": { + "line": 16, + "column": 17 + }, + "end": { + "line": 16, + "column": 24 + } + } + }, + "generator": false, + "async": false, + "expression": false, + "params": [ + { + "type": "Identifier", + "name": "x", + "typeAnnotation": { + "type": "TSNumberKeyword", + "loc": { + "start": { + "line": 16, + "column": 28 + }, + "end": { + "line": 16, + "column": 34 + } + } + }, + "decorators": [], + "loc": { + "start": { + "line": 16, + "column": 25 + }, + "end": { + "line": 16, + "column": 26 + } + } + } + ], + "returnType": { + "type": "TSBooleanKeyword", + "loc": { + "start": { + "line": 16, + "column": 37 + }, + "end": { + "line": 16, + "column": 44 + } + } + }, + "loc": { + "start": { + "line": 16, + "column": 8 + }, + "end": { + "line": 16, + "column": 45 + } + } + }, + "loc": { + "start": { + "line": 16, + "column": 8 + }, + "end": { + "line": 16, + "column": 45 + } + } + }, + "source": null, + "specifiers": [], + "loc": { + "start": { + "line": 16, + "column": 1 + }, + "end": { + "line": 16, + "column": 45 + } + } + }, + { + "type": "TSNamespaceExportDeclaration", + "id": { + "type": "Identifier", + "name": "mathLib", + "decorators": [], + "loc": { + "start": { + "line": 17, + "column": 21 + }, + "end": { + "line": 17, + "column": 28 + } + } + }, + "loc": { + "start": { + "line": 17, + "column": 1 + }, + "end": { + "line": 17, + "column": 29 + } + } + } + ], + "loc": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 17, + "column": 29 + } + } +} diff --git a/es2panda/test/parser/ts/cases/declaration/test-namespace-export-declaration.d.ts b/es2panda/test/parser/ts/cases/declaration/test-namespace-export-declaration.d.ts new file mode 100644 index 0000000000..79d7ad4637 --- /dev/null +++ b/es2panda/test/parser/ts/cases/declaration/test-namespace-export-declaration.d.ts @@ -0,0 +1,17 @@ +/* + * Copyright (c) 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. + */ + +export function isPrime(x: number): boolean; +export as namespace mathLib; \ No newline at end of file diff --git a/es2panda/test/runner.py b/es2panda/test/runner.py index 76526b230e..8b79f1de9f 100755 --- a/es2panda/test/runner.py +++ b/es2panda/test/runner.py @@ -145,6 +145,9 @@ class Test: def log_cmd(self, cmd): self.reproduce += "\n" + ' '.join(cmd) + def get_path_to_expected(self): + return "%s-expected.txt" % (path.splitext(self.path)[0]) + def run(self, runner): cmd = runner.cmd_prefix + [runner.es2panda, "--dump-ast"] cmd.extend(self.flags) @@ -156,7 +159,7 @@ class Test: out, err = process.communicate() self.output = out.decode("utf-8", errors="ignore") + err.decode("utf-8", errors="ignore") - expected_path = "%s-expected.txt" % (path.splitext(self.path)[0]) + expected_path = self.get_path_to_expected(); try: with open(expected_path, 'r') as fp: expected = fp.read() @@ -496,13 +499,13 @@ class RegressionRunner(Runner): def __init__(self, args): Runner.__init__(self, args, "Regresssion") - def add_directory(self, directory, extension, flags): + def add_directory(self, directory, extension, flags, func=Test): glob_expression = path.join( self.test_root, directory, "*.%s" % (extension)) files = glob(glob_expression) files = fnmatch.filter(files, self.test_root + '**' + self.args.filter) - self.tests += list(map(lambda f: Test(f, flags), files)) + self.tests += list(map(lambda f: func(f, flags), files)) def test_path(self, src): return src @@ -807,7 +810,7 @@ class CompilerTest(Test): process = subprocess.Popen(run_abc_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = process.communicate() self.output = out.decode("utf-8", errors="ignore") + err.decode("utf-8", errors="ignore") - expected_path = "%s-expected.txt" % (path.splitext(self.path)[0]) + expected_path = self.get_path_to_expected() try: with open(expected_path, 'r') as fp: expected = fp.read() @@ -823,6 +826,12 @@ class CompilerTest(Test): return self +class TSDeclarationTest(Test): + def get_path_to_expected(self): + file_name = self.path[:self.path.find(".d.ts")] + return "%s-expected.txt" % file_name + + def main(): args = get_args() @@ -835,7 +844,8 @@ def main(): ["--parse-only", "--module", "--extension=ts"]) runner.add_directory("parser/ts/type_checker", "ts", ["--parse-only", "--enable-type-check", "--module", "--extension=ts"]) - + runner.add_directory("parser/ts/cases/declaration", "d.ts", + ["--parse-only", "--module", "--extension=ts"], TSDeclarationTest) runners.append(runner) if args.test262: diff --git a/es2panda/util/helpers.cpp b/es2panda/util/helpers.cpp index afe9ed62ab..14f909b075 100644 --- a/es2panda/util/helpers.cpp +++ b/es2panda/util/helpers.cpp @@ -122,6 +122,20 @@ int64_t Helpers::GetIndex(const util::StringView &str) return value; } +bool Helpers::FileExtensionIs(std::string_view filePath, std::string_view extension) +{ + return filePath.length() > extension.length() && Helpers::EndsWith(filePath, extension); +} + +bool Helpers::EndsWith(std::string_view str, std::string_view suffix) +{ + if (str.length() < suffix.length()) { + return false; + } + size_t expectPos = str.length() - suffix.length(); + return str.find(suffix, expectPos) == expectPos; +} + std::string Helpers::ToString(double number) { std::string str; diff --git a/es2panda/util/helpers.h b/es2panda/util/helpers.h index 35144bce15..9321178fda 100644 --- a/es2panda/util/helpers.h +++ b/es2panda/util/helpers.h @@ -56,6 +56,8 @@ public: static bool IsIndex(double number); static int64_t GetIndex(const util::StringView &str); + static bool FileExtensionIs(std::string_view filePath, std::string_view extension); + static bool EndsWith(std::string_view str, std::string_view suffix); static std::string ToString(double number); static util::StringView ToStringView(ArenaAllocator *allocator, double number); static util::StringView ToStringView(ArenaAllocator *allocator, int32_t number); -- Gitee