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