• 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
16import fs from 'fs';
17import path from 'path';
18import ts from 'typescript';
19import _ from 'lodash';
20
21import { NodeProcessorHelper, parserParam } from './NodeProcessor';
22import { ResultsProcessHelper } from './ResultsProcess';
23import { ApiType, BasicApiInfo, ApiInfo } from '../../typedef/parser/ApiInfoDefination';
24import { StringConstant } from '../../utils/Constant';
25import { FileUtils } from '../../utils/FileUtils';
26import { DiffHelper } from '../diff/diff';
27import * as ResultsInfo from '../../typedef/parser/ResultsInfo';
28
29/**
30 * 解析d.ts工具类
31 */
32export class Parser {
33  /**
34   * 根据传入的文件目录,对目录下的d.ts文件进行解析,
35   * 并将其整合到一个map对象中
36   *
37   * @param { string } fileDir 传入的文件目录
38   * @param { string } [collectFile = ''] 统计的文件或目录
39   * @returns { FilesMap } 返回解析后的map对象
40   */
41  static parseDir(fileDir: string, collectFile: string = ''): FilesMap {
42    const files: Array<string> = FileUtils.readFilesInDir(fileDir, (name) => {
43      return name.endsWith(StringConstant.DTS_EXTENSION) || name.endsWith(StringConstant.DETS_EXTENSION);
44    });
45    if (Boolean(process.env.NEED_DETECTION)) {
46      parserParam.setFileDir(fileDir);
47      parserParam.setRootNames(files);
48    }
49    const apiMap: FilesMap = new Map();
50    let collectFiles: Array<string> = [];
51    if (collectFile === '') {
52      collectFiles = files;
53    } else if (FileUtils.isDirectory(collectFile)) {
54      collectFiles = FileUtils.readFilesInDir(collectFile, (name) => {
55        return name.endsWith(StringConstant.DTS_EXTENSION) || name.endsWith(StringConstant.DETS_EXTENSION);
56      });
57    } else if (FileUtils.isFile(collectFile)) {
58      collectFiles = [collectFile];
59    }
60    collectFiles.forEach((filePath: string) => {
61      Parser.parseFile(fileDir, filePath, apiMap);
62    });
63    return apiMap;
64  }
65
66  /**
67   * 根据文件名解析api信息
68   *
69   * @param { string } fileDir 文件的跟路径
70   * @param { string } filePath 文件路径名
71   * @param { FilesMap } [apiMap] 返回处理后的存储api信息的map对象
72   * @returns { FilesMap } 返回处理后的存储api信息的map对象
73   */
74  static parseFile(fileDir: string, filePath: string, apiMap?: FilesMap): FilesMap {
75    if (!fs.existsSync(filePath)) {
76      return new Map();
77    }
78    if (Boolean(process.env.NEED_DETECTION)) {
79      parserParam.setFilePath(filePath);
80    }
81    const fileContent: string = fs.readFileSync(filePath, StringConstant.UTF8);
82    let relFilePath: string = '';
83    relFilePath = path.relative(fileDir, filePath);
84    const fileName = path
85      .basename(filePath)
86      .replace(new RegExp(StringConstant.DTS_EXTENSION, 'g'), StringConstant.TS_EXTENSION)
87      .replace(new RegExp(StringConstant.DETS_EXTENSION, 'g'), StringConstant.ETS_EXTENSION);
88    const sourceFile: ts.SourceFile = ts.createSourceFile(fileName, fileContent, ts.ScriptTarget.ES2017, true);
89    const fileArr: Array<string> = [filePath];
90    sourceFile.statements.forEach((statement: ts.Statement) => {
91      if (ts.isImportDeclaration(statement) && statement.moduleSpecifier.getText().startsWith('./', 1)) {
92        fileArr.push(path.resolve(filePath, '..', statement.moduleSpecifier.getText().replace(/'|"/g, '')));
93      }
94    });
95    if (Boolean(process.env.NEED_DETECTION)) {
96      parserParam.setProgram([fileDir]);
97    }
98    const sourceFileInfo: ApiInfo = new ApiInfo(ApiType.SOURCE_FILE, sourceFile, undefined);
99    sourceFileInfo.setFilePath(relFilePath);
100    sourceFileInfo.setFileAbsolutePath(filePath);
101    sourceFileInfo.setApiName(relFilePath);
102    sourceFileInfo.setIsStruct(filePath.endsWith(StringConstant.ETS_EXTENSION));
103    const currentApiMap: FileInfoMap = new Map();
104    currentApiMap.set(StringConstant.SELF, [sourceFileInfo]);
105    NodeProcessorHelper.processReference(sourceFile, currentApiMap, sourceFileInfo);
106    sourceFile.forEachChild((cNode: ts.Node) => {
107      NodeProcessorHelper.processNode(cNode, currentApiMap, sourceFileInfo);
108    });
109    if (!apiMap) {
110      apiMap = new Map();
111    }
112    apiMap.set(relFilePath, currentApiMap);
113    return apiMap;
114  }
115
116  /**
117   * 根据api节点的层级关系获取解析后的api对象
118   *
119   * @param { string[] } locations api节点的层级关系
120   * @param { FilesMap } apiMap api所在的map对象
121   * @returns { BasicApiInfo[] | undefined } 查询结果
122   */
123  static getApiInfo(locations: string[], apiMap: FilesMap, isAllSheet: boolean): BasicApiInfo[] {
124    const apiInfos: BasicApiInfo[] = [];
125    if (locations.length === 0) {
126      return apiInfos;
127    }
128    let currentMap: Map<string, object> = apiMap;
129    for (let i = 0; i < locations.length; i++) {
130      const key: string = locations[i];
131      const tempMap: object | undefined = currentMap.get(key);
132      if (!tempMap) {
133        return apiInfos;
134      }
135      currentMap = tempMap as Map<string, object>;
136    }
137    const currentMapValue: object | undefined = currentMap.get(StringConstant.SELF);
138    if (!currentMapValue) {
139      return apiInfos;
140    }
141    apiInfos.push(...(currentMapValue as BasicApiInfo[]));
142    if (isAllSheet) {
143      const isSameNameApi: boolean = DiffHelper.judgeIsSameNameFunction(apiInfos);
144      apiInfos.forEach((apiInfo: BasicApiInfo) => {
145        apiInfo.setIsSameNameFunction(isSameNameApi);
146      });
147    }
148    return apiInfos;
149  }
150
151  /**
152   * 该方法主要目的为了后续验证第一步基础解析的结果是否正确
153   *
154   * @param { FilesMap } apiMap 根据文件解析的map对象
155   * @returns { string } 返回解析后的字符串
156   */
157  static getParseResults(apiMap: FilesMap): string {
158    const clonedApiMap: FilesMap = _.cloneDeep(apiMap);
159    const apiResults: Map<string, BasicApiInfo[]> = new Map();
160    // map的第一层key均为文件路径名
161    for (const filePath of clonedApiMap.keys()) {
162      const fileMap: FileInfoMap = apiMap.get(filePath) as FileInfoMap;
163      const apis: BasicApiInfo[] = ResultsProcessHelper.processFileApiMap(fileMap);
164      apiResults.set(filePath, apis);
165    }
166    return JSON.stringify(Object.fromEntries(apiResults), null, 2);
167  }
168
169  static getParseEachSince(apiMap: FilesMap): string {
170    const clonedApiMap: FilesMap = _.cloneDeep(apiMap);
171    const apiResults: Map<string, Array<ResultsInfo.BasicApiInfo>> = new Map();
172    // map的第一层key均为文件路径名
173    for (const filePath of clonedApiMap.keys()) {
174      const fileMap: FileInfoMap = apiMap.get(filePath) as FileInfoMap;
175      const sinceArr: Array<ResultsInfo.BasicApiInfo> = ResultsProcessHelper.processFileApiMapForEachSince(fileMap);
176      apiResults.set(filePath, sinceArr);
177      return JSON.stringify(sinceArr, null, 2);
178    }
179    return '';
180  }
181
182  /**
183   * 获取解析结果的所有api
184   *
185   * @param {FilesMap} parseResult 解析结果
186   * @return  {BasicApiInfo[]} 遍历平铺所有api
187   */
188  static getAllBasicApi(parseResult: FilesMap): BasicApiInfo[] {
189    let apiInfos: BasicApiInfo[] = [];
190    // map的第一层key均为文件路径名
191    for (const filePath of parseResult.keys()) {
192      const fileMap: FileInfoMap = parseResult.get(filePath) as FileInfoMap;
193      const apis: BasicApiInfo[] = ResultsProcessHelper.processFileApiMapForGetBasicApi(fileMap);
194      apiInfos = apiInfos.concat(apis);
195    }
196    return apiInfos;
197  }
198}
199
200export type BasicApiInfoMap = Map<'_self', BasicApiInfo[]>;
201
202export type ApiInfosMap = Map<string, BasicApiInfoMap | BasicApiInfo[]>;
203
204export type FileInfoMap = Map<string, ApiInfosMap | BasicApiInfo[]>;
205
206export type FilesMap = Map<string, FileInfoMap>;
207