• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2023-2023 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/utils/json_builder.h"
18 #include "libpandabase/utils/json_parser.h"
19 #include "libpandabase/os/filesystem.h"
20 #include "util/language.h"
21 #include "generated/signatures.h"
22 
23 #include <fstream>
24 #include <regex>
25 #include <sstream>
26 #include <system_error>
27 
28 #ifndef ARKTSCONFIG_USE_FILESYSTEM
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 
42 // NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
43 #define CHECK(cond, ret, msg)                                   \
44     if (!(cond)) {                                              \
45         std::cerr << "ArkTsConfig error: " << msg << std::endl; \
46         return ret;                                             \
47     }
48 
49 namespace panda::es2panda {
50 
IsAbsolute(const std::string & path)51 static bool IsAbsolute(const std::string &path)
52 {
53 #ifndef ARKTSCONFIG_USE_FILESYSTEM
54     return !path.empty() && path[0] == '/';
55 #else
56     return fs::path(path).is_absolute();
57 #endif  // ARKTSCONFIG_USE_FILESYSTEM
58 }
59 
JoinPaths(const std::string & a,const std::string & b)60 std::string JoinPaths(const std::string &a, const std::string &b)
61 {
62 #ifndef ARKTSCONFIG_USE_FILESYSTEM
63     return a + '/' + b;
64 #else
65     return (fs::path(a) / b).string();
66 #endif  // ARKTSCONFIG_USE_FILESYSTEM
67 }
68 
ParentPath(const std::string & path)69 std::string ParentPath(const std::string &path)
70 {
71 #ifndef ARKTSCONFIG_USE_FILESYSTEM
72     auto pos = path.find('/');
73     return pos == std::string::npos ? path : path.substr(0, pos);
74 #else
75     return fs::path(path).parent_path().string();
76 #endif  // ARKTSCONFIG_USE_FILESYSTEM
77 }
78 
MakeAbsolute(const std::string & path,const std::string & base)79 static std::string MakeAbsolute(const std::string &path, const std::string &base)
80 {
81     return IsAbsolute(path) ? path : JoinPaths(base, path);
82 }
83 
84 #ifdef ARKTSCONFIG_USE_FILESYSTEM
85 
Pattern(std::string value,std::string base)86 ArkTsConfig::Pattern::Pattern(std::string value, std::string base) : value_(std::move(value)), base_(std::move(base))
87 {
88     ASSERT(fs::path(base_).is_absolute());
89 }
90 
IsPattern() const91 bool ArkTsConfig::Pattern::IsPattern() const
92 {
93     return (value_.find('*') != std::string::npos) || (value_.find('?') != std::string::npos);
94 }
95 
GetSearchRoot() const96 std::string ArkTsConfig::Pattern::GetSearchRoot() const
97 {
98     fs::path relative;
99     if (!IsPattern()) {
100         relative = value_;
101     } else {
102         auto foundStar = value_.find_first_of('*');
103         auto foundQuestion = value_.find_first_of('?');
104         relative = value_.substr(0, std::min(foundStar, foundQuestion));
105         relative = relative.parent_path();
106     }
107     return MakeAbsolute(relative.string(), base_);
108 }
109 
Match(const std::string & path) const110 bool ArkTsConfig::Pattern::Match(const std::string &path) const
111 {
112     ASSERT(fs::path(path).is_absolute());
113     fs::path value = fs::path(value_);
114     std::string pattern = value.is_absolute() ? value.string() : (base_ / value).string();
115 
116     // Replace arktsconfig special symbols with regular expressions
117     if (IsPattern()) {
118         // '**' matches any directory nested to any level
119         pattern = std::regex_replace(pattern, std::regex("\\*\\*/"), ".*");
120         // '*' matches zero or more characters (excluding directory separators)
121         pattern = std::regex_replace(pattern, std::regex("([^\\.])\\*"), "$1[^/]*");
122         // '?' matches any one character (excluding directory separators)
123         pattern = std::regex_replace(pattern, std::regex("\\?"), "[^/]");
124     }
125     if (!value.has_extension()) {
126         // default extensions to match
127         pattern += R"(.*(\.ts|\.d\.ts|\.ets)$)";
128     }
129     std::smatch m;
130     auto res = std::regex_match(path, m, std::regex(pattern));
131     return res;
132 }
133 
134 #endif  // ARKTSCONFIG_USE_FILESYSTEM
135 
136 #ifdef ARKTSCONFIG_USE_FILESYSTEM
ResolveConfigLocation(const std::string & relPath,const std::string & base)137 static std::string ResolveConfigLocation(const std::string &relPath, const std::string &base)
138 {
139     auto resolvedPath = MakeAbsolute(relPath, base);
140     auto newBase = base;
141     while (!fs::exists(resolvedPath)) {
142         resolvedPath = MakeAbsolute(relPath, JoinPaths(newBase, "node_modules"));
143         if (newBase == ParentPath(newBase)) {
144             return "";
145         }
146         newBase = ParentPath(newBase);
147     }
148     return resolvedPath;
149 }
150 #endif  // ARKTSCONFIG_USE_FILESYSTEM
151 
ValidDynamicLanguages()152 static std::string ValidDynamicLanguages()
153 {
154     JsonArrayBuilder builder;
155     for (auto &l : Language::All()) {
156         if (l.IsDynamic()) {
157             builder.Add(l.ToString());
158         }
159     }
160     return std::move(builder).Build();
161 }
162 
Parse()163 bool ArkTsConfig::Parse()
164 {
165     static const std::string BASE_URL = "baseUrl";
166     static const std::string COMPILER_OPTIONS = "compilerOptions";
167     static const std::string EXCLUDE = "exclude";
168     static const std::string EXTENDS = "extends";
169     static const std::string FILES = "files";
170     static const std::string INCLUDE = "include";
171     static const std::string OUT_DIR = "outDir";
172     static const std::string PATHS = "paths";
173     static const std::string DYNAMIC_PATHS = "dynamicPaths";
174     static const std::string ROOT_DIR = "rootDir";
175     static const std::string LANGUAGE = "language";
176     static const std::string HAS_DECL = "hasDecl";
177 
178     ASSERT(!isParsed_);
179     isParsed_ = true;
180     auto arktsConfigDir = ParentPath(panda::os::GetAbsolutePath(configPath_));
181 
182     // Read input
183     std::ifstream inputStream(configPath_);
184     CHECK(!inputStream.fail(), false, "Failed to open file: " << configPath_);
185     std::stringstream ss;
186     ss << inputStream.rdbuf();
187     std::string tsConfigSource = ss.str();
188     inputStream.close();
189 
190     // Parse json
191     auto arktsConfig = std::make_unique<JsonObject>(tsConfigSource);
192     CHECK(arktsConfig->IsValid(), false, "ArkTsConfig is not valid json");
193 
194 #ifdef ARKTSCONFIG_USE_FILESYSTEM
195     // Parse "extends"
196     auto extends = arktsConfig->GetValue<JsonObject::StringT>(EXTENDS);
197     if (extends != nullptr) {
198         auto basePath = ResolveConfigLocation(*extends, arktsConfigDir);
199         CHECK(!basePath.empty(), false, "Can't resolve config path: " << *extends);
200         auto base = ArkTsConfig(basePath);
201         CHECK(base.Parse(), false, "Failed to parse base config: " << *extends);
202         Inherit(base);
203     }
204 #endif  // ARKTSCONFIG_USE_FILESYSTEM
205 
206     // Parse "baseUrl", "outDir", "rootDir"
207     auto compilerOptions = arktsConfig->GetValue<JsonObject::JsonObjPointer>(COMPILER_OPTIONS);
208     auto parseRelDir = [&](std::string &dst, const std::string &key) {
209         if (compilerOptions != nullptr) {
210             auto path = compilerOptions->get()->GetValue<JsonObject::StringT>(key);
211             dst = ((path != nullptr) ? *path : "");
212         }
213         dst = MakeAbsolute(dst, arktsConfigDir);
214     };
215     parseRelDir(baseUrl_, BASE_URL);
216     parseRelDir(outDir_, OUT_DIR);
217     parseRelDir(rootDir_, ROOT_DIR);
218 
219     // Parse "paths"
220     if (compilerOptions != nullptr) {
221         auto paths = compilerOptions->get()->GetValue<JsonObject::JsonObjPointer>(PATHS);
222         if (paths != nullptr) {
223             for (size_t keyIdx = 0; keyIdx < paths->get()->GetSize(); ++keyIdx) {
224                 auto &key = paths->get()->GetKeyByIndex(keyIdx);
225                 if (paths_.count(key) == 0U) {
226                     paths_.insert({key, {}});
227                 }
228 
229                 auto values = paths->get()->GetValue<JsonObject::ArrayT>(key);
230                 CHECK(values, false, "Invalid value for 'path' with key '" << key << "'");
231                 CHECK(!values->empty(), false, "Substitutions for pattern '" << key << "' shouldn't be an empty array");
232                 for (auto &v : *values) {
233                     auto p = *v.Get<JsonObject::StringT>();
234                     paths_[key].emplace_back(MakeAbsolute(p, baseUrl_));
235                 }
236             }
237         }
238 
239         auto dynamicPaths = compilerOptions->get()->GetValue<JsonObject::JsonObjPointer>(DYNAMIC_PATHS);
240         if (dynamicPaths != nullptr) {
241             for (size_t keyIdx = 0; keyIdx < dynamicPaths->get()->GetSize(); ++keyIdx) {
242                 auto &key = dynamicPaths->get()->GetKeyByIndex(keyIdx);
243                 auto data = dynamicPaths->get()->GetValue<JsonObject::JsonObjPointer>(key);
244                 CHECK(data, false, "Invalid value for for dynamic path with key '" << key << "'");
245 
246                 auto langValue = data->get()->GetValue<JsonObject::StringT>(LANGUAGE);
247                 CHECK(langValue, false, "Invalid 'language' value for dynamic path with key '" << key << "'");
248 
249                 auto lang = Language::FromString(*langValue);
250                 CHECK((lang && lang->IsDynamic()), false,
251                       "Invalid 'language' value for dynamic path with key '" << key << "'. Should be one of "
252                                                                              << ValidDynamicLanguages());
253 
254                 CHECK(compiler::Signatures::Dynamic::IsSupported(*lang), false,
255                       "Interoperability with language '" << lang->ToString() << "' is not supported");
256 
257                 auto hasDeclValue = data->get()->GetValue<JsonObject::BoolT>(HAS_DECL);
258                 CHECK(hasDeclValue, false, "Invalid 'hasDecl' value for dynamic path with key '" << key << "'");
259 
260                 auto normalizedKey = panda::os::NormalizePath(key);
261                 auto res = dynamicPaths_.insert({normalizedKey, DynamicImportData(*lang, *hasDeclValue)});
262                 CHECK(res.second, false, "Duplicated dynamic path '" << key << "' for key '" << key << "'");
263             }
264         }
265     }
266 
267     // Parse "files"
268     auto files = arktsConfig->GetValue<JsonObject::ArrayT>(FILES);
269     if (files != nullptr) {
270         files_ = {};
271         CHECK(!files->empty(), false, "The 'files' list in config file is empty");
272         for (auto &f : *files) {
273             files_.emplace_back(MakeAbsolute(*f.Get<JsonObject::StringT>(), arktsConfigDir));
274         }
275     }
276 
277 #ifdef ARKTSCONFIG_USE_FILESYSTEM
278     // Parse "include"
279     auto include = arktsConfig->GetValue<JsonObject::ArrayT>(INCLUDE);
280     if (include != nullptr) {
281         include_ = {};
282         CHECK(!include->empty(), false, "The 'include' list in config file is empty");
283         for (auto &i : *include) {
284             include_.emplace_back(*i.Get<JsonObject::StringT>(), arktsConfigDir);
285         }
286     }
287     // Parse "exclude"
288     auto exclude = arktsConfig->GetValue<JsonObject::ArrayT>(EXCLUDE);
289     if (exclude != nullptr) {
290         exclude_ = {};
291         CHECK(!exclude->empty(), false, "The 'exclude' list in config file is empty");
292         for (auto &e : *exclude) {
293             exclude_.emplace_back(*e.Get<JsonObject::StringT>(), arktsConfigDir);
294         }
295     }
296 #endif  // ARKTSCONFIG_USE_FILESYSTEM
297 
298     return true;
299 }
300 
Inherit(const ArkTsConfig & base)301 void ArkTsConfig::Inherit(const ArkTsConfig &base)
302 {
303     baseUrl_ = base.baseUrl_;
304     outDir_ = base.outDir_;
305     rootDir_ = base.rootDir_;
306     paths_ = base.paths_;
307     files_ = base.files_;
308 #ifdef ARKTSCONFIG_USE_FILESYSTEM
309     include_ = base.include_;
310     exclude_ = base.exclude_;
311 #endif  // ARKTSCONFIG_USE_FILESYSTEM
312 }
313 
314 // Remove '/' and '*' from the end of path
TrimPath(const std::string & path)315 static std::string TrimPath(const std::string &path)
316 {
317     std::string trimmedPath = path;
318     while (!trimmedPath.empty() && (trimmedPath.back() == '*' || trimmedPath.back() == '/')) {
319         trimmedPath.pop_back();
320     }
321     return trimmedPath;
322 }
323 
ResolvePath(const std::string & path)324 std::string ArkTsConfig::ResolvePath(const std::string &path)
325 {
326     for (const auto &[alias, paths] : paths_) {
327         auto trimmedAlias = TrimPath(alias);
328         size_t pos = path.rfind(trimmedAlias, 0);
329         if (pos == 0) {
330             std::string resolved = path;
331             // NOTE(ivagin): arktsconfig contains array of paths for each prefix, for now just get first one
332             std::string newPrefix = TrimPath(paths[0]);
333             resolved.replace(pos, trimmedAlias.length(), newPrefix);
334             return resolved;
335         }
336     }
337     return "";
338 }
339 
340 #ifdef ARKTSCONFIG_USE_FILESYSTEM
MatchExcludes(const fs::path & path,const std::vector<ArkTsConfig::Pattern> & excludes)341 static bool MatchExcludes(const fs::path &path, const std::vector<ArkTsConfig::Pattern> &excludes)
342 {
343     for (auto &e : excludes) {
344         if (e.Match(path.string())) {
345             return true;
346         }
347     }
348     return false;
349 }
350 
GetSourceList(const std::shared_ptr<ArkTsConfig> & arktsConfig)351 static std::vector<fs::path> GetSourceList(const std::shared_ptr<ArkTsConfig> &arktsConfig)
352 {
353     auto includes = arktsConfig->Include();
354     auto excludes = arktsConfig->Exclude();
355     auto files = arktsConfig->Files();
356 
357     // If "files" and "includes" are empty - include everything from tsconfig root
358     auto configDir = fs::absolute(fs::path(arktsConfig->ConfigPath())).parent_path();
359     if (files.empty() && includes.empty()) {
360         includes = {ArkTsConfig::Pattern("**/*", configDir.string())};
361     }
362     // If outDir in not default add it into exclude
363     if (!fs::equivalent(arktsConfig->OutDir(), configDir)) {
364         excludes.emplace_back("**/*", arktsConfig->OutDir());
365     }
366 
367     // Collect "files"
368     std::vector<fs::path> sourceList;
369     for (auto &f : files) {
370         CHECK(fs::exists(f) && fs::path(f).has_filename(), {}, "No such file: " << f);
371         sourceList.emplace_back(f);
372     }
373 
374     // Collect "include"
375     // TSC traverses folders for sources starting from 'include' rather than from 'rootDir', so we do the same
376     for (auto &include : includes) {
377         auto traverseRoot = fs::path(include.GetSearchRoot());
378         if (!fs::exists(traverseRoot)) {
379             continue;
380         }
381         if (!fs::is_directory(traverseRoot)) {
382             if (include.Match(traverseRoot.string()) && !MatchExcludes(traverseRoot, excludes)) {
383                 sourceList.emplace_back(traverseRoot);
384             }
385             continue;
386         }
387         for (const auto &dirEntry : fs::recursive_directory_iterator(traverseRoot)) {
388             if (include.Match(dirEntry.path().string()) && !MatchExcludes(dirEntry, excludes)) {
389                 sourceList.emplace_back(dirEntry);
390             }
391         }
392     }
393     return sourceList;
394 }
395 
396 // Analogue of 'std::filesystem::relative()'
397 // Example: Relative("/a/b/c", "/a/b") returns "c"
Relative(const fs::path & src,const fs::path & base)398 static fs::path Relative(const fs::path &src, const fs::path &base)
399 {
400     fs::path tmpPath = src;
401     fs::path relPath;
402     while (!fs::equivalent(tmpPath, base)) {
403         relPath = relPath.empty() ? tmpPath.filename() : tmpPath.filename() / relPath;
404         if (tmpPath == tmpPath.parent_path()) {
405             return fs::path();
406         }
407         tmpPath = tmpPath.parent_path();
408     }
409     return relPath;
410 }
411 
412 // Compute path to destination file and create subfolders
ComputeDestination(const fs::path & src,const fs::path & rootDir,const fs::path & outDir)413 static fs::path ComputeDestination(const fs::path &src, const fs::path &rootDir, const fs::path &outDir)
414 {
415     auto rel = Relative(src, rootDir);
416     CHECK(!rel.empty(), {}, rootDir << " is not root directory for " << src);
417     auto dst = outDir / rel;
418     fs::create_directories(dst.parent_path());
419     return dst.replace_extension("abc");
420 }
421 
FindProjectSources(const std::shared_ptr<ArkTsConfig> & arktsConfig)422 std::vector<std::pair<std::string, std::string>> FindProjectSources(const std::shared_ptr<ArkTsConfig> &arktsConfig)
423 {
424     auto sourceFiles = GetSourceList(arktsConfig);
425     std::vector<std::pair<std::string, std::string>> compilationList;
426     for (auto &src : sourceFiles) {
427         auto dst = ComputeDestination(src, arktsConfig->RootDir(), arktsConfig->OutDir());
428         CHECK(!dst.empty(), {}, "Invalid destination file");
429         compilationList.emplace_back(src.string(), dst.string());
430     }
431 
432     return compilationList;
433 }
434 #else
FindProjectSources(const std::shared_ptr<ArkTsConfig> & arkts_config)435 std::vector<std::pair<std::string, std::string>> FindProjectSources(
436     [[maybe_unused]] const std::shared_ptr<ArkTsConfig> &arkts_config)
437 {
438     ASSERT(false);
439     return {};
440 }
441 #endif  // ARKTSCONFIG_USE_FILESYSTEM
442 
443 }  // namespace panda::es2panda
444