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