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