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