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
18namespace ark::es2panda::compiler {
19
20static 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
39static 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->ThrowSyntaxError("Files '" + prog1->FileName().Mutf8() + "' and '" +
54                                              prog2->FileName().Mutf8() +
55                                              "' are in the same folder, but have different package names.",
56                                          lexer::SourcePosition(0, 0));
57        }
58    };
59
60    for (const auto &srcIter : program->ExternalSources()) {
61        // in the external sources, all programs for a record in the map is in the same module,
62        // it's enough to check the first of them
63        const auto *const extSrc = std::get<1>(srcIter).front();
64        throwErrorIfPackagesConflict(program, extSrc);
65
66        for (const auto &srcIterCmp : program->ExternalSources()) {
67            const auto *const extSrcCpm = std::get<1>(srcIterCmp).front();
68            throwErrorIfPackagesConflict(extSrc, extSrcCpm);
69        }
70    }
71}
72
73static void ValidateImportDeclarationsSourcePath(const public_lib::Context *const ctx,
74                                                 const ArenaVector<parser::Program *> &packagePrograms,
75                                                 const std::vector<const ir::Statement *> &importDeclarations)
76{
77    for (const auto *const stmt : importDeclarations) {
78        const bool doesImportFromPackage =
79            std::any_of(packagePrograms.cbegin(), packagePrograms.cend(), [&stmt](const parser::Program *const prog) {
80                return prog->SourceFilePath() == stmt->AsETSImportDeclaration()->ResolvedSource()->Str();
81            });
82        if (doesImportFromPackage) {
83            ctx->parser->ThrowSyntaxError("Package module cannot import from a file in it's own package",
84                                          stmt->Start());
85        }
86    }
87}
88
89static 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
109bool PackageImplicitImport::Perform(public_lib::Context *const ctx, parser::Program *const program)
110{
111    if (!program->IsPackageModule() || 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