• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2023-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 "arktsconfig.h"
17 #include "libpandabase/os/filesystem.h"
18 #include "util/language.h"
19 #include "util/diagnosticEngine.h"
20 #include "generated/signatures.h"
21 
22 #include <fstream>
23 #include <memory>
24 #include <regex>
25 #include <sstream>
26 #include <system_error>
27 
28 namespace ark::es2panda {
29 
Check(bool cond,const diagnostic::DiagnosticKind & diag,const util::DiagnosticMessageParams & params)30 bool ArkTsConfig::Check(bool cond, const diagnostic::DiagnosticKind &diag, const util::DiagnosticMessageParams &params)
31 {
32     if (!cond) {
33         diagnosticEngine_.LogDiagnostic(diag, params);
34         return false;
35     }
36     return true;
37 }
38 
IsAbsolute(const std::string & path)39 static bool IsAbsolute(const std::string &path)
40 {
41 #ifndef ARKTSCONFIG_USE_FILESYSTEM
42     return !path.empty() && path[0] == '/';
43 #else
44     return fs::path(path).is_absolute();
45 #endif  // ARKTSCONFIG_USE_FILESYSTEM
46 }
47 
48 #ifndef ARKTSCONFIG_USE_FILESYSTEM
49 #else
NormalizePath(const fs::path & p)50 fs::path NormalizePath(const fs::path &p)
51 {
52     fs::path result;
53     for (const auto &part : p) {
54         if (part == ".") {
55             continue;
56         }
57         if (part == "..") {
58             if (!result.empty() && result.filename() != "..") {
59                 result = result.parent_path();
60             } else {
61                 result /= part;
62             }
63         } else {
64             result /= part;
65         }
66     }
67     if (fs::exists(result)) {
68         return fs::canonical(result);
69     }
70     return result;
71 }
72 #endif  // ARKTSCONFIG_USE_FILESYSTEM
73 
JoinPaths(const std::string & a,const std::string & b)74 std::string JoinPaths(const std::string &a, const std::string &b)
75 {
76 #ifndef ARKTSCONFIG_USE_FILESYSTEM
77     return a + '/' + b;
78 #else
79     return NormalizePath(fs::path(a) / b).string();
80 #endif  // ARKTSCONFIG_USE_FILESYSTEM
81 }
82 
ParentPath(const std::string & path)83 std::string ParentPath(const std::string &path)
84 {
85 #ifndef ARKTSCONFIG_USE_FILESYSTEM
86     auto pos = path.find('/');
87     return pos == std::string::npos ? path : path.substr(0, pos);
88 #else
89     return fs::path(path).parent_path().string();
90 #endif  // ARKTSCONFIG_USE_FILESYSTEM
91 }
92 
MakeAbsolute(const std::string & path,const std::string & base)93 static std::string MakeAbsolute(const std::string &path, const std::string &base)
94 {
95     return IsAbsolute(path) ? path : JoinPaths(base, path);
96 }
97 
98 #ifdef ARKTSCONFIG_USE_FILESYSTEM
99 
Pattern(std::string value,std::string base)100 ArkTsConfig::Pattern::Pattern(std::string value, std::string base) : value_(std::move(value)), base_(std::move(base))
101 {
102     ES2PANDA_ASSERT(fs::path(base_).is_absolute());
103 }
104 
IsPattern() const105 bool ArkTsConfig::Pattern::IsPattern() const
106 {
107     return (value_.find('*') != std::string::npos) || (value_.find('?') != std::string::npos);
108 }
109 
GetSearchRoot() const110 std::string ArkTsConfig::Pattern::GetSearchRoot() const
111 {
112     fs::path relative;
113     if (!IsPattern()) {
114         relative = value_;
115     } else {
116         auto foundStar = value_.find_first_of('*');
117         auto foundQuestion = value_.find_first_of('?');
118         relative = value_.substr(0, std::min(foundStar, foundQuestion));
119         relative = relative.parent_path();
120     }
121     return MakeAbsolute(relative.string(), base_);
122 }
123 
Match(const std::string & path) const124 bool ArkTsConfig::Pattern::Match(const std::string &path) const
125 {
126     ES2PANDA_ASSERT(fs::path(path).is_absolute());
127     fs::path value = fs::path(value_);
128     std::string pattern = value.is_absolute() ? value.string() : (base_ / value).string();
129 
130     // Replace arktsconfig special symbols with regular expressions
131     if (IsPattern()) {
132         // '**' matches any directory nested to any level
133         pattern = std::regex_replace(pattern, std::regex("\\*\\*/"), ".*");
134         // '*' matches zero or more characters (excluding directory separators)
135         pattern = std::regex_replace(pattern, std::regex("([^\\.])\\*"), "$1[^/]*");
136         // '?' matches any one character (excluding directory separators)
137         pattern = std::regex_replace(pattern, std::regex("\\?"), "[^/]");
138         // './src' -> 'src'
139         pattern = std::regex_replace(pattern, std::regex("\\.\\/"), "");
140         // '[^/]*.' -> '[^/]*\.'
141         pattern = std::regex_replace(pattern, std::regex(R"(\[\^\/\]\*\.)"), "[^/]*\\.");
142     }
143     if (!value.has_extension()) {
144         // default extensions to match
145         pattern += R"(.*(\.ts|\.ets|\.sts)$)";
146     }
147     std::smatch m;
148     auto res = std::regex_match(path, m, std::regex(pattern));
149     return res;
150 }
151 
ResolveConfigLocation(const std::string & relPath,const std::string & base)152 static std::string ResolveConfigLocation(const std::string &relPath, const std::string &base)
153 {
154     auto resolvedPath = MakeAbsolute(relPath, base);
155     auto newBase = base;
156     while (!fs::exists(resolvedPath)) {
157         resolvedPath = MakeAbsolute(relPath, JoinPaths(newBase, "node_modules"));
158         if (newBase == ParentPath(newBase)) {
159             return "";
160         }
161         newBase = ParentPath(newBase);
162     }
163     return resolvedPath;
164 }
165 
ParseExtends(const std::string & configPath,const std::string & extends,const std::string & configDir,std::unordered_set<std::string> & parsedConfigPath)166 std::optional<ArkTsConfig> ArkTsConfig::ParseExtends(const std::string &configPath, const std::string &extends,
167                                                      const std::string &configDir,
168                                                      std::unordered_set<std::string> &parsedConfigPath)
169 {
170     auto basePath = ResolveConfigLocation(extends, configDir);
171     if (!Check(!basePath.empty(), diagnostic::UNRESOLVABLE_CONFIG_PATH, {extends})) {
172         return {};
173     }
174 
175     if (!Check(basePath != configPath, diagnostic::CYCLIC_IMPORT, {})) {
176         return {};
177     }
178 
179     auto base = ArkTsConfig(basePath, diagnosticEngine_);
180     if (!Check(base.Parse(parsedConfigPath), diagnostic::WRONG_BASE_CONFIG, {extends})) {
181         return {};
182     }
183 
184     return base;
185 }
186 #endif  // ARKTSCONFIG_USE_FILESYSTEM
187 
ValidDynamicLanguages()188 static std::string ValidDynamicLanguages()
189 {
190     JsonArrayBuilder builder;
191     for (auto &l : Language::All()) {
192         if (l.IsDynamic()) {
193             builder.Add(l.ToString());
194         }
195     }
196     return std::move(builder).Build();
197 }
198 
ParsePaths(const JsonObject::JsonObjPointer * options,PathsMap & pathsMap,const std::string & baseUrl)199 bool ArkTsConfig::ParsePaths(const JsonObject::JsonObjPointer *options, PathsMap &pathsMap, const std::string &baseUrl)
200 {
201     auto paths = options->get()->GetValue<JsonObject::JsonObjPointer>("paths");
202     if (paths == nullptr) {
203         return true;
204     }
205 
206     for (size_t keyIdx = 0; keyIdx < paths->get()->GetSize(); ++keyIdx) {
207         auto &key = paths->get()->GetKeyByIndex(keyIdx);
208         if (pathsMap.count(key) == 0U) {
209             pathsMap.insert({key, {}});
210         }
211 
212         auto values = paths->get()->GetValue<JsonObject::ArrayT>(key);
213         if (!Check(values != nullptr, diagnostic::INVALID_VALUE, {"path", key})) {
214             return false;
215         }
216 
217         if (!Check(!values->empty(), diagnostic::EMPTY_ARRAY_SUBSTITOTIONS, {key})) {
218             return false;
219         }
220 
221         for (auto &v : *values) {
222             auto p = *v.Get<JsonObject::StringT>();
223             pathsMap[key].emplace_back(MakeAbsolute(p, baseUrl));
224         }
225     }
226 
227     return true;
228 }
229 
230 static constexpr auto LANGUAGE = "language";   // CC-OFF(G.NAM.03-CPP) project code style
231 static constexpr auto DECL_PATH = "declPath";  // CC-OFF(G.NAM.03-CPP) project code style
232 static constexpr auto OHM_URL = "ohmUrl";      // CC-OFF(G.NAM.03-CPP) project code style
233 
ParseDynamicPaths(const JsonObject::JsonObjPointer * options,std::map<std::string,DynamicImportData,CompareByLength> & dynamicPathsMap,const std::string & baseUrl)234 bool ArkTsConfig::ParseDynamicPaths(const JsonObject::JsonObjPointer *options,
235                                     std::map<std::string, DynamicImportData, CompareByLength> &dynamicPathsMap,
236                                     const std::string &baseUrl)
237 {
238     if (options == nullptr) {
239         return true;
240     }
241     auto dynamicPaths = options->get()->GetValue<JsonObject::JsonObjPointer>("dynamicPaths");
242     if (dynamicPaths == nullptr) {
243         return true;
244     }
245     for (size_t keyIdx = 0; keyIdx < dynamicPaths->get()->GetSize(); ++keyIdx) {
246         auto &key = dynamicPaths->get()->GetKeyByIndex(keyIdx);
247         auto data = dynamicPaths->get()->GetValue<JsonObject::JsonObjPointer>(key);
248         if (!ParseSingleDynamicPath(key, data, dynamicPathsMap, baseUrl)) {
249             return false;
250         }
251     }
252     return true;
253 }
254 
ParseSingleDynamicPath(const std::string & key,const JsonObject::JsonObjPointer * data,std::map<std::string,DynamicImportData,CompareByLength> & dynamicPathsMap,const std::string & baseUrl)255 bool ArkTsConfig::ParseSingleDynamicPath(const std::string &key, const JsonObject::JsonObjPointer *data,
256                                          std::map<std::string, DynamicImportData, CompareByLength> &dynamicPathsMap,
257                                          const std::string &baseUrl)
258 {
259     if (IsAbsolute(key)) {
260         diagnosticEngine_.LogDiagnostic(diagnostic::DYNAMIC_PATHS_ABSOLUTE, util::DiagnosticMessageParams {key});
261     }
262     if (!Check(data != nullptr, diagnostic::INVALID_VALUE, {"dynamic path", key})) {
263         return false;
264     }
265     auto langValue = data->get()->GetValue<JsonObject::StringT>(LANGUAGE);
266     if (!Check(langValue != nullptr, diagnostic::INVALID_LANGUAGE, {LANGUAGE, key, ValidDynamicLanguages()})) {
267         return false;
268     }
269     auto lang = Language::FromString(*langValue);
270     if (!Check(lang && lang->IsDynamic(), diagnostic::INVALID_LANGUAGE, {LANGUAGE, key, ValidDynamicLanguages()})) {
271         return false;
272     }
273     auto isSupportLang = compiler::Signatures::Dynamic::IsSupported(*lang);
274     if (!Check(isSupportLang, diagnostic::UNSUPPORTED_LANGUAGE_FOR_INTEROP, {lang->ToString()})) {
275         return false;
276     }
277     auto ohmUrl = data->get()->GetValue<JsonObject::StringT>(OHM_URL);
278     if (ohmUrl == nullptr) {
279         diagnosticEngine_.LogDiagnostic(diagnostic::NO_OHMURL, util::DiagnosticMessageParams {key});
280     }
281     std::string ohmUrlValue = (ohmUrl == nullptr) ? "" : *ohmUrl;
282     auto declPathValue = data->get()->GetValue<JsonObject::StringT>(DECL_PATH);
283     std::string normalizedDeclPath {};
284     if (declPathValue != nullptr) {
285         normalizedDeclPath = IsAbsolute(*declPathValue) ? ark::os::GetAbsolutePath(*declPathValue)
286                                                         : MakeAbsolute(*declPathValue, baseUrl);
287         if (!Check(ark::os::IsFileExists(normalizedDeclPath), diagnostic::INVALID_DYNAMIC_PATH, {key})) {
288             return false;
289         }
290     }
291     auto res = dynamicPathsMap.insert(
292         {ark::os::NormalizePath(key), ArkTsConfig::DynamicImportData(*lang, normalizedDeclPath, ohmUrlValue)});
293     return Check(res.second, diagnostic::DUPLICATED_DYNAMIC_PATH, {normalizedDeclPath, key});
294 }
295 
296 template <class Collection, class Function>
ParseCollection(const JsonObject * config,Collection & out,const std::string & target,Function && constructor)297 bool ArkTsConfig::ParseCollection(const JsonObject *config, Collection &out, const std::string &target,
298                                   Function &&constructor)
299 {
300     auto *arr = config->GetValue<JsonObject::ArrayT>(target);
301     if (!Check(arr != nullptr, diagnostic::INVALID_JSON_TYPE, {target, "array"})) {
302         return false;
303     }
304 
305     out = {};
306     if (!Check(!arr->empty(), diagnostic::EMPTY_LIST, {target})) {
307         return false;
308     }
309 
310     for (auto &i : *arr) {
311         out.emplace_back(constructor(*i.Get<JsonObject::StringT>()));
312     }
313 
314     return true;
315 }
316 
ResolveConfigDependencies(std::unordered_map<std::string,std::shared_ptr<ArkTsConfig>> & dependencies,std::vector<std::string> & dependencyPaths,std::unordered_set<std::string> & parsedConfigPath)317 void ArkTsConfig::ResolveConfigDependencies(std::unordered_map<std::string, std::shared_ptr<ArkTsConfig>> &dependencies,
318                                             std::vector<std::string> &dependencyPaths,
319                                             std::unordered_set<std::string> &parsedConfigPath)
320 {
321     for (auto dependency : dependencyPaths) {
322         auto config = std::make_shared<ArkTsConfig>(std::string_view(dependency), diagnosticEngine_);
323         if (!Check(config->Parse(parsedConfigPath), diagnostic::EMPTY_LIST, {dependency})) {
324             continue;
325         }
326         dependencies.emplace(config->Package(), std::move(config));
327     }
328 }
329 
ReadConfig(const std::string & path)330 std::optional<std::string> ArkTsConfig::ReadConfig(const std::string &path)
331 {
332     std::ifstream inputStream(path);
333     if (!Check(!inputStream.fail(), diagnostic::FAILED_TO_OPEN_FILE, {path})) {
334         return {};
335     }
336 
337     std::stringstream ss;
338     ss << inputStream.rdbuf();
339     return ss.str();
340 }
341 
ValueOrEmptyString(const JsonObject::JsonObjPointer * json,const std::string & key)342 static std::string ValueOrEmptyString(const JsonObject::JsonObjPointer *json, const std::string &key)
343 {
344     ES2PANDA_ASSERT(json != nullptr);
345     auto res = json->get()->GetValue<JsonObject::StringT>(key);
346     return (res != nullptr) ? *res : "";
347 }
348 
ResolvePathInDependenciesImpl(ArkTsConfig * arktsConfig,std::map<std::string,std::vector<std::string>,CompareByLength> & paths,std::unordered_map<std::string,std::string> & entries)349 static void ResolvePathInDependenciesImpl(ArkTsConfig *arktsConfig,
350                                           std::map<std::string, std::vector<std::string>, CompareByLength> &paths,
351                                           std::unordered_map<std::string, std::string> &entries)
352 {
353     for (const auto &dependencyPath : arktsConfig->Paths()) {
354         paths.emplace(dependencyPath.first, dependencyPath.second);
355     }
356     if (!arktsConfig->Entry().empty()) {
357         entries.emplace(arktsConfig->Package(), arktsConfig->Entry());
358     }
359     for (const auto &config : arktsConfig->Dependencies()) {
360         ResolvePathInDependenciesImpl(config.second.get(), paths, entries);
361     }
362 }
363 
ResolveAllDependenciesInArkTsConfig()364 void ArkTsConfig::ResolveAllDependenciesInArkTsConfig()
365 {
366     ResolvePathInDependenciesImpl(this, paths_, entries_);
367 }
368 
ParseCompilerOptions(std::string & arktsConfigDir,std::unordered_set<std::string> & parsedConfigPath,const JsonObject * arktsConfig)369 bool ArkTsConfig::ParseCompilerOptions(std::string &arktsConfigDir, std::unordered_set<std::string> &parsedConfigPath,
370                                        const JsonObject *arktsConfig)
371 {
372     auto compilerOptions = arktsConfig->GetValue<JsonObject::JsonObjPointer>(COMPILER_OPTIONS);
373     // Parse "package"
374     package_ = ValueOrEmptyString(compilerOptions, PACKAGE);
375 
376     // Parse "baseUrl", "outDir", "rootDir"
377     baseUrl_ = MakeAbsolute(ValueOrEmptyString(compilerOptions, BASE_URL), arktsConfigDir);
378     outDir_ = MakeAbsolute(ValueOrEmptyString(compilerOptions, OUT_DIR), arktsConfigDir);
379     rootDir_ = MakeAbsolute(ValueOrEmptyString(compilerOptions, ROOT_DIR), arktsConfigDir);
380 
381     // Parse "entry"
382     if (compilerOptions->get()->HasKey(ENTRY)) {
383         entry_ = MakeAbsolute(ValueOrEmptyString(compilerOptions, ENTRY), baseUrl_);
384     }
385 
386     // Parse "useUrl"
387     if (compilerOptions->get()->HasKey(USE_EMPTY_PACKAGE)) {
388         auto *useUrl = compilerOptions->get()->GetValue<JsonObject::BoolT>(USE_EMPTY_PACKAGE);
389         ES2PANDA_ASSERT(useUrl != nullptr);
390         useUrl_ = *useUrl;
391     }
392 
393     // Parse "dependencies"
394     auto concatPath = [this](const auto &val) { return MakeAbsolute(val, baseUrl_); };
395     std::vector<std::string> dependencyPaths;
396     if (compilerOptions->get()->HasKey(DEPENDENCIES)) {
397         ParseCollection(compilerOptions->get(), dependencyPaths, DEPENDENCIES, concatPath);
398     }
399     if (!dependencyPaths.empty()) {
400         ResolveConfigDependencies(dependencies_, dependencyPaths, parsedConfigPath);
401     }
402     // Parse "paths"
403     if (!ParsePaths(compilerOptions, paths_, baseUrl_)) {
404         return false;
405     }
406     // Parse "dynamicPaths"
407     if (!ParseDynamicPaths(compilerOptions, dynamicPaths_, baseUrl_)) {
408         return false;
409     }
410     return true;
411 }
412 
413 // CC-OFFNXT(huge_method[C++], G.FUN.01-CPP, G.FUD.05) solid logic
Parse(std::unordered_set<std::string> & parsedConfigPath)414 bool ArkTsConfig::Parse(std::unordered_set<std::string> &parsedConfigPath)
415 {
416     // For circurlar dependencies, just skip parsing
417     if (parsedConfigPath.find(configPath_) != parsedConfigPath.end()) {
418         return true;
419     }
420     parsedConfigPath.emplace(configPath_);
421 
422     ES2PANDA_ASSERT(!isParsed_);
423     isParsed_ = true;
424     auto arktsConfigDir = ParentPath(ark::os::GetAbsolutePath(configPath_));
425 
426     // Read input
427     auto tsConfigSource = ReadConfig(configPath_);
428     if (!Check(tsConfigSource.has_value(), diagnostic::UNRESOLVABLE_CONFIG_PATH, {configPath_})) {
429         return false;
430     }
431 
432     // Parse json
433     auto arktsConfig = std::make_unique<JsonObject>(*tsConfigSource);
434     if (!Check(arktsConfig->IsValid(), diagnostic::INVALID_JSON, {})) {
435         return false;
436     }
437 
438 #ifdef ARKTSCONFIG_USE_FILESYSTEM
439     // Parse "extends"
440     if (arktsConfig->HasKey(EXTENDS)) {
441         auto *extends = arktsConfig->GetValue<JsonObject::StringT>(EXTENDS);
442         if (!Check(extends != nullptr, diagnostic::INVALID_JSON_TYPE, {EXTENDS, "string"})) {
443             return false;
444         }
445         const auto &base = ParseExtends(configPath_, *extends, arktsConfigDir, parsedConfigPath);
446         if (!base.has_value()) {
447             return false;
448         }
449         Inherit(*base);
450     }
451 #endif  // ARKTSCONFIG_USE_FILESYSTEM
452 
453     // Parse "compilerOptions"
454     if (arktsConfig->HasKey(COMPILER_OPTIONS) &&
455         !ParseCompilerOptions(arktsConfigDir, parsedConfigPath, arktsConfig.get())) {
456         return false;
457     }
458 
459     // Parse "files"
460     auto concatPath = [&arktsConfigDir](const auto &val) { return MakeAbsolute(val, arktsConfigDir); };
461     if (arktsConfig->HasKey(FILES) && !ParseCollection(arktsConfig.get(), files_, FILES, concatPath)) {
462         return false;
463     }
464 
465 #ifdef ARKTSCONFIG_USE_FILESYSTEM
466     // Parse "include" and "exclude"
467     auto consPattern = [&arktsConfigDir](const auto &val) { return Pattern {val, arktsConfigDir}; };
468     if (arktsConfig->HasKey(INCLUDE) && !ParseCollection(arktsConfig.get(), include_, INCLUDE, consPattern)) {
469         return false;
470     }
471     if (arktsConfig->HasKey(EXCLUDE) && !ParseCollection(arktsConfig.get(), exclude_, EXCLUDE, consPattern)) {
472         return false;
473     }
474 #endif  // ARKTSCONFIG_USE_FILESYSTEM
475 
476     return true;
477 }
478 
Inherit(const ArkTsConfig & base)479 void ArkTsConfig::Inherit(const ArkTsConfig &base)
480 {
481     package_ = base.package_;
482     baseUrl_ = base.baseUrl_;
483     outDir_ = base.outDir_;
484     rootDir_ = base.rootDir_;
485     paths_ = base.paths_;
486     files_ = base.files_;
487 #ifdef ARKTSCONFIG_USE_FILESYSTEM
488     include_ = base.include_;
489     exclude_ = base.exclude_;
490 #endif  // ARKTSCONFIG_USE_FILESYSTEM
491 }
492 
493 // Remove '/' and '*' from the end of path
TrimPath(const std::string & path)494 static std::string TrimPath(const std::string &path)
495 {
496     std::string trimmedPath = path;
497     while (!trimmedPath.empty() && (trimmedPath.back() == '*' || trimmedPath.back() == '/')) {
498         trimmedPath.pop_back();
499     }
500     return trimmedPath;
501 }
502 
ResolvePath(std::string_view path,bool isDynamic) const503 std::optional<std::string> ArkTsConfig::ResolvePath(std::string_view path, bool isDynamic) const
504 {
505     auto tryResolveWithPaths = [this, &path]() -> std::optional<std::string> {
506         for (const auto &[alias, paths] : paths_) {
507             auto trimmedAlias = TrimPath(alias);
508             size_t pos = path.rfind(trimmedAlias, 0);
509             if (pos == 0) {
510                 auto resolved = std::string(path);
511                 // NOTE(ivagin): arktsconfig contains array of paths for each prefix, for now just get first one
512                 std::string newPrefix = TrimPath(paths[0]);
513                 resolved.replace(pos, trimmedAlias.length(), newPrefix);
514                 return resolved;
515             }
516         }
517         return std::nullopt;
518     };
519 
520     auto tryResolveWithDynamicPaths = [this, &path]() -> std::optional<std::string> {
521         auto normalizedPath = ark::os::NormalizePath(std::string(path));
522         for (const auto &[dynPath, _] : dynamicPaths_) {
523             // NOTE(dkofanov): #23877. Fail, if there is no direct match of normalized dynamic module path.
524             // It may be worth to take an attempt to resolve 'path' as relative to some defined dynamicPath in order to
525             // keep 'arktsconfig.json's smaller.
526             if (normalizedPath == dynPath) {
527                 return dynPath;
528             }
529         }
530         return std::nullopt;
531     };
532 
533     if (isDynamic) {
534         auto result = tryResolveWithDynamicPaths();
535         if (result != std::nullopt) {
536             return result;
537         }
538         // NOTE: #26150. we fall to tryResolveWithPaths in this case 1.2->1.0->1.2
539         return tryResolveWithPaths();
540     }
541     auto result = tryResolveWithPaths();
542     if (result != std::nullopt) {
543         return result;
544     }
545     return tryResolveWithDynamicPaths();
546 }
547 
548 #ifdef ARKTSCONFIG_USE_FILESYSTEM
MatchExcludes(const fs::path & path,const std::vector<ArkTsConfig::Pattern> & excludes)549 static bool MatchExcludes(const fs::path &path, const std::vector<ArkTsConfig::Pattern> &excludes)
550 {
551     for (auto &e : excludes) {
552         if (e.Match(path.string())) {
553             return true;
554         }
555     }
556     return false;
557 }
558 
GetSourceList(const std::shared_ptr<ArkTsConfig> & arktsConfig)559 std::vector<fs::path> GetSourceList(const std::shared_ptr<ArkTsConfig> &arktsConfig)
560 {
561     auto includes = arktsConfig->Include();
562     auto excludes = arktsConfig->Exclude();
563     auto files = arktsConfig->Files();
564 
565     // If "files" and "includes" are empty - include everything from tsconfig root
566     auto configDir = fs::absolute(fs::path(arktsConfig->ConfigPath())).parent_path();
567     if (files.empty() && includes.empty()) {
568         includes = {ArkTsConfig::Pattern("**/*", configDir.string())};
569     }
570     // If outDir in not default add it into exclude
571     if (fs::exists(arktsConfig->OutDir()) && !fs::equivalent(arktsConfig->OutDir(), configDir)) {
572         excludes.emplace_back("**/*", arktsConfig->OutDir());
573     }
574 
575     // Collect "files"
576     std::vector<fs::path> sourceList;
577     for (auto &f : files) {
578         if (!arktsConfig->Check(fs::exists(f) && fs::path(f).has_filename(), diagnostic::NO_FILE, {f})) {
579             return {};
580         }
581 
582         sourceList.emplace_back(f);
583     }
584 
585     // Collect "include"
586     // TSC traverses folders for sources starting from 'include' rather than from 'rootDir', so we do the same
587     for (auto &include : includes) {
588         auto traverseRoot = fs::path(include.GetSearchRoot());
589         if (!fs::exists(traverseRoot)) {
590             continue;
591         }
592         if (!fs::is_directory(traverseRoot)) {
593             if (include.Match(traverseRoot.string()) && !MatchExcludes(traverseRoot, excludes)) {
594                 sourceList.emplace_back(traverseRoot);
595             }
596             continue;
597         }
598         for (const auto &dirEntry : fs::recursive_directory_iterator(traverseRoot)) {
599             if (include.Match(dirEntry.path().string()) && !MatchExcludes(dirEntry, excludes) &&
600                 !fs::is_directory(dirEntry)) {
601                 sourceList.emplace_back(dirEntry);
602             }
603         }
604     }
605     return sourceList;
606 }
607 
608 // Analogue of 'std::filesystem::relative()'
609 // Example: Relative("/a/b/c", "/a/b") returns "c"
Relative(const fs::path & src,const fs::path & base)610 static fs::path Relative(const fs::path &src, const fs::path &base)
611 {
612     fs::path tmpPath = src;
613     fs::path relPath;
614     while (!fs::equivalent(tmpPath, base)) {
615         relPath = relPath.empty() ? tmpPath.filename() : tmpPath.filename() / relPath;
616         if (tmpPath == tmpPath.parent_path()) {
617             return fs::path();
618         }
619         tmpPath = tmpPath.parent_path();
620     }
621     return relPath;
622 }
623 
624 // Compute path to destination file and create subfolders
ComputeDestination(const fs::path & src,const fs::path & rootDir,const fs::path & outDir)625 fs::path ArkTsConfig::ComputeDestination(const fs::path &src, const fs::path &rootDir, const fs::path &outDir)
626 {
627     auto rel = Relative(src, rootDir);
628     std::stringstream sRootDir;
629     sRootDir << rootDir;
630     std::stringstream sSrc;
631     sSrc << src;
632     if (!Check(!rel.empty(), diagnostic::NOT_ROOT_DIR, {sRootDir.str(), sSrc.str()})) {
633         return {};
634     }
635 
636     // CC-OFFNXT(G.EXP.22-CPP) false positive, overloaded operator
637     auto dst = outDir / rel;
638     fs::create_directories(dst.parent_path());
639     return dst.replace_extension("abc");
640 }
641 
FindProjectSources(const std::shared_ptr<ArkTsConfig> & arktsConfig)642 std::vector<std::pair<std::string, std::string>> FindProjectSources(const std::shared_ptr<ArkTsConfig> &arktsConfig)
643 {
644     auto sourceFiles = GetSourceList(arktsConfig);
645     std::vector<std::pair<std::string, std::string>> compilationList;
646     for (auto &src : sourceFiles) {
647         auto dst = arktsConfig->ComputeDestination(src, arktsConfig->RootDir(), arktsConfig->OutDir());
648         if (!arktsConfig->Check(!dst.empty(), diagnostic::INVALID_DESTINATION_FILE, {})) {
649             return {};
650         }
651 
652         compilationList.emplace_back(src.string(), dst.string());
653     }
654 
655     return compilationList;
656 }
657 #else
FindProjectSources(const std::shared_ptr<ArkTsConfig> & arkts_config)658 std::vector<std::pair<std::string, std::string>> FindProjectSources(
659     // CC-OFFNXT(G.FMT.06-CPP) project code style
660     [[maybe_unused]] const std::shared_ptr<ArkTsConfig> &arkts_config)
661 {
662     ES2PANDA_ASSERT(false);
663     return {};
664 }
665 #endif  // ARKTSCONFIG_USE_FILESYSTEM
666 
667 }  // namespace ark::es2panda
668