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