• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2023-2024 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 
18 namespace ark::es2panda::compiler {
19 
MergeExternalFilesIntoCompiledProgram(parser::Program * const program,const ArenaVector<parser::Program * > & packagePrograms)20 static void MergeExternalFilesIntoCompiledProgram(parser::Program *const program,
21                                                   const ArenaVector<parser::Program *> &packagePrograms)
22 {
23     for (auto *const extProg : packagePrograms) {
24         const auto extClassDecls = extProg->Ast()->Statements();
25         for (auto *const stmt : extClassDecls) {
26             if (stmt->IsETSPackageDeclaration()) {
27                 continue;
28             }
29 
30             stmt->SetParent(program->Ast());
31 
32             // Because same package files must be in one folder, relative path references in an external
33             // source's import declaration certainly will be the same (and can be resolved) from the global program too
34             program->Ast()->Statements().emplace_back(stmt);
35         }
36     }
37 }
38 
ValidateFolderContainOnlySamePackageFiles(const public_lib::Context * const ctx,const parser::Program * const program)39 static void ValidateFolderContainOnlySamePackageFiles(const public_lib::Context *const ctx,
40                                                       const parser::Program *const program)
41 {
42     const auto throwErrorIfPackagesConflict = [&ctx](const parser::Program *const prog1,
43                                                      const parser::Program *const prog2) {
44         if ((prog1 == prog2) || !(prog1->IsPackageModule() && prog2->IsPackageModule())) {
45             return;
46         }
47 
48         if ((prog1->ModuleName() != prog2->ModuleName()) && (prog1->SourceFileFolder() == prog2->SourceFileFolder())) {
49             // There exist 2 files in the same folder, with different package names
50             //
51             // Showing the full path would be more informative, but it also leaks it to the stdout, which is
52             // not the best idea
53             ctx->parser->LogSyntaxError("Files '" + prog1->FileName().Mutf8() + "' and '" + prog2->FileName().Mutf8() +
54                                             "' are in the same folder, but have different package names.",
55                                         lexer::SourcePosition(0, 0));
56         }
57     };
58 
59     for (const auto &srcIter : program->ExternalSources()) {
60         // in the external sources, all programs for a record in the map is in the same module,
61         // it's enough to check the first of them
62         const auto *const extSrc = std::get<1>(srcIter).front();
63         throwErrorIfPackagesConflict(program, extSrc);
64 
65         for (const auto &srcIterCmp : program->ExternalSources()) {
66             const auto *const extSrcCpm = std::get<1>(srcIterCmp).front();
67             throwErrorIfPackagesConflict(extSrc, extSrcCpm);
68         }
69     }
70 }
71 
ValidateImportDeclarationsSourcePath(const public_lib::Context * const ctx,const ArenaVector<parser::Program * > & packagePrograms,const std::vector<const ir::Statement * > & importDeclarations)72 static void ValidateImportDeclarationsSourcePath(const public_lib::Context *const ctx,
73                                                  const ArenaVector<parser::Program *> &packagePrograms,
74                                                  const std::vector<const ir::Statement *> &importDeclarations)
75 {
76     for (const auto *const stmt : importDeclarations) {
77         const bool doesImportFromPackage =
78             std::any_of(packagePrograms.cbegin(), packagePrograms.cend(), [&stmt](const parser::Program *const prog) {
79                 return prog->SourceFilePath() == stmt->AsETSImportDeclaration()->ResolvedSource()->Str();
80             });
81         if (doesImportFromPackage) {
82             ctx->parser->LogSyntaxError("Package module cannot import from a file in it's own package", stmt->Start());
83         }
84     }
85 }
86 
ValidateNoImportComesFromSamePackage(const public_lib::Context * const ctx,parser::Program * const program,ArenaVector<parser::Program * > packagePrograms)87 static void ValidateNoImportComesFromSamePackage(const public_lib::Context *const ctx, parser::Program *const program,
88                                                  ArenaVector<parser::Program *> packagePrograms)
89 {
90     // Making sure that the variable is not a reference. We modify the local vector here, which must not have any side
91     // effects to the original one. Don't change it.
92     static_assert(!std::is_reference_v<decltype(packagePrograms)>);
93     packagePrograms.emplace_back(program);
94 
95     for (const auto *const packageProg : packagePrograms) {
96         // Filter out only import declarations
97         std::vector<const ir::Statement *> importDeclarations {};
98         const auto &progStatements = packageProg->Ast()->Statements();
99         std::copy_if(progStatements.begin(), progStatements.end(), std::back_inserter(importDeclarations),
100                      [](const ir::Statement *const stmt) { return stmt->IsETSImportDeclaration(); });
101 
102         // Validate if all import declaration refers to a path outside of the package module
103         ValidateImportDeclarationsSourcePath(ctx, packagePrograms, importDeclarations);
104     }
105 }
106 
Perform(public_lib::Context * const ctx,parser::Program * const program)107 bool PackageImplicitImport::Perform(public_lib::Context *const ctx, parser::Program *const program)
108 {
109     if (!program->IsPackageModule() || program->VarBinder()->IsGenStdLib()) {
110         // Only run for package module files
111         return true;
112     }
113 
114     ValidateFolderContainOnlySamePackageFiles(ctx, program);
115 
116     auto &externalSources = program->ExternalSources();
117     if (externalSources.count(program->ModuleName()) == 0) {
118         // No other files in the package, return
119         return true;
120     }
121 
122     auto &packagePrograms = externalSources.at(program->ModuleName());
123 
124     // NOTE (mmartin): Very basic sorting of files in the package, to merge them in a prescribed order
125     std::stable_sort(packagePrograms.begin(), packagePrograms.end(),
126                      [](const parser::Program *const prog1, const parser::Program *const prog2) {
127                          return prog1->FileName() < prog2->FileName();
128                      });
129 
130     MergeExternalFilesIntoCompiledProgram(program, packagePrograms);
131     ValidateNoImportComesFromSamePackage(ctx, program, packagePrograms);
132 
133     // All entities were merged into the main program from the external sources of the same package,
134     // so we can delete all of them
135     externalSources.erase(program->ModuleName());
136 
137     return true;
138 }
139 
140 }  // namespace ark::es2panda::compiler
141