1 /**
2 * Copyright (c) 2024-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 "importPathManager.h"
17 #include "es2panda.h"
18 #include <libpandabase/os/filesystem.h>
19 #include "util/arktsconfig.h"
20 #include "util/diagnostic.h"
21 #include "util/diagnosticEngine.h"
22 #include "generated/diagnostic.h"
23
24 #include "parser/context/parserContext.h"
25 #include "parser/program/program.h"
26 #include "ir/expressions/literals/stringLiteral.h"
27
28 #ifdef USE_UNIX_SYSCALL
29 #include <dirent.h>
30 #include <sys/types.h>
31 #include <unistd.h>
32 #else
33 #if __has_include(<filesystem>)
34 #include <filesystem>
35 namespace fs = std::filesystem;
36 #elif __has_include(<experimental/filesystem>)
37 #include <experimental/filesystem>
38 namespace fs = std::experimental::filesystem;
39 #endif
40 #endif
41 namespace ark::es2panda::util {
42
43 constexpr size_t SUPPORTED_INDEX_FILES_SIZE = 3;
44 constexpr size_t SUPPORTED_EXTENSIONS_SIZE = 6;
45 constexpr size_t ALLOWED_EXTENSIONS_SIZE = 8;
46
IsCompatibleExtension(const std::string & extension)47 static bool IsCompatibleExtension(const std::string &extension)
48 {
49 return extension == ".ets" || extension == ".ts" || extension == ".sts";
50 }
51
IsAbsolute(const std::string & path)52 static bool IsAbsolute(const std::string &path)
53 {
54 #ifndef ARKTSCONFIG_USE_FILESYSTEM
55 return !path.empty() && path[0] == '/';
56 #else
57 return fs::path(path).is_absolute();
58 #endif // ARKTSCONFIG_USE_FILESYSTEM
59 }
60
GatherImportMetadata(parser::Program * program,ImportFlags importFlags,ir::StringLiteral * importPath)61 ImportPathManager::ImportMetadata ImportPathManager::GatherImportMetadata(parser::Program *program,
62 ImportFlags importFlags,
63 ir::StringLiteral *importPath)
64 {
65 srcPos_ = &importPath->Start();
66 // NOTE(dkofanov): The code below expresses the idea of 'dynamicPaths' defining separated, virtual file system.
67 // Probably, paths of common imports should be isolated from the host fs as well, being resolved by 'ModuleInfo'
68 // instead of 'AbsoluteName'.
69 isDynamic_ = program->ModuleInfo().isDeclForDynamicStaticInterop;
70 auto curModulePath = isDynamic_ ? program->ModuleInfo().moduleName : program->AbsoluteName();
71 auto [resolvedImportPath, resolvedIsDynamic] = ResolvePath(curModulePath.Utf8(), importPath);
72 if (resolvedImportPath.empty()) {
73 ES2PANDA_ASSERT(diagnosticEngine_.IsAnyError());
74 return ImportMetadata {util::ImportFlags::NONE, Language::Id::COUNT, ERROR_LITERAL};
75 }
76
77 globalProgram_->AddFileDependencies(std::string(curModulePath), std::string(resolvedImportPath));
78
79 ImportMetadata importData {importFlags};
80 importData.resolvedSource = resolvedImportPath;
81 if (resolvedIsDynamic) {
82 ES2PANDA_ASSERT(!IsAbsolute(std::string(importData.resolvedSource)));
83 auto it = arktsConfig_->DynamicPaths().find(std::string(importData.resolvedSource));
84 ES2PANDA_ASSERT(it != arktsConfig_->DynamicPaths().cend());
85 const auto &dynImportData = it->second;
86 importData.lang = dynImportData.GetLanguage().GetId();
87 importData.declPath = dynImportData.DeclPath();
88 importData.ohmUrl = dynImportData.OhmUrl();
89 } else {
90 ES2PANDA_ASSERT(IsAbsolute(std::string(importData.resolvedSource)));
91 importData.lang = ToLanguage(program->Extension()).GetId();
92 importData.declPath = util::ImportPathManager::DUMMY_PATH;
93 importData.ohmUrl = util::ImportPathManager::DUMMY_PATH;
94 }
95
96 if (globalProgram_->AbsoluteName() != resolvedImportPath) {
97 AddToParseList(importData);
98 }
99
100 return importData;
101 }
102
IsRelativePath(std::string_view path)103 static bool IsRelativePath(std::string_view path)
104 {
105 std::string currentDirReferenceLinux = "./";
106 std::string parentDirReferenceLinux = "../";
107 std::string currentDirReferenceWindows = ".\\";
108 std::string parentDirReferenceWindows = "..\\";
109
110 return ((path.find(currentDirReferenceLinux) == 0) || (path.find(parentDirReferenceLinux) == 0) ||
111 (path.find(currentDirReferenceWindows) == 0) || (path.find(parentDirReferenceWindows) == 0));
112 }
113
ResolvePathAPI(StringView curModulePath,ir::StringLiteral * importPath) const114 util::StringView ImportPathManager::ResolvePathAPI(StringView curModulePath, ir::StringLiteral *importPath) const
115 {
116 srcPos_ = &importPath->Start();
117 // NOTE(dkofanov): #23698 related. In case of 'dynamicPaths', resolved path is "virtual" module-path, may be not
118 // what the plugin expecting.
119 return ResolvePath(curModulePath.Utf8(), importPath).resolvedPath;
120 }
121
ResolvePath(std::string_view curModulePath,ir::StringLiteral * importPath) const122 ImportPathManager::ResolvedPathRes ImportPathManager::ResolvePath(std::string_view curModulePath,
123 ir::StringLiteral *importPath) const
124 {
125 if (importPath->Str().Empty()) {
126 diagnosticEngine_.LogDiagnostic(diagnostic::EMPTY_IMPORT_PATH, util::DiagnosticMessageParams {});
127 return {*importPath};
128 }
129 const auto &entriesMap = arktsConfig_->Entries();
130 if (auto it = entriesMap.find(importPath->Str().Mutf8()); it != entriesMap.cend()) {
131 return {UString(it->second, allocator_).View().Utf8()};
132 }
133 if (IsRelativePath(*importPath)) {
134 const size_t pos = curModulePath.find_last_of("/\\");
135 ES2PANDA_ASSERT(pos != std::string::npos);
136
137 auto currentDirectory = curModulePath.substr(0, pos);
138 auto resolvedPath = UString(currentDirectory, allocator_);
139 resolvedPath.Append(pathDelimiter_);
140 resolvedPath.Append(*importPath);
141 // NOTE(dkofanov): Suspicious shortcut: shouldn't it fallthrough into `ResolveAbsolutePath`?
142 return AppendExtensionOrIndexFileIfOmitted(resolvedPath.View());
143 }
144
145 return ResolveAbsolutePath(*importPath);
146 }
147
ResolveAbsolutePath(const ir::StringLiteral & importPathNode) const148 ImportPathManager::ResolvedPathRes ImportPathManager::ResolveAbsolutePath(const ir::StringLiteral &importPathNode) const
149 {
150 std::string_view importPath {importPathNode};
151 ES2PANDA_ASSERT(!IsRelativePath(importPath));
152
153 if (importPath.at(0) == pathDelimiter_.at(0)) {
154 std::string baseUrl = arktsConfig_->BaseUrl();
155 baseUrl.append(importPath, 0, importPath.length());
156
157 return AppendExtensionOrIndexFileIfOmitted(UString(baseUrl, allocator_).View());
158 }
159
160 const size_t pos = importPath.find_first_of("/\\");
161 bool containsDelim = (pos != std::string::npos);
162 auto rootPart = containsDelim ? importPath.substr(0, pos) : importPath;
163 if (!stdLib_.empty() &&
164 ((rootPart == "std") || (rootPart == "escompat"))) { // Get std or escompat path from CLI if provided
165 auto baseUrl = std::string(GetRealPath(StringView(stdLib_))) + pathDelimiter_.at(0) + std::string(rootPart);
166
167 if (containsDelim) {
168 baseUrl.append(1, pathDelimiter_.at(0));
169 baseUrl.append(importPath, rootPart.length() + 1, importPath.length());
170 }
171 return {UString(baseUrl, allocator_).View().Utf8()};
172 }
173
174 ES2PANDA_ASSERT(arktsConfig_ != nullptr);
175 auto resolvedPath = arktsConfig_->ResolvePath(importPath, isDynamic_);
176 if (!resolvedPath) {
177 diagnosticEngine_.LogDiagnostic(
178 diagnostic::IMPORT_CANT_FIND_PREFIX,
179 util::DiagnosticMessageParams {util::StringView(importPath), util::StringView(arktsConfig_->ConfigPath())},
180 *srcPos_);
181 return {""};
182 }
183 return AppendExtensionOrIndexFileIfOmitted(UString(resolvedPath.value(), allocator_).View());
184 }
185
186 #ifdef USE_UNIX_SYSCALL
UnixWalkThroughDirectoryAndAddToParseList(const ImportMetadata importMetadata)187 void ImportPathManager::UnixWalkThroughDirectoryAndAddToParseList(const ImportMetadata importMetadata)
188 {
189 const auto directoryPath = std::string(importMetadata.resolvedSource);
190 DIR *dir = opendir(directoryPath.c_str());
191 if (dir == nullptr) {
192 diagnosticEngine_.LogDiagnostic(diagnostic::OPEN_FOLDER_FAILED, util::DiagnosticMessageParams {directoryPath},
193 *srcPos_);
194 return;
195 }
196
197 struct dirent *entry;
198 while ((entry = readdir(dir)) != nullptr) {
199 if (entry->d_type != DT_REG) {
200 continue;
201 }
202
203 std::string fileName = entry->d_name;
204 std::string::size_type pos = fileName.find_last_of('.');
205 if (pos == std::string::npos || !IsCompatibleExtension(fileName.substr(pos))) {
206 continue;
207 }
208
209 std::string filePath = directoryPath + "/" + entry->d_name;
210 auto globElemImportMetadata = importMetadata;
211 globElemImportMetadata.resolvedSource = UString(filePath, allocator_).View().Utf8();
212 AddToParseList(globElemImportMetadata);
213 }
214
215 closedir(dir);
216 return;
217 }
218 #endif
219
AddImplicitPackageImportToParseList(StringView packageDir,const lexer::SourcePosition & srcPos)220 void ImportPathManager::AddImplicitPackageImportToParseList(StringView packageDir, const lexer::SourcePosition &srcPos)
221 {
222 srcPos_ = &srcPos;
223 ES2PANDA_ASSERT(
224 IsAbsolute(packageDir.Mutf8())); // This should be an absolute path for 'AddToParseList' be able to resolve it.
225 AddToParseList({util::ImportFlags::IMPLICIT_PACKAGE_IMPORT, Language::Id::ETS, packageDir.Utf8(),
226 util::ImportPathManager::DUMMY_PATH});
227 }
228
AddToParseList(const ImportMetadata importMetadata)229 void ImportPathManager::AddToParseList(const ImportMetadata importMetadata)
230 {
231 auto resolvedPath = importMetadata.resolvedSource;
232 bool isDeclForDynamic = !IsAbsolute(std::string(resolvedPath)); // Avoiding interpreting dynamic-path as directory.
233 if (!isDeclForDynamic && ark::os::file::File::IsDirectory(std::string(resolvedPath))) {
234 #ifdef USE_UNIX_SYSCALL
235 UnixWalkThroughDirectoryAndAddToParseList(importMetadata);
236 #else
237 for (auto const &entry : fs::directory_iterator(std::string(resolvedPath))) {
238 if (!fs::is_regular_file(entry) || !IsCompatibleExtension(entry.path().extension().string())) {
239 continue;
240 }
241 auto globElemImportMetadata = importMetadata;
242 globElemImportMetadata.resolvedSource = UString(entry.path().string(), allocator_).View().Utf8();
243 AddToParseList(globElemImportMetadata);
244 }
245 return;
246 #endif
247 }
248
249 // Check if file has been already added to parse list
250 if (const auto &found = std::find_if(
251 // CC-OFFNXT(G.FMT.06) project code style
252 parseList_.begin(), parseList_.end(),
253 [&resolvedPath](const ParseInfo &info) { return (info.importData.resolvedSource == resolvedPath); });
254 found != parseList_.end()) {
255 // The 'parseList_' can contain at most 1 record with the same source file path (else it'll break things).
256 //
257 // If a file is added as implicit package imported before, then we may add it again without the implicit import
258 // directive (and remove the other one), to handle when an implicitly package imported file explicitly imports
259 // it. Re-parsing it is necessary, because if the implicitly package imported file contains a syntax error, then
260 // it'll be ignored, but we must not ignore it if an explicitly imported file contains a parse error. Also this
261 // addition can happen during parsing the files in the parse list, so re-addition is necessary in order to
262 // surely re-parse it.
263 //
264 // If a file was already not implicitly package imported, then it's just a duplicate, return
265 if (!found->importData.IsImplicitPackageImported() || importMetadata.IsImplicitPackageImported()) {
266 return;
267 }
268
269 parseList_.erase(found);
270 }
271
272 if (!isDeclForDynamic && !ark::os::file::File::IsRegularFile(std::string(resolvedPath))) {
273 diagnosticEngine_.LogDiagnostic(diagnostic::UNAVAILABLE_SRC_PATH, util::DiagnosticMessageParams {resolvedPath},
274 *srcPos_);
275 return;
276 }
277
278 // 'Object.ets' must be the first in the parse list
279 // NOTE (mmartin): still must be the first?
280 const std::size_t position = resolvedPath.find_last_of("/\\");
281 const bool isDefaultImport = (importMetadata.importFlags & ImportFlags::DEFAULT_IMPORT) != 0;
282 const auto parseInfo = ParseInfo {false, importMetadata};
283 if (isDefaultImport && (resolvedPath.substr(position + 1, resolvedPath.length()) == "Object.ets")) {
284 parseList_.emplace(parseList_.begin(), parseInfo);
285 } else {
286 parseList_.emplace_back(parseInfo);
287 }
288 }
289
MarkAsParsed(StringView path)290 void ImportPathManager::MarkAsParsed(StringView path)
291 {
292 for (auto &parseInfo : parseList_) {
293 if (parseInfo.importData.resolvedSource == path.Utf8()) {
294 parseInfo.isParsed = true;
295 return;
296 }
297 }
298 }
299
GetRealPath(StringView path) const300 StringView ImportPathManager::GetRealPath(StringView path) const
301 {
302 const std::string realPath = ark::os::GetAbsolutePath(path.Mutf8());
303 if (realPath.empty() || realPath == path.Mutf8()) {
304 return path;
305 }
306
307 return UString(realPath, allocator_).View();
308 }
309
TryMatchDynamicPath(std::string_view fixedPath) const310 std::string ImportPathManager::TryMatchDynamicPath(std::string_view fixedPath) const
311 {
312 // Probably, 'NormalizePath' should be moved to 'AppendExtensionOrIndexFileIfOmitted'.
313 auto normalizedPath = ark::os::NormalizePath(std::string(fixedPath));
314 std::replace_if(
315 normalizedPath.begin(), normalizedPath.end(), [&](auto &c) { return c == pathDelimiter_[0]; }, '/');
316 // NOTE(dkofanov): #23877. See also 'arktsconfig.cpp'.
317 if (arktsConfig_->DynamicPaths().find(normalizedPath) != arktsConfig_->DynamicPaths().cend()) {
318 return normalizedPath;
319 }
320 return {};
321 }
322
DirOrDirWithIndexFile(StringView dir) const323 std::string_view ImportPathManager::DirOrDirWithIndexFile(StringView dir) const
324 {
325 // Supported index files: keep this checking order
326 std::array<std::string, SUPPORTED_INDEX_FILES_SIZE> supportedIndexFiles = {"index.ets", "index.sts", "index.ts"};
327 for (const auto &indexFile : supportedIndexFiles) {
328 std::string indexFilePath = dir.Mutf8() + ark::os::file::File::GetPathDelim().at(0) + indexFile;
329 if (ark::os::file::File::IsRegularFile(indexFilePath)) {
330 return GetRealPath(UString(indexFilePath, allocator_).View()).Utf8();
331 }
332 }
333
334 return dir.Utf8();
335 }
336 // NOTE(dkofanov): Be cautious: potentially no-op and may retrun the input string view. Make sure 'basePath' won't go
337 // out of scope.
AppendExtensionOrIndexFileIfOmitted(StringView basePath) const338 ImportPathManager::ResolvedPathRes ImportPathManager::AppendExtensionOrIndexFileIfOmitted(StringView basePath) const
339 {
340 auto fixedPath = basePath.Mutf8();
341 char delim = pathDelimiter_.at(0);
342 std::replace_if(
343 fixedPath.begin(), fixedPath.end(), [&](auto &c) { return ((delim != c) && ((c == '\\') || (c == '/'))); },
344 delim);
345 if (auto resolvedDynamic = TryMatchDynamicPath(fixedPath); !resolvedDynamic.empty()) {
346 return {UString(resolvedDynamic, allocator_).View().Utf8(), true};
347 }
348
349 auto path = UString(fixedPath, allocator_).View();
350 StringView realPath = GetRealPath(path);
351 if (ark::os::file::File::IsRegularFile(realPath.Mutf8())) {
352 return {realPath.Utf8()};
353 }
354
355 if (ark::os::file::File::IsDirectory(realPath.Mutf8())) {
356 return {DirOrDirWithIndexFile(realPath)};
357 }
358
359 // Supported extensions: keep this checking order, and header files should follow source files
360 std::array<std::string, SUPPORTED_EXTENSIONS_SIZE> supportedExtensions = {".ets", ".d.ets", ".sts",
361 ".d.sts", ".ts", ".d.ts"};
362 for (const auto &extension : supportedExtensions) {
363 if (ark::os::file::File::IsRegularFile(path.Mutf8() + extension)) {
364 return {GetRealPath(UString(path.Mutf8().append(extension), allocator_).View()).Utf8()};
365 }
366 }
367
368 diagnosticEngine_.LogDiagnostic(diagnostic::UNSUPPORTED_PATH,
369 util::DiagnosticMessageParams {util::StringView(path.Mutf8())}, *srcPos_);
370 return {""};
371 }
372
FormUnitName(std::string_view name)373 static std::string FormUnitName(std::string_view name)
374 {
375 // this policy may change
376 return std::string(name);
377 }
378
379 // Transform /a/b/c.ets to a.b.c
FormRelativeModuleName(std::string relPath)380 static std::string FormRelativeModuleName(std::string relPath)
381 {
382 bool isMatched = false;
383 // Supported extensions: keep this checking order, and source files should follow header files
384 std::array<std::string, ALLOWED_EXTENSIONS_SIZE> supportedExtensionsDesc = {".d.ets", ".ets", ".d.sts", ".sts",
385 ".d.ts", ".ts", ".js", ".abc"};
386 for (const auto &ext : supportedExtensionsDesc) {
387 if (relPath.size() >= ext.size() && relPath.compare(relPath.size() - ext.size(), ext.size(), ext) == 0) {
388 relPath = relPath.substr(0, relPath.size() - ext.size());
389 isMatched = true;
390 break;
391 }
392 }
393 if (relPath.empty()) {
394 return "";
395 }
396
397 if (!isMatched) {
398 ASSERT_PRINT(false, "Invalid relative filename: " + relPath);
399 }
400 while (relPath[0] == util::PATH_DELIMITER) {
401 relPath = relPath.substr(1);
402 }
403 std::replace(relPath.begin(), relPath.end(), util::PATH_DELIMITER, '.');
404 return relPath;
405 }
406
FormModuleNameSolelyByAbsolutePath(const util::Path & path)407 util::StringView ImportPathManager::FormModuleNameSolelyByAbsolutePath(const util::Path &path)
408 {
409 std::string filePath(path.GetAbsolutePath());
410 if (filePath.rfind(absoluteEtsPath_, 0) != 0) {
411 diagnosticEngine_.LogDiagnostic(diagnostic::SOURCE_OUTSIDE_ETS_PATH,
412 util::DiagnosticMessageParams {util::StringView(filePath)}, *srcPos_);
413 return "";
414 }
415 auto name = FormRelativeModuleName(filePath.substr(absoluteEtsPath_.size()));
416 return util::UString(name, allocator_).View();
417 }
418
419 template <typename DynamicPaths, typename ModuleNameFormer>
TryFormDynamicModuleName(const DynamicPaths & dynPaths,const ModuleNameFormer & tryFormModuleName)420 static std::string TryFormDynamicModuleName(const DynamicPaths &dynPaths, const ModuleNameFormer &tryFormModuleName)
421 {
422 for (auto const &[unitName, did] : dynPaths) {
423 if (did.DeclPath().empty()) {
424 // NOTE(dkofanov): related to #23698. Current assumption: if 'declPath' is absent, it is a pure-dynamic
425 // source, and, as soon it won't be parsed, no module should be created.
426 continue;
427 }
428 if (auto res = tryFormModuleName(unitName, did.DeclPath()); res) {
429 return res.value();
430 }
431 }
432 return "";
433 }
434
FormModuleName(const util::Path & path,const lexer::SourcePosition & srcPos)435 util::StringView ImportPathManager::FormModuleName(const util::Path &path, const lexer::SourcePosition &srcPos)
436 {
437 srcPos_ = &srcPos;
438 return FormModuleName(path);
439 }
440
FormModuleName(const util::Path & path)441 util::StringView ImportPathManager::FormModuleName(const util::Path &path)
442 {
443 if (!absoluteEtsPath_.empty()) {
444 return FormModuleNameSolelyByAbsolutePath(path);
445 }
446
447 if (arktsConfig_->Package().empty() && !arktsConfig_->UseUrl()) {
448 return path.GetFileName();
449 }
450
451 std::string const filePath(path.GetAbsolutePath());
452
453 // should be implemented with a stable name -> path mapping list
454 auto const tryFormModuleName = [filePath](std::string_view unitName,
455 std::string_view unitPath) -> std::optional<std::string> {
456 if (filePath.rfind(unitPath, 0) != 0) {
457 return std::nullopt;
458 }
459 auto relativePath = FormRelativeModuleName(filePath.substr(unitPath.size()));
460 return FormUnitName(unitName) +
461 (relativePath.empty() || FormUnitName(unitName).empty() ? relativePath : ("." + relativePath));
462 };
463
464 for (auto const &[unitName, unitPath] : arktsConfig_->Entries()) {
465 if (unitPath == filePath) {
466 return util::UString(unitName, allocator_).View();
467 }
468 }
469
470 if (auto res = tryFormModuleName(arktsConfig_->Package(), arktsConfig_->BaseUrl() + pathDelimiter_.data()); res) {
471 return util::UString(res.value(), allocator_).View();
472 }
473 if (!stdLib_.empty()) {
474 if (auto res = tryFormModuleName("std", stdLib_ + pathDelimiter_.at(0) + "std"); res) {
475 return util::UString(res.value(), allocator_).View();
476 }
477 if (auto res = tryFormModuleName("escompat", stdLib_ + pathDelimiter_.at(0) + "escompat"); res) {
478 return util::UString(res.value(), allocator_).View();
479 }
480 }
481 for (auto const &[unitName, unitPath] : arktsConfig_->Paths()) {
482 if (auto res = tryFormModuleName(unitName, unitPath[0]); res) {
483 return util::UString(res.value(), allocator_).View();
484 }
485 }
486 if (auto dmn = TryFormDynamicModuleName(arktsConfig_->DynamicPaths(), tryFormModuleName); !dmn.empty()) {
487 return util::UString(dmn, allocator_).View();
488 }
489 // NOTE (hurton): as a last step, try resolving using the BaseUrl again without a path delimiter at the end
490 if (auto res = tryFormModuleName(arktsConfig_->Package(), arktsConfig_->BaseUrl()); res) {
491 return util::UString(res.value(), allocator_).View();
492 }
493
494 diagnosticEngine_.LogDiagnostic(diagnostic::UNRESOLVED_MODULE,
495 util::DiagnosticMessageParams {util::StringView(filePath)}, *srcPos_);
496 return "";
497 }
498
IsValid() const499 bool ImportPathManager::ImportMetadata::IsValid() const
500 {
501 return resolvedSource != ERROR_LITERAL;
502 }
503
FormRelativePath(const util::Path & path)504 util::StringView ImportPathManager::FormRelativePath(const util::Path &path)
505 {
506 std::string filePath(path.GetAbsolutePath());
507 auto const tryFormRelativePath = [&filePath](std::string const &basePath,
508 std::string const &prefix) -> std::optional<std::string> {
509 if (filePath.rfind(basePath, 0) != 0) {
510 return std::nullopt;
511 }
512 return filePath.replace(0, basePath.size(), prefix);
513 };
514
515 if (!absoluteEtsPath_.empty()) {
516 if (auto res = tryFormRelativePath(absoluteEtsPath_ + pathDelimiter_.data(), ""); res) {
517 return util::UString(res.value(), allocator_).View();
518 }
519 }
520
521 if (arktsConfig_->Package().empty() && !arktsConfig_->UseUrl()) {
522 return path.GetFileNameWithExtension();
523 }
524
525 for (auto const &[unitName, unitPath] : arktsConfig_->Entries()) {
526 if (unitPath == filePath) {
527 return util::UString(unitName, allocator_).View();
528 }
529 }
530
531 if (auto res = tryFormRelativePath(arktsConfig_->BaseUrl(), arktsConfig_->Package()); res) {
532 return util::UString(res.value(), allocator_).View();
533 }
534
535 for (auto const &[unitName, unitPath] : arktsConfig_->Paths()) {
536 if (auto res = tryFormRelativePath(unitPath[0], unitName); res) {
537 return util::UString(res.value(), allocator_).View();
538 }
539 }
540
541 for (auto const &[unitName, unitPath] : arktsConfig_->DynamicPaths()) {
542 if (auto res = tryFormRelativePath(unitName, unitName); res) {
543 return util::UString(res.value(), allocator_).View();
544 }
545 }
546
547 return path.GetFileNameWithExtension();
548 }
549
550 } // namespace ark::es2panda::util
551 #undef USE_UNIX_SYSCALL
552