• 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 { FileSystem } = require('./utils');
17const path = require('path');
18const ts = require('typescript');
19const fs = require('fs');
20
21class SystemApiRecognizer {
22  constructor(systemRoot) {
23    this.systemRoot = systemRoot;
24    this.arkUIDecorator = new Set(['@Builder', '@Styles', '@Extend']);
25    this.arkUIRender = new Set(['build']);
26    this.componentAttributeMap = new Map();
27    this.componentAttributeTypeSymbolMap = new Map();
28    this.forEachComponents = new Set(['LazyForEach', 'ForEach']);
29  }
30
31  /**
32   * 遍历AST, 识别系统API调用。
33   *
34   * @param {ts.Node} node
35   * @param {string} fileName
36   */
37  visitNode(node, fileName) {
38    if (this.isArkUIRenderMethod(node)) {
39      this.visitUIRenderNode(node.body, fileName);
40    } else {
41      this.visitNormalNode(node, fileName);
42    }
43  }
44
45  /**
46   * 遍历访问TypeScript 节点
47   *
48   * @param {ts.Node} node
49   * @param {string} fileName
50   */
51  visitNormalNode(node, fileName) {
52    if (node) {
53      if (ts.isCallExpression(node)) {
54        this.recognizeNormalCallExpression(node, fileName);
55      } else {
56        this.recognizeNormal(node, fileName);
57        ts.forEachChild(node, (child) => {
58          this.visitNode(child, fileName);
59        });
60      }
61    }
62  }
63
64  /**
65   * 遍历访问 UI 渲染节点
66   *
67   * @param {ts.Block} node
68   * @param {string} fileName
69   */
70  visitUIRenderNode(node, fileName) {
71    node.statements.forEach((statement) => {
72      this.recognizeUIComponents(statement, fileName);
73    });
74  }
75
76  setTypeChecker(typeChecker) {
77    this.typeChecker = typeChecker;
78  }
79
80  /**
81   * 保存系统API
82   *
83   * @param {ApiDeclarationInformation} apiInfo
84   */
85  addApiInformation(apiInfo) {
86    if (!this.apiInfos) {
87      this.apiInfos = [];
88    }
89    this.apiInfos.push(apiInfo);
90  }
91
92  getApiInformations() {
93    const apiDecInfos = this.apiInfos ? this.apiInfos : [];
94    apiDecInfos.forEach((apiInfo) => {
95      apiInfo.setApiNode(undefined);
96    });
97    return apiDecInfos;
98  }
99
100  isSdkApi(apiFilePath) {
101    return FileSystem.isInDirectory(this.systemRoot, apiFilePath);
102  }
103
104  /**
105   * 判断是否为创建ArkUI组件的方法
106   *
107   * @param {ts.Node} node
108   */
109  isArkUIRenderMethod(node) {
110    if (ts.isMethodDeclaration(node) || ts.isFunctionDeclaration(node)) {
111      return this.isBuildMethodInStruct(node) || this.hasArkUIDecortor(node);
112    }
113    return false;
114  }
115
116  /**
117   * 是否为struct 中的 build 方法。
118   *
119   * @param {ts.Node} node
120   * @returns ture or false
121   */
122  isBuildMethodInStruct(node) {
123    return node.name && this.arkUIRender.has(node.name.getText()) && ts.isStructDeclaration(node.parent);
124  }
125
126  /**
127   * 判断是否有ArkUI注解
128   *
129   * @param {ts.Node} node
130   * @returns true or false
131   */
132  hasArkUIDecortor(node) {
133    if (node.decorators) {
134      for (const decorator of node.decorators) {
135        const decoratorName = this.getDecortorIdefiner(decorator);
136        if (!decoratorName) {
137          continue;
138        }
139        const decoratorStr = `@${decoratorName.getText()}`;
140        if (!this.arkUIDecorator.has(decoratorStr)) {
141          continue;
142        }
143        const decoratroSymbol = this.typeChecker.getSymbolAtLocation(decoratorName);
144        if (this.isSdkApi(decoratroSymbol.valueDeclaration.getSourceFile().fileName)) {
145          return true;
146        }
147      }
148    }
149    return false;
150  }
151
152  getDecortorIdefiner(decoratorExp) {
153    if (ts.isDecorator(decoratorExp) || ts.isCallExpression(decoratorExp)) {
154      return this.getDecortorIdefiner(decoratorExp.expression);
155    }
156    return ts.isIdentifier(decoratorExp) ? decoratorExp : undefined;
157  }
158
159  /**
160   * 获取AST节点的API信息。
161   *
162   * @param {ts.Node} node
163   * @param {string} fileName
164   * @param {Function} positionCallback
165   * @returns {ApiDeclarationInformation | undefined} apiDecInfo
166   */
167  recognizeApiWithNode(node, fileName, positionCallback, useDeclarations) {
168    if (!node) {
169      return undefined;
170    }
171    const symbol = this.typeChecker.getSymbolAtLocation(node);
172    return this.recognizeApiWithNodeAndSymbol(node, symbol, fileName, positionCallback, useDeclarations);
173  }
174
175  recognizeApiWithNodeAndSymbol(node, symbol, fileName, positionCallback, useDeclarations) {
176    if (symbol) {
177      const apiDecInfo = this.getSdkApiDeclarationWithSymbol(symbol, node, useDeclarations);
178      if (apiDecInfo) {
179        apiDecInfo.setPosition(ts.getLineAndCharacterOfPosition(node.getSourceFile(), positionCallback(node)));
180        apiDecInfo.setSourceFileName(fileName);
181        this.addApiInformation(apiDecInfo);
182        return apiDecInfo;
183      }
184    }
185    return undefined;
186  }
187
188  /**
189   * 识别TS代码节点中的系统API
190   *
191   * @param {ts.Node} node
192   * @param {string} fileName
193   */
194  recognizeNormal(node, fileName) {
195    if (ts.isPropertyAccessExpression(node)) {
196      this.recognizePropertyAccessExpression(node, fileName);
197    } else if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) {
198      this.recognizeHeritageClauses(node, fileName);
199    } else if (ts.isNewExpression(node) && ts.isIdentifier(node.expression)) {
200      this.recognizeApiWithNode(node.expression, fileName, (node) => node.getStart());
201    } else if (ts.isStructDeclaration(node)) {
202      this.recognizeHeritageClauses(node, fileName);
203    }
204  }
205
206  recognizeNormalCallExpression(node, fileName) {
207    if (!node) {
208      return undefined;
209    }
210    if (ts.isCallExpression(node)) {
211      const apiDecInfo = this.recognizeNormalCallExpression(node.expression, fileName);
212      this.recognizeNormalCallExpressionArguments(apiDecInfo, node.arguments, fileName);
213      return apiDecInfo;
214    } else if (ts.isPropertyAccessExpression(node)) {
215      this.recognizeNormalCallExpression(node.expression, fileName);
216      return this.recognizePropertyAccessExpression(node, fileName);
217    } else {
218      return undefined;
219    }
220  }
221
222  recognizeNormalCallExpressionArguments(apiDecInfo, args, fileName) {
223    if (args.length === 0) {
224      return;
225    }
226    const parameters = apiDecInfo ? apiDecInfo.apiNode.parameters : undefined;
227    args.forEach((arg, index) => {
228      // interface 定义作为函数入参时, 统计为API
229      if (parameters && parameters[index] && parameters[index].type) {
230        const paramType = parameters[index].type;
231        if (ts.isTypeReferenceNode(paramType)) {
232          const paramTypeApiDecInfo = this.recognizeApiWithNode(paramType.typeName, fileName, (node) => node.getStart(), true);
233          if (paramTypeApiDecInfo) {
234            this.modifyTypeReferenceSourceFileName(paramType.typeName, paramTypeApiDecInfo);
235            paramTypeApiDecInfo.setApiType('interface');
236            paramTypeApiDecInfo.setPosition(ts.getLineAndCharacterOfPosition(arg.getSourceFile(), arg.getStart()));
237          }
238        }
239      }
240      this.recognizeArgument(arg, fileName);
241    });
242  }
243
244  modifyTypeReferenceSourceFileName(typeNameNode, apiDecInfo) {
245    const symbol = this.typeChecker.getSymbolAtLocation(typeNameNode);
246    if (symbol) {
247      const typeDec = symbol.declarations[0];
248      let importDec = typeDec;
249      while (importDec && !ts.isImportDeclaration(importDec)) {
250        importDec = importDec.parent;
251      }
252      if (!importDec) {
253        return;
254      }
255      const moduleSpecifier = importDec.moduleSpecifier;
256      this.saveApiDecInfo(moduleSpecifier, apiDecInfo, typeNameNode);
257    }
258  }
259
260  saveApiDecInfo(moduleSpecifier, apiDecInfo, typeNameNode) {
261    const specialInterfaceSet = new Set(['Callback', 'AsyncCallback']);
262    if (ts.isStringLiteral(moduleSpecifier, apiDecInfo)) {
263      const useTypeFileName = apiDecInfo.apiNode.getSourceFile().fileName;
264      const moduleRelativePaths = moduleSpecifier.getText().match(/^['"](.*)['"]$/);
265      if (moduleRelativePaths.length < 2) {
266        return;
267      }
268      const modulePath = path.resolve(path.dirname(useTypeFileName), `${moduleRelativePaths[1]}.d.ts`);
269      if (fs.existsSync(modulePath)) {
270        if (specialInterfaceSet.has(typeNameNode.getText())) {
271          apiDecInfo.apiText = this.getSpecialInterfaceText(modulePath, typeNameNode.getText());
272        }
273        const dtsPath = path.relative(this.systemRoot, modulePath).replace(/\\/g, '/');
274        apiDecInfo.dtsName = path.basename(modulePath);
275        apiDecInfo.packageName = path.relative(this.systemRoot, modulePath);
276        apiDecInfo.dtsPath = this.formatDtsPath(dtsPath);
277        apiDecInfo.typeName = apiDecInfo.propertyName;
278      }
279    }
280  }
281
282  getSpecialInterfaceText(modulePath, interfaceName) {
283    const fileContent = fs.readFileSync(modulePath, 'utf-8');
284    const apiFileName = path.basename(modulePath).replace('d.ts', '.ts');
285    const sourceFile = ts.createSourceFile(apiFileName, fileContent, ts.ScriptTarget.ES2017, true);
286    let interfaceText = '';
287    sourceFile.statements.forEach(stat => {
288      if (ts.isInterfaceDeclaration(stat) && stat.name.escapedText === interfaceName) {
289        interfaceText = stat.getText().split('{')[0];
290      }
291    });
292    return interfaceText;
293  }
294
295  formatDtsPath(dtsPath) {
296    if (dtsPath.indexOf('api/@internal/full/canvaspattern.d.ts') > -1) {
297      return dtsPath.replace('api/@internal/full/', 'interface/sdk-js/api/common/full/');
298    } else if (dtsPath.indexOf('api/@internal/full/featureability.d.ts') > -1) {
299      return dtsPath.replace('api/@internal/full/', '/interface/sdk-js/api/common/full/');
300    } else if (dtsPath.indexOf('api/internal/full') > -1) {
301      return dtsPath.replace('api/@internal/full/', '/interface/sdk-js/api/@internal/ets/');
302    } else if (dtsPath.indexOf('component/') > -1) {
303      return dtsPath.replace('component/', 'interface/sdk-js/api/@internal/component/ets/');
304    } else {
305      return path.join('/interface/sdk-js', dtsPath).replace(/\\/g, '/');
306    }
307  }
308
309  /**
310   * 识别UI组件及其属性
311   *
312   * @param {ts.Node} node
313   * @param {string} fileName
314   */
315  recognizeUIComponents(node, fileName) {
316    if (ts.isEtsComponentExpression(node)) {
317      // ETS组件的声明, EtsComponentExpression 表示有子组件的组件.
318      this.recognizeEtsComponentExpression(node, fileName);
319    } else if (ts.isCallExpression(node)) {
320      // 组件链式调用
321      this.recognizeComponentAttributeChain(node, fileName, []);
322    } else if (ts.isIfStatement(node)) {
323      this.recognizeIfStatement(node, fileName);
324    } else {
325      ts.forEachChild(node, (child) => {
326        this.recognizeUIComponents(child, fileName);
327      });
328    }
329  }
330
331  /**
332   * 识别条件渲染
333   * @param {ts.Node} node
334   * @param {string} fileName
335   */
336  recognizeIfStatement(node, fileName) {
337    this.recognizeArgument(node.expression, fileName);
338    const thenStatements = ts.isBlock(node) ? node.statements :
339      (ts.isBlock(node.thenStatement) ? node.thenStatement.statements : undefined);
340    if (thenStatements) {
341      thenStatements.forEach((child) => {
342        this.recognizeUIComponents(child, fileName);
343      });
344    }
345    if (node.elseStatement) {
346      this.recognizeIfStatement(node.elseStatement, fileName);
347    }
348  }
349
350  /**
351   * 识别组件链式调用中的API
352   * @param {ts.Node} node
353   * @param {string} fileName
354   */
355  recognizeComponentAttributeChain(node, fileName) {
356    if (ts.isCallExpression(node)) {
357      const chainResult = this.recognizeComponentAttributeChain(node.expression, fileName);
358      this.recognizeArguments(node.arguments, fileName);
359      return new ComponentAttrResult(chainResult.componentInfo, undefined);
360    } else if (ts.isPropertyAccessExpression(node)) {
361      const chainResult = this.recognizeComponentAttributeChain(node.expression, fileName);
362      const attrInfo = this.recognizeEtsComponentAndAttributeApi(node, fileName);
363      if (chainResult.componentInfo && attrInfo) {
364        attrInfo.setComponentName(chainResult.componentInfo.propertyName);
365      }
366      return new ComponentAttrResult(chainResult.componentInfo, attrInfo);
367    } else if (ts.isEtsComponentExpression(node)) {
368      return new ComponentAttrResult(this.recognizeEtsComponentExpression(node, fileName), undefined);
369    } else if (ts.isIdentifier(node)) {
370      return new ComponentAttrResult(this.recognizeEtsComponentAndAttributeApi(node, fileName), undefined);
371    } else {
372      return new ComponentAttrResult(undefined, undefined);
373    }
374  }
375
376  /**
377   * 识别ArkUI组件表达式
378   * @param {ts.EtsComponentExpression} node
379   * @param {string} fileName
380   * @returns
381   */
382  recognizeEtsComponentExpression(node, fileName) {
383    // node.expression is component name Identifier
384    const apiDecInfo = this.recognizeEtsComponentAndAttributeApi(node.expression, fileName);
385
386    if (node.arguments) {
387      this.recognizeComponentArguments(apiDecInfo, node.arguments, fileName);
388    }
389
390    if (node.body) {
391      node.body.statements.forEach((statement) => {
392        this.recognizeUIComponents(statement, fileName);
393      });
394    }
395
396    return apiDecInfo;
397  }
398
399  /**
400   * 识别组件入参
401   * @param {ApiDeclarationInformation} apiDecInfo
402   * @param {ts.Node[]} args
403   * @param {string} fileName
404   */
405  recognizeComponentArguments(apiDecInfo, args, fileName) {
406    // ForEach, LazyForEach
407    if (apiDecInfo && this.forEachComponents.has(apiDecInfo.propertyName)) {
408      args.forEach((arg, index) => {
409        // itemGenerator
410        if (index === 1) {
411          this.visitUIRenderNode(arg.body ? arg.body : arg, fileName);
412        } else {
413          this.recognizeArgument(arg, fileName);
414        }
415      });
416    } else {
417      this.recognizeArguments(args, fileName);
418    }
419  }
420
421  /**
422   * 识别参数中的API
423   * @param {ts.Node[]} args
424   * @param {string} fileName
425   */
426  recognizeArguments(args, fileName) {
427    args.forEach((arg) => {
428      this.recognizeArgument(arg, fileName);
429    });
430  }
431
432  recognizeArgument(node, fileName) {
433    if (!node) {
434      return;
435    }
436    this.recognizeNormal(node, fileName);
437    ts.forEachChild(node, (child) => {
438      this.recognizeArgument(child, fileName);
439    });
440  }
441
442
443  /**
444   * 获取组件或属性的API详情
445   *
446   * @param {ts.Identifier} node
447   * @param {string} fileName
448   * @returns {ApiDeclarationInformation | undefined} apiDecInfo
449   */
450  recognizeEtsComponentAndAttributeApi(node, fileName) {
451    // recognize component
452    const apiDecInfo = this.recognizeApiWithNode(node, fileName,
453      (node) => ts.isPropertyAccessExpression(node) ? node.name.getStart() : node.getStart());
454    return apiDecInfo;
455  }
456
457  /**
458   * 通过语义分析,识别继承关系中的系统API
459   *
460   * @param {ts.ClassDeclaration | ts.InterfaceDeclaration} node
461   * @param {ts.ClassDeclaration|ts.InterfaceDeclaration} node
462   */
463  recognizeHeritageClauses(node, fileName) {
464    if (!node.heritageClauses) {
465      return;
466    }
467    const nodeType = this.typeChecker.getTypeAtLocation(node);
468    const nodeSymbol = nodeType.getSymbol();
469    if (!nodeSymbol.members) {
470      return;
471    }
472    const heritagePropertyMap = this.collectAllHeritageMethods(node);
473    if (!nodeSymbol.valueDeclaration) {
474      return;
475    }
476    if (!nodeSymbol.valueDeclaration.heritageClauses) {
477      return;
478    }
479    if (heritagePropertyMap.size === 0) {
480      const extendNodes = nodeSymbol.valueDeclaration.heritageClauses[0].types;
481      this.getExtendClassPropertyMap(extendNodes, heritagePropertyMap);
482    }
483    if (heritagePropertyMap === 0) {
484      return;
485    }
486    nodeSymbol.members.forEach((memberSymbol, memberName) => {
487      if (heritagePropertyMap.has(memberName)) {
488        const apiDecInfo = this.getSdkApiDeclarationWithSymbol(heritagePropertyMap.get(memberName), undefined);
489        if (apiDecInfo) {
490          apiDecInfo.setPosition(ts.getLineAndCharacterOfPosition(node.getSourceFile(), memberSymbol.valueDeclaration.getStart()));
491          apiDecInfo.setSourceFileName(fileName);
492          this.addApiInformation(apiDecInfo);
493        }
494      }
495    });
496  }
497
498  /**
499   * 通过向上查找继承的节点,收集多次继承情况下的API
500   * @param {ts.Node} extendNodes
501   * @param {Map} extendClassPropertyMap
502   */
503  getExtendClassPropertyMap(extendNodes, extendClassPropertyMap) {
504    extendNodes.forEach(extendNode => {
505      const extendNodeType = this.typeChecker.getTypeAtLocation(extendNode);
506      if (!extendNodeType) {
507        return;
508      }
509      const extendNodeSymbol = extendNodeType.getSymbol();
510      if (!extendNodeSymbol) {
511        return;
512      }
513      const valueDeclaration = extendNodeSymbol.declarations[0];
514      if (valueDeclaration && !this.isSdkApi(valueDeclaration.getSourceFile().fileName) && extendNodeSymbol.valueDeclaration &&
515        extendNodeSymbol.valueDeclaration.heritageClauses) {
516        const parentNodes = extendNodeSymbol.valueDeclaration.heritageClauses[0].types;
517        this.getExtendClassPropertyMap(parentNodes, extendClassPropertyMap);
518      } else {
519        extendNodeSymbol.members.forEach((memberSymbol, memberName) => {
520          extendClassPropertyMap.set(memberName, memberSymbol);
521        });
522      }
523    });
524  }
525
526  /**
527   * 搜集所有父类(属于SDK中的API)的方法和属性。
528   *
529   * @param {ts.Node} node
530   * @returns Map
531   */
532  collectAllHeritageMethods(node) {
533    const heritageMembers = new Map();
534    if (!node.heritageClauses) {
535      return heritageMembers;
536    }
537    node.heritageClauses.forEach((heritage) => {
538      heritage.types.forEach((child) => {
539        const childSymbol = this.typeChecker.getSymbolAtLocation(child.expression);
540        if (!childSymbol) {
541          return;
542        }
543        const type = this.typeChecker.getTypeOfSymbolAtLocation(childSymbol, child);
544        const typeSymbol = type.getSymbol();
545        if (!typeSymbol) {
546          return;
547        }
548        if (!typeSymbol && childSymbol.members) {
549          childSymbol.members.forEach((memberSymbol, memberName) => {
550            heritageMembers.set(memberName, memberSymbol);
551          });
552          return;
553        }
554        const valueDeclaration = typeSymbol.valueDeclaration;
555        if (!valueDeclaration || !this.isSdkApi(valueDeclaration.getSourceFile().fileName)) {
556          return;
557        }
558        typeSymbol.members.forEach((memberSymbol, memberName) => {
559          heritageMembers.set(memberName, memberSymbol);
560        });
561      });
562    });
563    return heritageMembers;
564  }
565
566  /**
567   * 解析属性访问
568   *
569   * @param {ts.PropertyAccessExpression} node
570   */
571  recognizePropertyAccessExpression(node, fileName) {
572    return this.recognizeApiWithNode(node, fileName, (node) => node.name.getStart());
573  }
574
575  /**
576   * 获取 export {xx} 中xx的定义
577   * @param {ts.Symbol} symbol
578   */
579  getAliasExcludesRealValueDeclarations(symbol) {
580    const symbolName = symbol.escapedName;
581    const sourceFile = symbol.declarations[0].getSourceFile();
582    const realSymbol = sourceFile.locals ? sourceFile.locals.get(symbolName) : undefined;
583    return realSymbol ? realSymbol.valueDeclaration : undefined;
584  }
585
586  getApiRawText(node) {
587    switch (node.kind) {
588      case ts.SyntaxKind.ClassDeclaration:
589        return `class ${node.name ? node.name.getText() : ''}`;
590      case ts.SyntaxKind.InterfaceDeclaration:
591        return `interface ${node.name ? node.name.getText() : ''}`;
592      case ts.SyntaxKind.EnumDeclaration:
593        return `enum ${node.name ? node.name.getText() : ''}`;
594      case ts.SyntaxKind.ModuleDeclaration:
595        return `namespace ${node.name ? node.name.getText() : ''}`;
596      case node.getText() === 'AsyncCallback' || node.getText() === 'Callback':
597        return;
598      default:
599        return node.getText();
600    }
601  }
602
603  getApiTypeName(node) {
604    if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isEnumDeclaration(node) ||
605      ts.isModuleDeclaration(node)) {
606      return node.name ? node.name.getText() : '';
607    }
608    return undefined;
609  }
610
611  /**
612   * 获取节点定义在哪个文件哪个类型下。
613   *
614   * @param {ts.Symbol} type
615   */
616  getSdkApiDeclarationWithSymbol(symbol, node, useDeclarations) {
617    const sourceSymbol = symbol;
618    let valudeDec = sourceSymbol && sourceSymbol.valueDeclaration ? sourceSymbol.valueDeclaration :
619      (sourceSymbol.flags & ts.SymbolFlags.AliasExcludes ? this.getAliasExcludesRealValueDeclarations(symbol) : undefined);
620    valudeDec = !valudeDec && useDeclarations ? symbol.declarations[0] : valudeDec;
621    if (!valudeDec) {
622      return undefined;
623    }
624    if (valudeDec.kind === ts.SyntaxKind.TypeParameter) {
625      return undefined;
626    }
627    const sourceFile = valudeDec.getSourceFile();
628    if (!this.isSdkApi(sourceFile.fileName)) {
629      return undefined;
630    }
631    let apiDecInfo = undefined;
632    if (symbol.declarations.length > 1) {
633      apiDecInfo = this.getSdkApiFromMultiDeclarations(symbol, node);
634    }
635    return apiDecInfo ? apiDecInfo : this.getSdkApiFromValueDeclaration(valudeDec);
636  }
637
638  getSdkApiFromMultiDeclarations(symbol, node) {
639    if (!node) {
640      return undefined;
641    }
642    let callExpressionNode = node;
643    while (callExpressionNode && !ts.isCallExpression(callExpressionNode)) {
644      callExpressionNode = callExpressionNode.parent;
645    }
646    if (!callExpressionNode) {
647      return undefined;
648    }
649    const matchedDec = this.findBestMatchedDeclaration(callExpressionNode, symbol);
650    if (!matchedDec) {
651      return undefined;
652    }
653    return this.getSdkApiFromValueDeclaration(matchedDec);
654  }
655
656  /**
657   * 查找参数个数匹配,参数类型匹配的API
658   * @param {number} argumentLength
659   * @param {ts.Symbol} symbol
660   * @returns api declaration node.
661   */
662  findBestMatchedDeclaration(callExpressionNode, symbol) {
663    const callExpArgLen = callExpressionNode.arguments.length;
664    let matchedDecs = [];
665    for (let dec of symbol.declarations) {
666      if (dec.parameters && dec.parameters.length === callExpArgLen) {
667        matchedDecs.push(dec);
668      }
669    }
670    if (matchedDecs.length === 1) {
671      return matchedDecs[0];
672    }
673    if ('on' === callExpressionNode.expression.name.getText()) {
674      return matchedDecs[0];
675    }
676    const lastArgument = callExpressionNode.arguments[callExpArgLen - 1];
677    if (this.isAsyncCallbackCallExp(lastArgument)) {
678      matchedDecs = matchedDecs.filter((value) => {
679        return this.isAsyncCallbackApi(value);
680      });
681    } else {
682      matchedDecs = matchedDecs.filter((value) => {
683        return !this.isAsyncCallbackApi(value);
684      });
685    }
686    return matchedDecs.length > 0 ? matchedDecs[0] : undefined;
687  }
688
689  isAsyncCallbackCallExp(node) {
690    const type = this.typeChecker.getTypeAtLocation(node);
691    if (!type || !type.symbol || !type.symbol.valueDeclaration) {
692      return false;
693    }
694    const typeValueDec = type.symbol.valueDeclaration;
695    return ts.isArrowFunction(typeValueDec) || ts.isMethodDeclaration(typeValueDec) ||
696      ts.isFunctionDeclaration(typeValueDec) || ts.isMethodSignature(typeValueDec) ||
697      ts.isFunctionExpression(typeValueDec);
698  }
699
700  isAsyncCallbackApi(declaration) {
701    if (!declaration.parameters) {
702      return false;
703    }
704    const lastArgument = declaration.parameters[declaration.parameters.length - 1];
705    const argumentType = this.typeChecker.getTypeAtLocation(lastArgument);
706    if (argumentType && argumentType.symbol) {
707      return argumentType.symbol.escapedName === 'AsyncCallback';
708    } else {
709      if (!lastArgument.type || !lastArgument.type.typeName) {
710        return false;
711      }
712      return 'AsyncCallback' === lastArgument.type.typeName.getText();
713    }
714  }
715
716  getSdkApiFromValueDeclaration(valudeDec) {
717    const sourceFile = valudeDec.getSourceFile();
718    const apiInfo = new ApiDeclarationInformation();
719    const dtsPath = sourceFile.fileName.replace(this.systemRoot.replace(/\\/g, '/'), '');
720    apiInfo.setPropertyName(this.getMethodOrTypeName(valudeDec));
721    apiInfo.setApiRawText(this.getApiRawText(valudeDec));
722    apiInfo.setPackageName(this.getPackageName(sourceFile.fileName));
723    apiInfo.setSdkFileName(path.basename(sourceFile.fileName));
724    apiInfo.setTypeName(this.getApiTypeName(valudeDec));
725    apiInfo.setApiNode(valudeDec);
726    apiInfo.setApiType(this.getApiTypeName(valudeDec));
727    apiInfo.setDtsPath(this.formatDtsPath(dtsPath));
728    apiInfo.setCompletedText(this.getApiText(valudeDec));
729
730    let curDeclaration = valudeDec.parent;
731    while (!ts.isSourceFile(curDeclaration)) {
732      const nodeName = this.getMethodOrTypeName(curDeclaration);
733      if (nodeName) {
734        apiInfo.setTypeName(nodeName);
735        apiInfo.setQualifiedTypeName(nodeName);
736      }
737      curDeclaration = curDeclaration.parent;
738    }
739    const qualifiedName = apiInfo.qualifiedTypeName ?
740      `${apiInfo.qualifiedTypeName}.${apiInfo.propertyName}` : `${apiInfo.propertyName}`;
741    apiInfo.setQualifiedName(qualifiedName);
742    return this.fillJSDocInformation(apiInfo, valudeDec);
743  }
744
745  getApiType(valudeDec) {
746    let type = apiType.get(valudeDec.kind);
747    if (ts.isPropertySignature(valudeDec) && valudeDec.type && ts.isFunctionTypeNode(valudeDec.type)) {
748      type = 'method';
749    }
750    return type;
751  }
752
753  getApiText(node) {
754    switch (node.kind) {
755      case ts.SyntaxKind.ClassDeclaration:
756        return node.getText().split('{')[0];
757      case ts.SyntaxKind.InterfaceDeclaration:
758        return node.getText().split('{')[0];
759      case ts.SyntaxKind.EnumDeclaration:
760        return node.getText().split('{')[0];
761      case ts.SyntaxKind.ModuleDeclaration:
762        return node.getText().split('{')[0];
763      default:
764        return node.getText();
765    }
766  }
767
768  /**
769   * 补充JsDoc中的信息。
770   *
771   * @param {ApiDeclarationInformation} apiInfo
772   * @param {ts.Node} node
773   * @returns
774   */
775  fillJSDocInformation(apiInfo, node) {
776    this.forEachJsDocTags(node, (tag) => {
777      (this.fillDeprecatedInfo(tag, apiInfo) || this.fillUseInsteadInfo(tag, apiInfo));
778    });
779    if (!apiInfo.deprecated) {
780      const thiz = this;
781      function reachJsDocTag(tag) {
782        thiz.fillDeprecatedInfo(tag, apiInfo);
783        return apiInfo.deprecated;
784      }
785
786      function reachParent(parent) {
787        thiz.forEachJsDocTags(parent, reachJsDocTag);
788        return apiInfo.deprecated;
789      }
790      this.forEachNodeParent(node, reachParent);
791    }
792    return apiInfo;
793  }
794
795  fillUseInsteadInfo(tag, apiInfo) {
796    if (tag.kind === ts.SyntaxKind.JSDocTag &&
797      tag.tagName.getText() === 'useinstead') {
798      apiInfo.setUseInstead(tag.comment);
799      return true;
800    }
801    return false;
802  }
803
804  fillDeprecatedInfo(tag, apiInfo) {
805    if (tag.kind === ts.SyntaxKind.JSDocDeprecatedTag) {
806      apiInfo.setDeprecated(tag.comment);
807      return true;
808    }
809    return false;
810  }
811
812  forEachJsDocTags(node, callback) {
813    if (!node.jsDoc || !callback) {
814      return;
815    }
816    const latestJsDoc = node.jsDoc[node.jsDoc.length - 1];
817    if (!latestJsDoc.tags) {
818      return;
819    }
820    for (const tag of latestJsDoc.tags) {
821      if (callback(tag)) {
822        break;
823      };
824    };
825  }
826
827  forEachNodeParent(node, callback) {
828    if (!node || !callback) {
829      return;
830    }
831    let curNode = node.parent;
832    while (curNode) {
833      if (callback(curNode)) {
834        break;
835      }
836      curNode = curNode.parent;
837    }
838  }
839
840  getMethodOrTypeName(node) {
841    return node.name ? node.name.getText() : undefined;
842  }
843
844  getPackageName(filePath) {
845    const isArkUI = FileSystem.isInDirectory(path.resolve(this.systemRoot, 'component'), filePath);
846    return isArkUI ? 'ArkUI' : path.relative(this.systemRoot, filePath);
847  }
848}
849
850const apiType = new Map([
851  [ts.SyntaxKind.FunctionDeclaration, 'method'],
852  [ts.SyntaxKind.MethodSignature, 'method'],
853  [ts.SyntaxKind.MethodDeclaration, 'method'],
854  [ts.SyntaxKind.EnumMember, 'enum_instance'],
855  [ts.SyntaxKind.PropertySignature, 'field'],
856  [ts.SyntaxKind.VariableStatement, 'constant'],
857  [ts.SyntaxKind.VariableDeclaration, 'constant'],
858  [ts.SyntaxKind.VariableDeclarationList, 'constant'],
859  [ts.SyntaxKind.TypeAliasDeclaration, 'type'],
860  [ts.SyntaxKind.ClassDeclaration, 'class'],
861  [ts.SyntaxKind.InterfaceDeclaration, 'interface'],
862  [ts.SyntaxKind.EnumDeclaration, 'enum_class'],
863  [ts.SyntaxKind.PropertyDeclaration, 'field']
864]);
865
866class ComponentAttrResult {
867  constructor(componentInfo, attrInfo) {
868    this.componentInfo = componentInfo;
869    this.attrInfo = attrInfo;
870  }
871}
872
873class ApiDeclarationInformation {
874  constructor() {
875    this.dtsName = '';
876    this.packageName = '';
877    this.propertyName = '';
878    this.qualifiedTypeName = '';
879    this.pos = '';
880    this.sourceFileName = '';
881    this.deprecated = '';
882    this.apiRawText = '';
883    this.qualifiedName = '';
884    this.useInstead = '';
885    this.typeName = '';
886  }
887
888  setSdkFileName(fileName) {
889    this.dtsName = fileName;
890  }
891
892  setPackageName(packageName) {
893    this.packageName = packageName;
894  }
895
896  setPropertyName(propertyName) {
897    this.propertyName = propertyName;
898  }
899
900  setQualifiedTypeName(typeName) {
901    if (!this.qualifiedTypeName) {
902      this.qualifiedTypeName = typeName;
903    } else {
904      this.qualifiedTypeName = `${typeName}.${this.qualifiedTypeName}`;
905    }
906  }
907
908  setTypeName(typeName) {
909    if (typeName && (!this.typeName || this.typeName === '')) {
910      this.typeName = typeName;
911    }
912  }
913
914  setPosition(pos) {
915    const { line, character } = pos;
916    this.pos = `${line + 1},${character + 1}`;
917  }
918
919  setSourceFileName(sourceFileName) {
920    this.sourceFileName = sourceFileName;
921  }
922
923  /**
924   * 设置废弃版本号
925   *
926   * @param {string} deprecated
927   */
928  setDeprecated(deprecated) {
929    const regExpResult = deprecated.match(/\s*since\s*(\d)+.*/);
930    if (regExpResult !== null && regExpResult.length === 2) {
931      this.deprecated = regExpResult[1];
932    }
933  }
934
935  setApiRawText(apiRawText) {
936    this.apiRawText = apiRawText.replace(/\;/g, '');
937  }
938
939  setQualifiedName(qualifiedName) {
940    this.qualifiedName = qualifiedName;
941  }
942
943  setUseInstead(useInstead) {
944    this.useInstead = useInstead;
945  }
946
947  setComponentName(componentName) {
948    this.componentName = componentName;
949  }
950
951  setApiNode(node) {
952    this.apiNode = node;
953  }
954
955  setApiType(apiType) {
956    this.apiType = apiType;
957  }
958
959  setDtsPath(dtsPath) {
960    this.dtsPath = dtsPath;
961  }
962
963  setCompletedText(completedText) {
964    this.apiText = completedText;
965  }
966}
967
968exports.SystemApiRecognizer = SystemApiRecognizer;