• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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