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 "dep_analyzer.h"
17 #include <chrono>
18
Dump(std::string & outFilePath)19 void DepAnalyzer::Dump(std::string &outFilePath)
20 {
21 std::ofstream outFile(outFilePath);
22 if (!outFile) {
23 std::cerr << "Error when opening a file " << outFilePath << std::endl;
24 return;
25 }
26
27 Dump(outFile);
28
29 outFile.close();
30 }
31
ConvertPath(const std::string & path) const32 std::string DepAnalyzer::ConvertPath(const std::string &path) const
33 {
34 std::string converted = path;
35
36 #if defined(_WIN32)
37 constexpr char BACKSLASH = '\\';
38 constexpr size_t ESCAPED_BACKSLASH_LENGTH = 2;
39
40 size_t foundPos = 0;
41 while ((foundPos = converted.find(BACKSLASH, foundPos)) != std::string::npos) {
42 converted.replace(foundPos, 1, "\\\\");
43 foundPos += ESCAPED_BACKSLASH_LENGTH;
44 }
45 #endif
46
47 return converted;
48 }
49
Dump(std::ostream & ostr)50 void DepAnalyzer::Dump(std::ostream &ostr)
51 {
52 std::string jsonTab = " ";
53 ostr << "{\n";
54
55 ostr << jsonTab << "\"dependencies\": {\n";
56 bool isFirst = true;
57 for (auto [file, deps] : fileDirectDependencies_) {
58 if (!isFirst) {
59 ostr << ",\n";
60 } else {
61 isFirst = false;
62 }
63 ostr << jsonTab << jsonTab << "\"" << ConvertPath(file) << "\": [";
64 bool isFirst2 = true;
65 for (const auto &dep : deps) {
66 if (!isFirst2) {
67 ostr << ", ";
68 } else {
69 isFirst2 = false;
70 }
71 ostr << "\"" << ConvertPath(dep) << "\"";
72 }
73 ostr << "]";
74 }
75 ostr << "\n" << jsonTab << "},\n";
76
77 ostr << jsonTab << "\"dependants\": {\n";
78 isFirst = true;
79 for (auto [file, deps] : fileDirectDependants_) {
80 if (!isFirst) {
81 ostr << ",\n";
82 } else {
83 isFirst = false;
84 }
85 ostr << jsonTab << jsonTab << "\"" << ConvertPath(file) << "\": [";
86 bool isFirst2 = true;
87 for (const auto &dep : deps) {
88 if (!isFirst2) {
89 ostr << ", ";
90 } else {
91 isFirst2 = false;
92 }
93 ostr << "\"" << ConvertPath(dep) << "\"";
94 }
95 ostr << "]";
96 }
97 ostr << "\n" << jsonTab << "}\n";
98 ostr << "}";
99 }
100
AddImports(ark::es2panda::parser::ETSParser * parser)101 void DepAnalyzer::AddImports(ark::es2panda::parser::ETSParser *parser)
102 {
103 ark::es2panda::util::StringView firstSourceFilePath = parser->GetGlobalProgramAbsName();
104 sourcePaths_.emplace_back(std::string(firstSourceFilePath));
105
106 ark::es2panda::util::ImportPathManager *manager = parser->GetImportPathManager();
107 auto &parseList = manager->ParseList();
108
109 for (auto &pl : parseList) {
110 sourcePaths_.emplace_back(std::string(pl.importData.resolvedSource));
111 }
112 }
113
MergeFileDeps(ark::es2panda::parser::Program * mainProgram)114 void DepAnalyzer::MergeFileDeps(ark::es2panda::parser::Program *mainProgram)
115 {
116 std::string progAbsPath = std::string {mainProgram->AbsoluteName()};
117
118 if (mainProgram->GetFileDependencies().empty()) {
119 fileDirectDependencies_.emplace(progAbsPath, std::unordered_set<std::string>());
120 }
121 if (fileDirectDependants_.count(progAbsPath) == 0U) {
122 fileDirectDependants_.emplace(progAbsPath, std::unordered_set<std::string>());
123 }
124 for (auto &[_, progs] : mainProgram->DirectExternalSources()) {
125 for (auto prog : progs) {
126 auto extprogAbsPath = std::string {prog->AbsoluteName()};
127 if (extprogAbsPath == progAbsPath) {
128 continue;
129 }
130
131 fileDirectDependencies_[progAbsPath].insert(extprogAbsPath);
132 fileDirectDependants_[extprogAbsPath].insert(progAbsPath);
133 }
134 }
135 for (const auto &[key, valueSet] : mainProgram->GetFileDependencies()) {
136 fileDirectDependencies_[key].insert(valueSet.begin(), valueSet.end());
137 for (const auto &v : valueSet) {
138 fileDirectDependants_[v].insert(key);
139 }
140 }
141 }
142
AnalyzeDepsForMultiFiles(const char * exec,std::vector<std::string> & fileList,std::string & arktsconfig)143 int DepAnalyzer::AnalyzeDepsForMultiFiles(const char *exec, std::vector<std::string> &fileList,
144 std::string &arktsconfig)
145 {
146 std::unordered_set<std::string> parsedFileList;
147 const auto *impl = es2panda_GetImpl(ES2PANDA_LIB_VERSION);
148
149 for (auto &file : fileList) {
150 if (parsedFileList.count(file) != 0U || fileDirectDependencies_.count(file) != 0U) {
151 continue;
152 }
153
154 es2panda_Config *cfg = nullptr;
155 if (!arktsconfig.empty()) {
156 std::array<const char *, 3> args = {exec, file.c_str(), arktsconfig.c_str()};
157 cfg = impl->CreateConfig(args.size(), args.data());
158 } else {
159 std::array<const char *, 2> args = {exec, file.c_str()};
160 cfg = impl->CreateConfig(args.size(), args.data());
161 }
162
163 if (cfg == nullptr) {
164 std::cerr << "Failed to create config" << std::endl;
165 return 1;
166 }
167 auto *cfgImpl = reinterpret_cast<ark::es2panda::public_lib::ConfigImpl *>(cfg);
168 auto parserInputCStr = cfgImpl->options->CStrParserInputContents().first;
169
170 es2panda_Context *ctx =
171 impl->CreateContextFromString(cfg, parserInputCStr, cfgImpl->options->SourceFileName().c_str());
172 auto *ctxImpl = reinterpret_cast<ark::es2panda::public_lib::Context *>(ctx);
173 impl->ProceedToState(ctx, ES2PANDA_STATE_PARSED);
174
175 if (ctxImpl->state == ES2PANDA_STATE_ERROR) {
176 ctxImpl->checker->LogTypeError(std::string("Parse Failed: ").append(ctxImpl->errorMessage),
177 ctxImpl->errorPos);
178 impl->DestroyContext(ctx);
179 impl->DestroyConfig(cfg);
180 return 1;
181 }
182
183 ark::es2panda::parser::Program *mainProgram = ctxImpl->parserProgram;
184 std::string mainProgramAbsPath = std::string {mainProgram->AbsoluteName()};
185 parsedFileList.insert(mainProgramAbsPath);
186 MergeFileDeps(mainProgram);
187
188 impl->DestroyContext(ctx);
189 impl->DestroyConfig(cfg);
190 }
191 return 0;
192 }
193
AddFileList(std::string & fileListPath,std::vector<std::string> & fileList)194 static void AddFileList(std::string &fileListPath, std::vector<std::string> &fileList)
195 {
196 std::ifstream inFile(fileListPath);
197 if (!inFile.is_open()) {
198 std::cerr << "Error when opening a file " << fileListPath << std::endl;
199 return;
200 }
201 std::string line;
202 while (getline(inFile, line)) {
203 if (!line.empty()) {
204 fileList.emplace_back(line);
205 }
206 }
207 }
208
ParseArguments(ark::Span<const char * const> args)209 std::optional<DepAnalyzerArgs> ParseArguments(ark::Span<const char *const> args)
210 {
211 DepAnalyzerArgs parsedArgs;
212 parsedArgs.programName = args[0];
213
214 for (size_t i = 1; i < args.size(); i++) {
215 if (std::strncmp(args[i], "--arktsconfig=", std::strlen("--arktsconfig=")) == 0) {
216 parsedArgs.arktsconfig = args[i];
217 continue;
218 }
219 if (std::strncmp(args[i], "@", std::strlen("@")) == 0) {
220 std::string_view arg(args[i]);
221 std::string fileListPath(arg.substr(1));
222 AddFileList(fileListPath, parsedArgs.fileList);
223 continue;
224 }
225 parsedArgs.fileList.emplace_back(args[i]);
226 }
227
228 return parsedArgs;
229 }
230
AnalyzeDeps(int argc,const char ** argv)231 int DepAnalyzer::AnalyzeDeps(int argc, const char **argv)
232 {
233 int minArgCount = 2;
234 if (argc < minArgCount) {
235 std::cerr << "No file has been entered for analysis" << std::endl;
236 return 1;
237 }
238 ark::Span<const char *const> args(argv, static_cast<size_t>(argc));
239 auto parsedArgs = ParseArguments(args);
240 if (AnalyzeDepsForMultiFiles(parsedArgs->programName.c_str(), parsedArgs->fileList, parsedArgs->arktsconfig) != 0) {
241 return 1;
242 }
243 return 0;
244 }
245