1 /**
2 * Copyright (c) 2023-2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "packageImplicitImport.h"
17 #include <generated/diagnostic.h>
18
19 namespace ark::es2panda::compiler {
20
MergeExternalFilesIntoCompiledProgram(parser::Program * const program,const ArenaVector<parser::Program * > & packagePrograms)21 static void MergeExternalFilesIntoCompiledProgram(parser::Program *const program,
22 const ArenaVector<parser::Program *> &packagePrograms)
23 {
24 for (auto *const extProg : packagePrograms) {
25 const auto extClassDecls = extProg->Ast()->Statements();
26 for (auto *const stmt : extClassDecls) {
27 if (stmt->IsETSPackageDeclaration()) {
28 continue;
29 }
30
31 stmt->SetParent(program->Ast());
32
33 // Because same package files must be in one folder, relative path references in an external
34 // source's import declaration certainly will be the same (and can be resolved) from the global program too
35 program->Ast()->Statements().emplace_back(stmt);
36 }
37 }
38 }
39
ValidateFolderContainOnlySamePackageFiles(const public_lib::Context * const ctx,const parser::Program * const program)40 static void ValidateFolderContainOnlySamePackageFiles(const public_lib::Context *const ctx,
41 const parser::Program *const program)
42 {
43 const auto throwErrorIfPackagesConflict = [&ctx](const parser::Program *const prog1,
44 const parser::Program *const prog2) {
45 if ((prog1 == prog2) || !prog1->IsPackage() || !prog2->IsPackage()) {
46 return;
47 }
48
49 if (prog1->ModuleName() != prog2->ModuleName() &&
50 prog1->SourceFile().GetAbsoluteParentFolder() == prog2->SourceFile().GetAbsoluteParentFolder()) {
51 // There exist 2 files in the same folder, with different package names
52 //
53 // Showing the full path would be more informative, but it also leaks it to the stdout, which is
54 // not the best idea
55 ctx->parser->LogError(diagnostic::DIFFERENT_PACKAGE_NAME,
56 {prog1->FileName().Mutf8(), prog2->FileName().Mutf8()},
57 lexer::SourcePosition(0, 0, prog1));
58 }
59 };
60
61 for (const auto &srcIter : program->ExternalSources()) {
62 // in the external sources, all programs for a record in the map is in the same module,
63 // it's enough to check the first of them
64 const auto *const extSrc = std::get<1>(srcIter).front();
65 throwErrorIfPackagesConflict(program, extSrc);
66
67 for (const auto &srcIterCmp : program->ExternalSources()) {
68 const auto *const extSrcCpm = std::get<1>(srcIterCmp).front();
69 throwErrorIfPackagesConflict(extSrc, extSrcCpm);
70 }
71 }
72 }
73
ValidateImportDeclarationsSourcePath(const public_lib::Context * const ctx,const ArenaVector<parser::Program * > & packagePrograms,const std::vector<const ir::Statement * > & importDeclarations)74 static void ValidateImportDeclarationsSourcePath(const public_lib::Context *const ctx,
75 const ArenaVector<parser::Program *> &packagePrograms,
76 const std::vector<const ir::Statement *> &importDeclarations)
77 {
78 for (const auto *const stmt : importDeclarations) {
79 const bool doesImportFromPackage =
80 std::any_of(packagePrograms.cbegin(), packagePrograms.cend(), [&stmt](const parser::Program *const prog) {
81 return prog->SourceFilePath() == stmt->AsETSImportDeclaration()->ResolvedSource();
82 });
83 if (doesImportFromPackage) {
84 ctx->parser->LogError(diagnostic::PACKAGE_MODULE_IMPORT_OWN_PACKAGE, {}, stmt->Start());
85 }
86 }
87 }
88
ValidateNoImportComesFromSamePackage(const public_lib::Context * const ctx,parser::Program * const program,ArenaVector<parser::Program * > packagePrograms)89 static void ValidateNoImportComesFromSamePackage(const public_lib::Context *const ctx, parser::Program *const program,
90 ArenaVector<parser::Program *> packagePrograms)
91 {
92 // Making sure that the variable is not a reference. We modify the local vector here, which must not have any side
93 // effects to the original one. Don't change it.
94 static_assert(!std::is_reference_v<decltype(packagePrograms)>);
95 packagePrograms.emplace_back(program);
96
97 for (const auto *const packageProg : packagePrograms) {
98 // Filter out only import declarations
99 std::vector<const ir::Statement *> importDeclarations {};
100 const auto &progStatements = packageProg->Ast()->Statements();
101 std::copy_if(progStatements.begin(), progStatements.end(), std::back_inserter(importDeclarations),
102 [](const ir::Statement *const stmt) { return stmt->IsETSImportDeclaration(); });
103
104 // Validate if all import declaration refers to a path outside of the package module
105 ValidateImportDeclarationsSourcePath(ctx, packagePrograms, importDeclarations);
106 }
107 }
108
Perform(public_lib::Context * const ctx,parser::Program * const program)109 bool PackageImplicitImport::Perform(public_lib::Context *const ctx, parser::Program *const program)
110 {
111 if (!program->IsPackage() || program->VarBinder()->IsGenStdLib()) {
112 // Only run for package module files
113 return true;
114 }
115
116 ValidateFolderContainOnlySamePackageFiles(ctx, program);
117
118 auto &externalSources = program->ExternalSources();
119 if (externalSources.count(program->ModuleName()) == 0) {
120 // No other files in the package, return
121 return true;
122 }
123
124 auto &packagePrograms = externalSources.at(program->ModuleName());
125
126 // NOTE (mmartin): Very basic sorting of files in the package, to merge them in a prescribed order
127 std::stable_sort(packagePrograms.begin(), packagePrograms.end(),
128 [](const parser::Program *const prog1, const parser::Program *const prog2) {
129 return prog1->FileName() < prog2->FileName();
130 });
131
132 MergeExternalFilesIntoCompiledProgram(program, packagePrograms);
133 ValidateNoImportComesFromSamePackage(ctx, program, packagePrograms);
134
135 // All entities were merged into the main program from the external sources of the same package,
136 // so we can delete all of them
137 externalSources.erase(program->ModuleName());
138
139 return true;
140 }
141
142 } // namespace ark::es2panda::compiler
143