• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 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
16const { readFile, etsComponentSet, collectAllApi, callMethod } = require('./util');
17const { excel } = require('./collectApi');
18const { collectBaseApi } = require('./format');
19const path = require('path');
20const fs = require('fs');
21const ts = require('typescript');
22
23let allCallApisInApp = [];
24
25function collectApis(url) {
26  const applicationUrl = path.resolve(__dirname, url);
27  const applicationFiles = [];
28  readFile(applicationUrl, applicationFiles);
29  if (applicationFiles.length === 0) {
30    console.error('ERROR:application directory is empty!');
31  } else {
32    parseFileContent(applicationFiles, visitEachNode);
33    const noRepeatApis = deleteRepeatApis(allCallApisInApp);
34    excel(noRepeatApis);
35  }
36}
37
38function deleteRepeatApis(allApis) {
39  let allApisSet = new Set();
40  let noRepeatApis = [];
41  allApis.forEach(api => {
42    allApisSet.add(JSON.stringify(api));
43  });
44  allApisSet.forEach(item => {
45    noRepeatApis.push(JSON.parse(item));
46  });
47  return noRepeatApis;
48}
49
50function parseFileContent(applicationFiles, callback) {
51  applicationFiles.forEach(url => {
52    if (/\.ets/.test(path.basename(url)) || /\.ts/.test(path.basename(url)) ||
53      /\.js(?!on)/.test(path.basename(url))) {
54      const content = fs.readFileSync(url, 'utf-8');
55      const fileName = path.basename(url).replace(/\.d.ts$|\.js/g, 'ts');
56      ts.transpileModule(content, {
57        compilerOptions: {
58          'target': ts.ScriptTarget.ES2017,
59        },
60        fileName: fileName,
61        transformers: { before: [callback(url)] },
62      });
63    }
64  });
65}
66
67function visitEachNode(url) {
68  return (context) => {
69    return (sourcefile) => {
70      const statements = sourcefile.statements;
71      // 存放import的d.ts文件和类
72      let importFiles = [];
73      // 存放符合调用条件的API和组件
74      let apiList = [];
75      statements.forEach(item => {
76        if (ts.isImportDeclaration(item)) {
77          judgeImportFile(item, importFiles);
78        } else {
79          collectApplicationApi(item, sourcefile, url, apiList);
80        }
81      });
82      apiList = addPackageName(apiList, importFiles);
83      handleInstantiatedCall(apiList);
84      allCallApisInApp = allCallApisInApp.concat(collectBaseApi(importFiles, apiList));
85      return sourcefile;
86    };
87  };
88}
89
90function handleInstantiatedCall(apiList) {
91  apiList.forEach(instantiatedApi => {
92    apiList.forEach(api => {
93      if (api !== undefined && instantiatedApi !== undefined && instantiatedApi.instantiateObject === api.moduleName) {
94        // 将所有实例化调用方式的API备注统一改为‘实例化对象方式调用’,便于后续处理。
95        api.notes = '实例化对象方式调用';
96        if (instantiatedApi.notes === callMethod.firstCallMethod) {
97          api.packageName = instantiatedApi.packageName;
98        } else if (instantiatedApi.notes === callMethod.secondCallMethod) {
99          api.packageName = instantiatedApi.packageName;
100          api.moduleName = instantiatedApi.apiName;
101        } else if (instantiatedApi.notes === callMethod.thirdCallMethod) {
102          api.packageName = instantiatedApi.packageName;
103          api.moduleName = instantiatedApi.moduleName;
104        } else if (instantiatedApi.notes === callMethod.fourthCallMethod) {
105          api.packageName = instantiatedApi.packageName;
106          api.moduleName = instantiatedApi.apiName;
107
108        }
109      }
110    });
111  });
112}
113
114// 收集import的文件名和类
115function judgeImportFile(node, importFiles) {
116  if (isImportFiles(node)) {
117    let importFileName = node.moduleSpecifier.text;
118    if (node.importClause && node.importClause.name !== undefined) {
119      importFiles.push({
120        importFile: importFileName,
121        importClass: node.importClause.name.escapedText,
122      });
123    } else if (node.importClause.namedBindings !== undefined &&
124      ts.isNamedImports(node.importClause.namedBindings)) {
125      node.importClause.namedBindings.elements.forEach(element => {
126        importFiles.push({
127          importFile: importFileName,
128          importClass: element.name.escapedText,
129        });
130      });
131    }
132  }
133}
134
135/**
136 * 收集所有的API,包含组件和API
137 * @param {ts.node} node
138 * @param {ts.sourcefile} sourcefile
139 * @param {string} url
140 * @param {Array} apiList [{callLocation:'', moduleName:'', apiName: '', packageName: '',
141 * instantiateObject:'',interfaceName: '',value:'', type:'',notes:''}]
142 */
143function collectApplicationApi(node, sourcefile, url, apiList) {
144  if (ts.isPropertyAccessExpression(node) && node.expression && ts.isIdentifier(node.name)) {
145    collectCommonCallApis(node, sourcefile, url, apiList);
146  } else if (ts.isQualifiedName(node) && ts.isTypeReferenceNode(node.parent)) {
147    if (node.parent.parent.name && ts.isIdentifier(node.parent.parent.name)) {
148      const note = callMethod.secondCallMethod;
149      const type = 'API';
150      const instantiateObject = node.parent.parent.name.escapedText;
151      const moduleName = node.left.escapedText;
152      const apiName = node.right.escapedText;
153      apiList.push(collectAllApi(url, sourcefile, moduleName, apiName, instantiateObject, '', '', type, note, node));
154    } else {
155      const type = 'API';
156      const instantiateObject = '';
157      const moduleName = node.left.escapedText;
158      const apiName = node.right.escapedText;
159      apiList.push(collectAllApi(url, sourcefile, moduleName, apiName, instantiateObject, '', '', type, '', node));
160    }
161
162  } else if (ts.isNewExpression(node) && ts.isPropertyDeclaration(node.parent)) {
163    collectNewExpressionApi(node, url, sourcefile, apiList);
164  } else if (ts.isClassDeclaration(node) && node.heritageClauses && node.members) {
165    collectLifeCycleApi(node, url, sourcefile, apiList);
166  } else if (isEtsComponentNode(node)) {
167    const type = 'ArkUI';
168    collectComponentApi(node, apiList, type, url, sourcefile);
169    if (node.arguments && ts.isIdentifier(node.expression)) {
170      collectComponentApis(sourcefile, url, type, node, apiList);
171    }
172  }
173  node.getChildren().forEach(item => collectApplicationApi(item, sourcefile, url, apiList));
174}
175
176function collectNewExpressionApi(node, url, sourcefile, apiList) {
177  if (etsComponentSet.has(node.expression.escapedText)) {
178    const type = 'ArkUI';
179    collectComponentApis(sourcefile, url, type, node, apiList);
180  } else if (ts.isPropertyAccessExpression(node.expression)) {
181    const moduleName = node.expression.expression.escapedText;
182    const apiName = node.expression.name.escapedText;
183    const instantiateObject = node.parent.name.escapedText;
184    const note = callMethod.fourthCallMethod;
185    const type = 'API';
186    apiList.push(collectAllApi(url, sourcefile, moduleName, apiName, instantiateObject, '', '', type, note, node));
187  } else {
188    const note = callMethod.thirdCallMethod;
189    const type = 'API';
190    const instantiateObject = node.parent.name.escapedText;
191    const moduleName = node.expression.escapedText;
192    apiList.push(collectAllApi(url, sourcefile, moduleName, '', instantiateObject, '', '', type, note, node));
193  }
194}
195
196function isEtsComponentNode(node) {
197  return ts.isEtsComponentExpression(node) || (ts.isCallExpression(node) && node.expression &&
198    ts.isIdentifier(node.expression) && etsComponentSet.has(node.expression.escapedText.toString()));
199}
200
201// 收集生命周期类型的API。
202function collectLifeCycleApi(node, url, sourcefile, apiList) {
203  const classNode = node.heritageClauses[0].types[0].expression;
204  const note = '';
205  const type = 'API';
206
207  if (ts.isIdentifier(classNode)) {
208    const moduleName = classNode.escapedText;
209    getLifeCycleApiWithoutValue(node.members, moduleName, type, note, node, apiList, url, sourcefile);
210  } else if (ts.isPropertyAccessExpression(classNode)) {
211    const moduleName = classNode.expression.escapedText;
212    const apiName = classNode.name.escapedText;
213    getValuableLifeCycleApi(node.members, moduleName, apiName, type, note, node, apiList, url, sourcefile);
214  }
215}
216
217function getLifeCycleApiWithoutValue(members, moduleName, type, note, node, apiList, url, sourcefile) {
218  members.forEach(member => {
219    if (ts.isConstructorDeclaration(member)) {
220      const apiName = 'constructor';
221      apiList.push(collectAllApi(url, sourcefile, moduleName, apiName, '', '', '', type, note, node));
222    } else {
223      const apiName = member.name ? member.name.escapedText : '';
224      apiList.push(collectAllApi(url, sourcefile, moduleName, apiName, '', '', '', type, note, node));
225    }
226  });
227}
228
229function getValuableLifeCycleApi(members, moduleName, apiName, type, note, node, apiList, url, sourcefile) {
230  let value = '';
231
232  members.forEach(member => {
233    if (ts.isConstructorDeclaration(member)) {
234      value = 'constructor';
235      apiList.push(collectAllApi(url, sourcefile, moduleName, apiName, '', '', value, type, note, node));
236    } else {
237      value = member.name ? member.name.escapedText : '';
238      apiList.push(collectAllApi(url, sourcefile, moduleName, apiName, '', '', value, type, note, node));
239    }
240  });
241}
242
243function collectComponentApis(sourcefile, url, type, componentNode, apiList) {
244  let componentName = componentNode.expression.escapedText;
245  const notes = '比较API';
246
247  if (componentNode.arguments) {
248    componentNode.arguments.forEach(argument => {
249      if (ts.isObjectLiteralExpression(argument)) {
250        let componentApiArr = collectNestedComponentApi(argument);
251        componentApiArr.forEach(componentApi => {
252          apiList.push(collectAllApi(url, sourcefile, componentName, componentApi, '',
253            '', '', type, notes, componentNode));
254        });
255      }
256    });
257  }
258}
259
260// 收集组件中写在{}里调用的API,可能会有嵌套调用的情况
261function collectNestedComponentApi(node) {
262  let resultArr = [];
263
264  if (ts.isObjectLiteralExpression(node)) {
265    node.properties.forEach(property => {
266      if (ts.isPropertyAssignment(property) && property.name && ts.isIdentifier(property.name)) {
267        resultArr.push(property.name.escapedText.toString());
268        if (property.initializer && ts.isObjectLiteralExpression(property.initializer)) {
269          resultArr = resultArr.concat(collectNestedComponentApi(property.initializer));
270        }
271      }
272    });
273  }
274  return resultArr;
275}
276
277function collectComponentApi(node, apiList, type, url, sourcefile) {
278  const notes = '';
279  let etsComponentBlockPos = new Set([]);
280  const componentName = node.expression.escapedText ? node.expression.escapedText.toString() :
281    node.expression.expression.escapedText.toString();
282
283  if (ts.isEtsComponentExpression(node) && ts.isBlock(node.parent.parent) &&
284    !etsComponentBlockPos.has(node.parent.parent.pos)) {
285    etsComponentBlockPos.add(node.parent.parent);
286    const blockNode = node.parent.parent;
287    const statements = blockNode.statements;
288    statements.forEach((stat, index) => {
289      if (stat.expression && ts.isEtsComponentExpression(stat.expression) &&
290        (componentName === stat.expression.escapedText || componentName === stat.expression.expression.escapedText)) {
291        getCommonCallComponentApi(statements, url, sourcefile, componentName, type, notes, apiList, index, stat);
292      }
293    });
294  } else if (ts.isCallExpression(node)) {
295    let temp = node.parent;
296    while (!ts.isExpressionStatement(temp) && !(ts.isCallExpression(temp) && ts.isCallExpression(temp.parent))) {
297      collectExpressionStatementApis(temp, url, sourcefile, componentName, type, notes, node, apiList);
298      temp = temp.parent;
299    }
300  }
301}
302
303function collectExpressionStatementApis(temp, url, sourcefile, componentName, type, notes, node, apiList) {
304  if (ts.isPropertyAccessExpression(temp)) {
305    if (ts.isPropertyAccessExpression(temp)) {
306      apiName = temp.name.escapedText.toString();
307      apiList.push(collectAllApi(url, sourcefile, componentName, apiName, '', '', '', type, notes, node));
308    } else if (ts.isIdentifier(temp)) {
309      apiName = temp.escapedText.toString();
310      apiList.push(collectAllApi(url, sourcefile, componentName, apiName, '', '', '', type, notes, node));
311    }
312  }
313}
314
315function getCommonCallComponentApi(statements, url, sourcefile, componentName, type, notes, apiList, index, stat) {
316  if (index + 1 < statements.length && ts.isExpressionStatement(statements[index + 1]) &&
317    statements[index + 1].expression && ts.isCallExpression(statements[index + 1].expression)) {
318    let temp = statements[index + 1].expression.expression;
319    while (temp) {
320      if (ts.isPropertyAccessExpression(temp)) {
321        apiName = temp.name.escapedText.toString();
322        apiList.push(collectAllApi(url, sourcefile, componentName, apiName, '', '', '', type, notes, temp));
323      } else if (ts.isIdentifier(temp)) {
324        apiName = temp.escapedText.toString();
325        apiList.push(collectAllApi(url, sourcefile, componentName, apiName, '', '', '', type, notes, temp));
326      }
327      temp = temp.expression;
328    }
329  }
330}
331
332// 收集常见调用方式的API
333function collectCommonCallApis(node, sourcefile, url, apiList) {
334  let type = 'API';
335  let moduleName = '';
336  let apiName = '';
337
338  if (ts.isCallExpression(node.expression) && ts.isPropertyAccessExpression(node.expression.expression) &&
339    node.expression.expression.expression.escapedText) {
340    moduleName = node.expression.expression.expression.escapedText;
341    apiName = node.name.escapedText.toString;
342    apiList.push(collectAllApi(url, sourcefile, moduleName, apiName, '', '', '', type, '', node));
343  } else if (ts.isPropertyAccessExpression(node.expression) && node.expression.expression) {
344    if (ts.isIdentifier(node.expression.expression)) {
345      moduleName = node.expression.expression.escapedText;
346      apiName = node.expression.name.escapedText;
347      const value = node.name.escapedText;
348      apiList.push(collectAllApi(url, sourcefile, moduleName, apiName, '', '', value, type, '', node));
349    } else {
350      moduleName = node.expression.name.escapedText;
351      apiName = node.name.escapedText;
352      apiList.push(collectAllApi(url, sourcefile, moduleName, apiName, '', '', '', type, '', node));
353    }
354  } else if (ts.isIdentifier(node.expression) && ts.isCallExpression(node.parent)) {
355    apiList.push(collectOnOffApi(node, url, type, sourcefile));
356  } else if (ts.isIdentifier(node.expression) && ts.isIdentifier(node.name)) {
357    moduleName = node.expression.escapedText;
358    apiName = node.name.escapedText;
359    apiList.push(collectAllApi(url, sourcefile, moduleName, apiName, '', '', '', type, '', node));
360  }
361}
362
363// 收集到的API是没有d.ts文件名的,通过这个函数添加上
364function addPackageName(apiList, importFiles) {
365  importFiles.forEach(importData => {
366    apiList.forEach(api => {
367      if (api !== undefined && importData.importClass.match(new RegExp(api.moduleName, 'i'))) {
368        api.packageName = importData.importFile;
369      }
370    });
371  });
372  return apiList;
373}
374
375// API名字为on/off的单独处理,拼接上type类型。
376function collectOnOffApi(node, url, type, sourcefile) {
377  const moduleName = node.expression.escapedText;
378  let instantiateObject = '';
379  let apiName = '';
380  let note = '';
381  if (node.parent.arguments && node.name.escapedText.toString() === 'on' ||
382    node.name.escapedText.toString() === 'off') {
383    node.parent.arguments.forEach(argument => {
384      if (ts.isStringLiteral(argument) || ts.isIdentifier(argument)) {
385        apiName = node.name.escapedText + '_' + argument.text;
386      }
387    });
388  } else if (ts.isVariableDeclaration(node.parent.parent)) {
389    instantiateObject = node.parent.parent.name.escapedText;
390    apiName = node.name.escapedText;
391    note = callMethod.firstCallMethod;
392  } else {
393    apiName = node.name.escapedText;
394  }
395  if (apiName !== '') {
396    return collectAllApi(url, sourcefile, moduleName, apiName, instantiateObject, '', '', type, note, node);
397  }
398  return {};
399}
400
401function isImportFiles(node) {
402  if (ts.isStringLiteral(node.moduleSpecifier) && ((node.moduleSpecifier.text).indexOf('@ohos.') !== -1 ||
403    (node.moduleSpecifier.text).indexOf('@system.') !== -1) && node.importClause !== undefined) {
404    return true;
405  }
406  return false;
407}
408
409try {
410  collectApis('../application');
411} catch (error) {
412  console.error('COLLECT IMPORT NAME ERROR: ', error);
413}