• 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 */
15import ExcelJS from 'exceljs';
16import path from 'path';
17import fs from 'fs';
18import { EnumUtils } from '../utils/EnumUtils';
19import { FileUtils } from '../utils/FileUtils';
20import { LogUtil } from '../utils/logUtil';
21import { FilesMap, Parser } from '../coreImpl/parser/parser';
22import { DiffHelper } from '../coreImpl/diff/diff';
23import { BasicDiffInfo, diffTypeMap, ApiDiffType } from '../typedef/diff/ApiInfoDiff';
24import { WriterHelper } from './writer';
25import { LocalEntry } from '../coreImpl/checker/local_entry';
26import { ApiResultSimpleInfo } from '../typedef/checker/result_type';
27import { NumberConstant } from '../utils/Constant';
28import { ApiStatisticsHelper } from '../coreImpl/statistics/Statistics';
29import { ApiStatisticsInfo } from '../typedef/statistics/ApiStatistics';
30import { SyscapProcessorHelper } from '../coreImpl/diff/syscapFieldProcessor';
31import { FunctionUtils } from '../utils/FunctionUtils';
32
33/**
34 * 工具名称的枚举值,用于判断执行哪个工具
35 *
36 * @enum { string }
37 */
38export enum toolNameType {
39  /**
40   * 统计工具
41   */
42  COOLECT = 'collect',
43  /**
44   * 检查工具
45   */
46  CHECK = 'check',
47  /**
48   * diff工具
49   */
50  DIFF = 'diff',
51}
52
53/**
54 * 工具名称的set集合,通过enum生成
55 */
56export const toolNameSet: Set<string> = new Set(EnumUtils.enum2arr(toolNameType));
57
58/**
59 * 输出文件格式,用于判断输出什么类型的文件
60 *
61 * @enum { string }
62 */
63export enum formatType {
64  JSON = 'json',
65  EXCEL = 'excel',
66  CHANGELOG = 'changelog',
67}
68
69/**
70 * 输出文件格式的set集合,通过enum生成
71 */
72export const formatSet: Set<string> = new Set(EnumUtils.enum2arr(formatType));
73
74export const Plugin: PluginType = {
75  pluginOptions: {
76    name: 'parser',
77    version: '0.1.0',
78    description: 'Compare the parser the SDKS',
79    commands: [
80      {
81        isRequiredOption: true,
82        options: [`-N,--tool-name <${[...toolNameSet]}>`, 'tool name ', 'collect'],
83      },
84      {
85        isRequiredOption: false,
86        options: ['-C,--collect-path <string>', 'collect api path', './api'],
87      },
88      {
89        isRequiredOption: false,
90        options: ['--old <string>', 'diff old sdk path', './api'],
91      },
92      {
93        isRequiredOption: false,
94        options: ['--new <string>', 'diff new sdk path', './api'],
95      },
96      {
97        isRequiredOption: false,
98        options: ['--old-version <string>', 'old sdk version', '0'],
99      },
100      {
101        isRequiredOption: false,
102        options: ['--new-version <string>', 'new sdk version', '0'],
103      },
104      {
105        isRequiredOption: false,
106        options: ['--output <string>', 'output file path', './'],
107      },
108      {
109        isRequiredOption: false,
110        options: [`--format <${[...formatSet]}>`, 'output file format', 'json'],
111      },
112      {
113        isRequiredOption: false,
114        options: ['--changelogUrl <string>', 'changelog url', ''],
115      },
116    ],
117  },
118
119  start: async function (argv: OptionObjType) {
120    const toolName: toolNameType = argv.toolName;
121    const method: ToolNameMethodType | undefined = toolNameMethod.get(toolName);
122    if (!method) {
123      LogUtil.i(
124        'CommandArgs',
125        `tool-name may use error name or don't have function,tool-name can use 'collect' or 'diff'`
126      );
127      return;
128    }
129    const options: OptionObjType = {
130      toolName: toolName,
131      collectPath: argv.collectPath,
132      old: argv.old,
133      new: argv.new,
134      oldVersion: argv.oldVersion,
135      newVersion: argv.newVersion,
136      output: argv.output,
137      format: argv.format,
138      changelogUrl: argv.changelogUrl,
139    };
140    const methodInfos: ToolNameValueType = method(options);
141
142    outputInfos(methodInfos.data, options, methodInfos.callback);
143  },
144  stop: function () {
145    LogUtil.i('commander', `elapsed time: ${Date.now() - startTime}`);
146  },
147};
148let startTime = Date.now();
149
150/**
151 * 工具获取完数据之后,根据format和其他数据处理输出
152 *
153 * @param {ToolReturnData} infos 工具返回的数据
154 * @param {OptionObjType} options 传入的命令参数
155 * @param {(ToolNameExcelCallback  | undefined)} callback 导出excel的回调
156 */
157function outputInfos(infos: ToolReturnData, options: OptionObjType, callback: ToolNameExcelCallback | undefined): void {
158  const format = options.format;
159  if (!format) {
160    return;
161  }
162  switch (format) {
163    case formatType.JSON:
164      WriterHelper.JSONReporter(
165        String(infos[0]),
166        options.output,
167        `${options.toolName}_${options.oldVersion}_${options.newVersion}.json`
168      );
169      break;
170    case formatType.EXCEL:
171      WriterHelper.ExcelReporter(infos, options.output, `${options.toolName}.xlsx`, callback);
172      break;
173    case formatType.CHANGELOG:
174      WriterHelper.JSONReporter(String(infos[0]), options.output, `${options.toolName}.json`);
175      break;
176    default:
177      break;
178  }
179}
180
181/**
182 * 收集api工具调用方法
183 *
184 * @param { OptionObjType } options
185 * @return { ToolNameValueType }
186 */
187function collectApi(options: OptionObjType): ToolNameValueType {
188  const fileDir: string = path.resolve(FileUtils.getBaseDirName(), options.collectPath);
189  let allApis: FilesMap;
190  try {
191    if (FileUtils.isDirectory(fileDir)) {
192      allApis = Parser.parseDir(fileDir);
193    } else {
194      allApis = Parser.parseFile(path.resolve(fileDir, '..'), fileDir);
195    }
196    const fileContent: string = Parser.getParseResults(allApis);
197    if (options.format === 'excel') {
198      const allApiStatisticsInfos: ApiStatisticsInfo[] | undefined =
199        ApiStatisticsHelper.getApiStatisticsInfos(allApis).allApiStatisticsInfos;
200      if (allApiStatisticsInfos) {
201        WriterHelper.ExcelReporter(
202          allApiStatisticsInfos,
203          options.output,
204          `all_${options.toolName}.xlsx`,
205          collectApiCallback as ToolNameExcelCallback
206        );
207      }
208    }
209
210    return {
211      data:
212        options.format === 'excel' ?
213          ApiStatisticsHelper.getApiStatisticsInfos(allApis).apiStatisticsInfos :
214          [fileContent],
215      callback: collectApiCallback as ToolNameExcelCallback,
216    };
217  } catch (exception) {
218    const error = exception as Error;
219    LogUtil.e(`error collect`, error.stack ? error.stack : error.message);
220    return {
221      data: [],
222      callback: collectApiCallback as ToolNameExcelCallback,
223    };
224  }
225}
226
227function collectApiCallback(apiData: ApiStatisticsInfo[], sheet: ExcelJS.Worksheet): void {
228  const apiRelationsSet: Set<string> = new Set();
229  const subsystemMap: Map<string, string> = FunctionUtils.readSubsystemFile().subsystemMap;
230  sheet.name = 'JsApi';
231  sheet.views = [{ xSplit: 1 }];
232  sheet.getRow(1).values = [
233    '模块名',
234    '类名',
235    '方法名',
236    '函数',
237    '类型',
238    '起始版本',
239    '废弃版本',
240    'syscap',
241    '错误码',
242    '是否为系统API',
243    '模型限制',
244    '权限',
245    '是否支持跨平台',
246    '是否支持卡片应用',
247    '是否为高阶API',
248    '装饰器',
249    '文件路径',
250    '子系统',
251  ];
252  let lineNumber = 2;
253  apiData.forEach((apiInfo: ApiStatisticsInfo) => {
254    const apiRelations: string = `${apiInfo.getHierarchicalRelations()},${apiInfo.getDefinedText()}`;
255    if (apiRelationsSet.has(apiRelations)) {
256      return;
257    }
258
259    sheet.getRow(lineNumber).values = [
260      apiInfo.getPackageName(),
261      apiInfo.getParentModuleName(),
262      apiInfo.getApiName(),
263      apiInfo.getDefinedText(),
264      apiInfo.getApiType(),
265      apiInfo.getSince() === '-1' ? '' : apiInfo.getSince(),
266      apiInfo.getDeprecatedVersion() === '-1' ? '' : apiInfo.getDeprecatedVersion(),
267      apiInfo.getSyscap(),
268      apiInfo.getErrorCodes().join() === '-1' ? '' : apiInfo.getErrorCodes().join(),
269      apiInfo.getApiLevel(),
270      apiInfo.getModelLimitation(),
271      apiInfo.getPermission(),
272      apiInfo.getIsCrossPlatForm(),
273      apiInfo.getIsForm(),
274      apiInfo.getIsAutomicService(),
275      apiInfo.getDecorators()?.join(),
276      apiInfo.getFilePath(),
277      subsystemMap.get(FunctionUtils.handleSyscap(apiInfo.getSyscap())),
278    ];
279    lineNumber++;
280    apiRelationsSet.add(apiRelations);
281  });
282}
283/**
284 * 收集api工具调用方法
285 *
286 * @param { OptionObjType } options
287 * @return { ToolNameValueType }
288 */
289function checkApi(options: OptionObjType): ToolNameValueType {
290  let allApis: FilesMap;
291  try {
292    let fileContent: ApiResultSimpleInfo[] = [];
293    if (process.env.NODE_ENV === 'development') {
294      fileContent = LocalEntry.checkEntryLocal();
295    } else if (process.env.NODE_ENV === 'production') {
296    }
297    let finalData: (string | ApiResultSimpleInfo)[] = [];
298    if (options.format === formatType.JSON) {
299      finalData = [JSON.stringify(fileContent, null, NumberConstant.INDENT_SPACE)];
300    } else {
301      finalData = fileContent;
302    }
303    return {
304      data: finalData,
305    };
306  } catch (exception) {
307    const error = exception as Error;
308    LogUtil.e('error collect', error.stack ? error.stack : error.message);
309    return {
310      data: [],
311    };
312  }
313}
314
315/**
316 * diffApi工具调用方法
317 *
318 * @param { OptionObjType } options
319 * @return { ToolNameValueType }
320 */
321function diffApi(options: OptionObjType): ToolNameValueType {
322  const oldFileDir: string = path.resolve(FileUtils.getBaseDirName(), options.old);
323  const newFileDir: string = path.resolve(FileUtils.getBaseDirName(), options.new);
324  const status: fs.Stats = fs.statSync(oldFileDir);
325  let data: BasicDiffInfo[] = [];
326  try {
327    if (status.isDirectory()) {
328      const oldSDKApiMap: FilesMap = Parser.parseDir(oldFileDir);
329      const newSDKApiMap: FilesMap = Parser.parseDir(newFileDir);
330      data = DiffHelper.diffSDK(oldSDKApiMap, newSDKApiMap);
331    } else {
332      const oldSDKApiMap: FilesMap = Parser.parseFile(path.resolve(oldFileDir, '..'), oldFileDir);
333      const newSDKApiMap: FilesMap = Parser.parseFile(path.resolve(newFileDir, '..'), newFileDir);
334      data = DiffHelper.diffSDK(oldSDKApiMap, newSDKApiMap);
335    }
336    let finalData: (string | BasicDiffInfo)[] = [];
337    if (options.format === formatType.JSON) {
338      finalData = [JSON.stringify(data, null, NumberConstant.INDENT_SPACE)];
339    } else {
340      finalData = data;
341    }
342    return {
343      data: finalData,
344      callback: diffApiCallback as ToolNameExcelCallback,
345    };
346  } catch (exception) {
347    const error = exception as Error;
348    LogUtil.e('error diff', error.stack ? error.stack : error.message);
349    return {
350      data: [],
351      callback: diffApiCallback as ToolNameExcelCallback,
352    };
353  }
354}
355
356/**
357 * diffApi工具导出excel时的回调方法
358 *
359 * @param {BasicDiffInfo[]} data diffApi工具获取到的数据
360 * @param {ExcelJS.Worksheet} sheet ExcelJS构建的Worksheet对象
361 */
362function diffApiCallback(data: BasicDiffInfo[], sheet: ExcelJS.Worksheet, dest?: string): void {
363  sheet.name = 'api差异';
364  sheet.views = [{ xSplit: 1 }];
365  sheet.getRow(1).values = ['操作标记', '差异项-旧版本', '差异项-新版本', 'd.ts文件', '归属子系统'];
366  data.forEach((diffInfo: BasicDiffInfo, index: number) => {
367    const dtsName = diffInfo.getNewDtsName() ? diffInfo.getNewDtsName() : diffInfo.getOldDtsName();
368    sheet.getRow(index + NumberConstant.LINE_IN_EXCEL).values = [
369      diffTypeMap.get(diffInfo.getDiffType()),
370      joinOldMessage(diffInfo),
371      joinNewMessage(diffInfo),
372      dtsName.replace(/\\/g, '/'),
373      SyscapProcessorHelper.matchSubsystem(diffInfo),
374    ];
375  });
376
377  WriterHelper.MarkdownReporter.writeInMarkdown(data, dest);
378}
379
380export function joinOldMessage(diffInfo: BasicDiffInfo): string {
381  if (diffInfo.getDiffMessage() === diffTypeMap.get(ApiDiffType.ADD)) {
382    return 'NA';
383  }
384  let oldDescription: string = '';
385  const relation: string[] = diffInfo.getOldHierarchicalRelations();
386  const parentModuleName: string = diffInfo.getParentModuleName(relation);
387  oldDescription =
388    diffInfo.getOldDescription() === '-1' || !diffInfo.getOldDescription() ? 'NA' : diffInfo.getOldDescription();
389  return `类名:${parentModuleName};\n` + `API声明:${diffInfo.getOldApiDefinedText()}\n差异内容:${oldDescription}`;
390}
391
392export function joinNewMessage(diffInfo: BasicDiffInfo): string {
393  if (diffInfo.getDiffMessage() === diffTypeMap.get(ApiDiffType.REDUCE)) {
394    return 'NA';
395  }
396  let newDescription: string = '';
397  const relation: string[] = diffInfo.getNewHierarchicalRelations();
398  const parentModuleName: string = diffInfo.getParentModuleName(relation);
399  newDescription =
400    diffInfo.getNewDescription() === '-1' || !diffInfo.getNewDescription() ? 'NA' : diffInfo.getNewDescription();
401  return `类名:${parentModuleName};\n` + `API声明:${diffInfo.getNewApiDefinedText()}\n差异内容:${newDescription}`;
402}
403
404/**
405 * 工具名称对应执行的方法
406 */
407export const toolNameMethod: Map<string, ToolNameMethodType> = new Map([
408  [toolNameType.COOLECT, collectApi],
409  [toolNameType.CHECK, checkApi],
410  [toolNameType.DIFF, diffApi],
411]);
412
413/**
414 * 命令传入参数
415 */
416export type OptionObjType = {
417  toolName: toolNameType;
418  collectPath: string;
419  old: string;
420  new: string;
421  oldVersion: string;
422  newVersion: string;
423  output: string;
424  format: formatType;
425  changelogUrl: string;
426};
427
428/**
429 * 各个工具当输出为excel时的回调方法
430 *
431 * @param { ToolReturnData } data 工具获取到的数据
432 * @param { ExcelJS.Worksheet } sheet ExcelJS构建的Worksheet对象
433 */
434export type ToolNameExcelCallback = (data: ToolReturnData, sheet: ExcelJS.Worksheet, dest?: string) => void;
435
436/**
437 * 各个工具调用方法返回的格式
438 */
439export type ToolNameValueType = {
440  /**
441   * 工具返回的数据格式,默认为数组,方便excel输出,如果是字符串,则将字符串传入数组第一个元素
442   *
443   * @type { Array}
444   */
445  data: ToolReturnData;
446
447  /**
448   * 用于excel方法回调,返回数据以及ExcelJS构建的Worksheet对象
449   *
450   * @type {toolNameExcelCallback}
451   */
452  callback?: ToolNameExcelCallback;
453};
454export type ToolReturnData = (string | ApiStatisticsInfo | ApiResultSimpleInfo | BasicDiffInfo)[];
455
456/**
457 * 各个工具调用方法
458 *
459 */
460export type ToolNameMethodType = (options: OptionObjType) => ToolNameValueType;
461
462export type PluginType = {
463  pluginOptions: PluginOptionsType;
464  start: (argv: OptionObjType) => Promise<void>;
465  stop: () => void;
466};
467
468export type PluginOptionsType = {
469  name: string;
470  version: string;
471  description: string;
472  commands: CommandType[];
473};
474
475export type CommandType = {
476  isRequiredOption: boolean;
477  readonly options: [string, string, string];
478};
479