• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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