• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2022-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 "options.h"
17 #include "util/diagnostic.h"
18 #include "util/diagnosticEngine.h"
19 #include "util/ustring.h"
20 #include "os/filesystem.h"
21 #include "utils/pandargs.h"
22 #include "arktsconfig.h"
23 #include "generated/diagnostic.h"
24 
25 #include <random>
26 #include <unordered_set>
27 #include <utility>
28 
29 #ifdef PANDA_WITH_BYTECODE_OPTIMIZER
30 #include "bytecode_optimizer/bytecodeopt_options.h"
31 #include "compiler/compiler_options.h"
32 #endif
33 
34 #ifdef ES2PANDA_COMPILE_BY_GN
35 #include "generated/es2panda_build_info.h"
36 #endif
37 
38 namespace ark::es2panda::util {
39 
Usage(const ark::PandArgParser & argparser)40 static std::string Usage(const ark::PandArgParser &argparser)
41 {
42     std::stringstream ss;
43 
44     ss << argparser.GetErrorString() << std::endl;
45     ss << "Usage: es2panda [OPTIONS] [input file]" << std::endl;
46     ss << std::endl;
47     ss << "optional arguments:" << std::endl;
48     ss << argparser.GetHelpString() << std::endl;
49     ss << std::endl;
50 
51     return ss.str();
52 }
53 
GetVersion()54 static std::string GetVersion()
55 {
56     std::stringstream ss;
57 
58     ss << std::endl;
59     ss << "  Es2panda Version " << ES2PANDA_VERSION << std::endl;
60 
61 // add check for PANDA_PRODUCT_BUILD after normal version tracking will be implemented
62 #ifdef ES2PANDA_DATE
63     ss << std::endl;
64     ss << "  Build date: ";
65     ss << ES2PANDA_DATE;
66 #endif  // ES2PANDA_DATE
67 #ifdef ES2PANDA_HASH
68     ss << std::endl;
69     ss << "  Last commit hash: ";
70     ss << ES2PANDA_HASH;
71     ss << std::endl;
72 #endif  // ES2PANDA_HASH
73 
74     return ss.str();
75 }
76 
77 template <typename T>
CallPandArgParser(const std::vector<std::string> & args,T & options,util::DiagnosticEngine & diagnosticEngine)78 bool Options::CallPandArgParser(const std::vector<std::string> &args, T &options,
79                                 util::DiagnosticEngine &diagnosticEngine)
80 {
81     ark::PandArgParser parser;
82     options.AddOptions(&parser);
83 
84     if (!parser.Parse(args)) {
85         diagnosticEngine.LogFatalError(parser.GetErrorString());
86         std::cerr << parser.GetHelpString();
87         return false;
88     }
89 
90     if (auto optionsErr = options.Validate(); optionsErr) {
91         diagnosticEngine.LogFatalError(optionsErr.value().GetMessage());
92         return false;
93     }
94 
95     return true;
96 }
97 
CallPandArgParser(const std::vector<std::string> & args)98 bool Options::CallPandArgParser(const std::vector<std::string> &args)
99 {
100     ark::PandArgParser parser;
101     AddOptions(&parser);
102     parser.PushBackTail(&inputFile_);
103     parser.EnableTail();
104     parser.EnableRemainder();
105     if (!parser.Parse(args) || IsHelp()) {
106         std::cerr << Usage(parser);
107         return false;
108     }
109 
110     if (auto optionsErr = Validate(); optionsErr) {
111         diagnosticEngine_.LogFatalError(optionsErr.value().GetMessage());
112         return false;
113     }
114 
115     return true;
116 }
117 
SplitPath(std::string_view path)118 static std::tuple<std::string_view, std::string_view, std::string_view> SplitPath(std::string_view path)
119 {
120     std::string_view fileDirectory;
121     std::string_view fileBaseName = path;
122     auto lastDelimPos = fileBaseName.find_last_of(ark::os::file::File::GetPathDelim());
123     if (lastDelimPos != std::string_view::npos) {
124         ++lastDelimPos;
125         fileDirectory = fileBaseName.substr(0, lastDelimPos);
126         fileBaseName = fileBaseName.substr(lastDelimPos);
127     }
128 
129     // Save all extensions.
130     std::string_view fileExtensions;
131     auto fileBaseNamePos = fileBaseName.find_first_of('.');
132     if (fileBaseNamePos > 0 && fileBaseNamePos != std::string_view::npos) {
133         fileExtensions = fileBaseName.substr(fileBaseNamePos);
134         fileBaseName = fileBaseName.substr(0, fileBaseNamePos);
135     }
136 
137     return {fileDirectory, fileBaseName, fileExtensions};
138 }
139 
140 /**
141  * @brief Generate evaluated expression wrapping code.
142  * @param sourceFilePath used for generating a unique package name.
143  * @param input expression source code file stream.
144  * @param output stream for generating expression wrapper.
145  */
GenerateEvaluationWrapper(std::string_view sourceFilePath,std::ifstream & input,std::stringstream & output)146 static void GenerateEvaluationWrapper(std::string_view sourceFilePath, std::ifstream &input, std::stringstream &output)
147 {
148     static constexpr std::string_view EVAL_PREFIX = "eval_";
149     static constexpr std::string_view EVAL_SUFFIX = "_eval";
150 
151     auto splittedPath = SplitPath(sourceFilePath);
152     auto fileBaseName = std::get<1>(splittedPath);
153 
154     std::random_device rd;
155     std::stringstream ss;
156     ss << EVAL_PREFIX << fileBaseName << '_' << rd() << EVAL_SUFFIX;
157     auto methodName = ss.str();
158 
159     output << "package " << methodName << "; class " << methodName << " { private static " << methodName << "() { "
160            << input.rdbuf() << " } }";
161 }
162 
ParseInputOutput()163 bool Options::ParseInputOutput()
164 {
165     auto isDebuggerEvalMode = IsDebuggerEval();
166     if (isDebuggerEvalMode && compilationMode_ != CompilationMode::SINGLE_FILE) {
167         diagnosticEngine_.LogDiagnostic(diagnostic::EVAL_MODE_NOT_SINGLE_INPUT, DiagnosticMessageParams {});
168         return false;
169     }
170 
171     if (compilationMode_ == CompilationMode::SINGLE_FILE || GetExtension() != ScriptExtension::ETS) {
172         std::ifstream inputStream(SourceFileName());
173         if (inputStream.fail()) {
174             diagnosticEngine_.LogDiagnostic(diagnostic::OPEN_FAILED,
175                                             util::DiagnosticMessageParams {util::StringView(SourceFileName())});
176             return false;
177         }
178 
179         std::stringstream ss;
180         if (isDebuggerEvalMode) {
181             GenerateEvaluationWrapper(SourceFileName(), inputStream, ss);
182         } else {
183             ss << inputStream.rdbuf();
184         }
185         parserInputContents_ = ss.str();
186     }
187 
188     if (WasSetOutput()) {
189         if (compilationMode_ == CompilationMode::PROJECT) {
190             diagnosticEngine_.LogDiagnostic(diagnostic::PROJ_COMP_WITH_OUTPUT, DiagnosticMessageParams {});
191             return false;
192         }
193     } else {
194         SetOutput(ark::os::RemoveExtension(BaseName(SourceFileName())).append(".abc"));
195     }
196 
197     return true;
198 }
199 
Parse(Span<const char * const> args)200 bool Options::Parse(Span<const char *const> args)
201 {
202     std::vector<std::string> es2pandaArgs;
203     auto argc = args.size();
204     for (size_t i = 1; i < argc; i++) {
205         es2pandaArgs.emplace_back(args[i]);
206     }
207 
208     if (!CallPandArgParser(es2pandaArgs)) {
209         return false;
210     }
211 
212     if (IsVersion()) {
213         std::cerr << GetVersion();
214         return false;
215     }
216 #ifdef PANDA_WITH_BYTECODE_OPTIMIZER
217     if ((WasSetBcoCompiler() && !CallPandArgParser(GetBcoCompiler(), ark::compiler::g_options, diagnosticEngine_)) ||
218         (WasSetBcoOptimizer() &&
219          !CallPandArgParser(GetBcoOptimizer(), ark::bytecodeopt::g_options, diagnosticEngine_))) {
220         return false;
221     }
222 #endif
223 
224     DetermineCompilationMode();
225     if (!DetermineExtension()) {
226         return false;
227     }
228     if (!ParseInputOutput()) {
229         return false;
230     }
231     if (extension_ != ScriptExtension::JS && IsModule()) {
232         diagnosticEngine_.LogDiagnostic(diagnostic::MODULE_INVALID_EXT, DiagnosticMessageParams {});
233         return false;
234     }
235 
236     if ((WasSetDumpEtsSrcBeforePhases() || WasSetDumpEtsSrcAfterPhases()) && extension_ != ScriptExtension::ETS) {
237         diagnosticEngine_.LogDiagnostic(diagnostic::DUMP_ETS_INVALID_EXT, DiagnosticMessageParams {});
238         return false;
239     }
240 
241     if (WasSetLogLevel()) {
242         logLevel_ = Logger::LevelFromString(GetLogLevel());
243     }
244 
245     parseJsdoc_ = WasSetParseJsdoc();
246 
247     InitCompilerOptions();
248 
249     return ProcessEtsSpecificOptions();
250 }
251 
VecToSet(const std::vector<std::string> & v)252 auto VecToSet(const std::vector<std::string> &v)
253 {
254     return std::set<std::string>(v.begin(), v.end());
255 }
256 
InitAstVerifierOptions()257 void Options::InitAstVerifierOptions()
258 {
259     auto initSeverity = [](std::array<bool, gen::ast_verifier::COUNT> *a, const std::vector<std::string> &v) {
260         for (const auto &str : v) {
261             (*a)[gen::ast_verifier::FromString(str)] = true;
262         }
263     };
264     initSeverity(&verifierWarnings_, gen::Options::GetAstVerifierWarnings());
265     initSeverity(&verifierErrors_, gen::Options::GetAstVerifierErrors());
266 
267     astVerifierPhases_ = VecToSet(gen::Options::GetAstVerifierPhases());
268 
269     if (HasVerifierPhase("before")) {
270         astVerifierBeforePhases_ = true;
271     }
272     if (HasVerifierPhase("each")) {
273         astVerifierEachPhase_ = true;
274     }
275     if (HasVerifierPhase("after")) {
276         astVerifierAfterPhases_ = true;
277     }
278 }
279 
InitCompilerOptions()280 void Options::InitCompilerOptions()
281 {
282     skipPhases_ = VecToSet(gen::Options::GetSkipPhases());
283 
284     dumpBeforePhases_ = VecToSet(gen::Options::GetDumpBeforePhases());
285     dumpEtsSrcBeforePhases_ = VecToSet(gen::Options::GetDumpEtsSrcBeforePhases());
286     dumpAfterPhases_ = VecToSet(gen::Options::GetDumpAfterPhases());
287     dumpEtsSrcAfterPhases_ = VecToSet(gen::Options::GetDumpEtsSrcAfterPhases());
288 
289     InitAstVerifierOptions();
290 
291     if (IsEtsWarnings()) {
292         InitializeWarnings();
293     }
294 }
295 
InitializeWarnings()296 void Options::InitializeWarnings()
297 {
298     std::array<bool, ETSWarnings::COUNT> warningSet {};
299     ES2PANDA_ASSERT(ETSWarnings::LAST < ETSWarnings::COUNT);
300 
301     const auto processWarningList = [&warningSet](const auto &list, bool v) {
302         static const std::map<std::string_view, std::pair<size_t, size_t>> WARNING_GROUPS {
303             {"subset_aware", {ETSWarnings::SUBSET_AWARE_FIRST, ETSWarnings::SUBSET_AWARE_LAST}},
304             {"subset_unaware", {ETSWarnings::SUBSET_UNAWARE_FIRST, ETSWarnings::SUBSET_UNAWARE_LAST}}};
305         const auto setWarningRange = [&warningSet, v](size_t first, size_t last) {
306             ES2PANDA_ASSERT(last < ETSWarnings::COUNT);
307             for (size_t i = first; i <= last; i++) {
308                 warningSet[i] = v;
309             }
310         };
311         for (const auto &warningOrGroup : list) {
312             if (WARNING_GROUPS.find(warningOrGroup) != WARNING_GROUPS.end()) {
313                 auto [first, last] = WARNING_GROUPS.at(warningOrGroup);
314                 setWarningRange(first, last);
315                 continue;
316             }
317             ES2PANDA_ASSERT(ets_warnings::FromString(warningOrGroup) != ETSWarnings::INVALID);
318             warningSet[ets_warnings::FromString(warningOrGroup)] = v;
319         }
320     };
321     processWarningList(GetEtsWarningsEnable(), true);
322     processWarningList(GetEtsWarningsDisable(), false);
323     for (size_t i = ETSWarnings::FIRST; i <= ETSWarnings::LAST; i++) {
324         if (warningSet[i]) {
325             etsWarningCollection_.emplace_back(static_cast<ETSWarnings>(i));
326         }
327     }
328 }
329 
DetermineExtension()330 bool Options::DetermineExtension()
331 {
332     if (compilationMode_ == CompilationMode::PROJECT) {
333         if (WasSetExtension() && gen::Options::GetExtension() != "ets") {
334             diagnosticEngine_.LogDiagnostic(diagnostic::PROJECT_EXT_NOT_ETS, DiagnosticMessageParams {});
335             return false;
336         }
337         extension_ = ScriptExtension::ETS;
338         return true;
339     }
340     std::string sourceFileExtension = SourceFileName().substr(SourceFileName().find_last_of('.') + 1);
341 #ifdef ENABLE_AFTER_21192
342     // NOTE(mkaskov): Enable after #21192
343     if (!SourceFileName().empty() && WasSetExtension() && gen::Options::GetExtension() != sourceFileExtension) {
344         diagnosticEngine_.LogDiagnostic(
345             diagnostic::EXTENSION_MISMATCH,
346             {std::string_view(sourceFileExtension), std::string_view(gen::Options::GetExtension())});
347     }
348 #endif  // ENABLE_AFTER_21192
349     // Note: the file suffix `.ets` is a valid suffix for compiler, which is equivalent to `.ets`
350     sourceFileExtension = sourceFileExtension == "ets" ? "ets" : sourceFileExtension;
351     auto tempExtension = WasSetExtension() ? gen::Options::GetExtension() : sourceFileExtension;
352     if (gen::extension::FromString(tempExtension) == ScriptExtension::INVALID) {
353         diagnosticEngine_.LogDiagnostic(diagnostic::UNKNOWN_EXT, DiagnosticMessageParams {});
354         return false;
355     }
356 
357     extension_ = gen::extension::FromString(tempExtension);
358     switch (extension_) {
359 #ifndef PANDA_WITH_ECMASCRIPT
360         case ScriptExtension::JS: {
361             diagnosticEngine_.LogDiagnostic(diagnostic::JS_UNSUPPORTED, util::DiagnosticMessageParams {});
362             return false;
363         }
364 #endif
365         case ScriptExtension::ETS: {
366             std::ifstream inputStream(GetArktsconfig());
367             if (inputStream.fail()) {
368                 diagnosticEngine_.LogDiagnostic(diagnostic::OPEN_FAILED_ARKTSCONF,
369                                                 util::DiagnosticMessageParams {GetArktsconfig()});
370                 return false;
371             }
372             return true;
373         }
374         case ScriptExtension::AS:
375         case ScriptExtension::TS: {
376             diagnosticEngine_.LogDiagnostic(diagnostic::UNKNOWN_EXT, util::DiagnosticMessageParams {});
377             return false;
378         }
379         default:
380             return true;
381     }
382 }
383 
ProcessEtsSpecificOptions()384 bool Options::ProcessEtsSpecificOptions()
385 {
386     if (GetExtension() != ScriptExtension::ETS) {
387         return true;
388     }
389 
390     if (auto config = ParseArktsConfig(); config != std::nullopt) {
391         arktsConfig_ = std::make_shared<ArkTsConfig>(*config);
392         return true;
393     }
394 
395     return false;
396 }
397 
ParseArktsConfig()398 std::optional<ArkTsConfig> Options::ParseArktsConfig()
399 {
400     auto config = ArkTsConfig {GetArktsconfig(), diagnosticEngine_};
401     std::unordered_set<std::string> parsedConfigPath;
402     if (!config.Parse(parsedConfigPath)) {
403         diagnosticEngine_.LogDiagnostic(diagnostic::INVALID_ARKTSCONFIG,
404                                         util::DiagnosticMessageParams {util::StringView(GetArktsconfig())});
405         return std::nullopt;
406     }
407     config.ResolveAllDependenciesInArkTsConfig();
408     // Don't need dependencies anymore, since all necessary information have been moved to current config
409     config.ResetDependencies();
410     return std::make_optional(config);
411 }
412 
413 }  // namespace ark::es2panda::util
414