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 path, { ParsedPath } from 'path';
16import fs, { Stats } from 'fs';
17import { Workbook, Worksheet } from 'exceljs';
18import ts, { LineAndCharacter } from 'typescript';
19import { ApiResultSimpleInfo, ApiResultInfo, ApiResultMessage, ApiCheckInfo, ErrorBaseInfo } from '../typedef/checker/result_type';
20import { ApiInfo, BasicApiInfo, ClassInfo, ParentClass } from '../typedef/parser/ApiInfoDefination';
21import { FileUtils } from './FileUtils';
22import { ApiCheckVersion } from '../coreImpl/checker/config/api_check_version.json';
23import { PunctuationMark } from './Constant';
24import { Comment } from '../typedef/parser/Comment';
25import { currentFilePath } from '../coreImpl/checker/src/api_check_plugin';
26import { toNumber } from 'lodash';
27
28
29export class PosOfNode {
30  /**
31   * 获取行列信息
32   * @param { ts.Node } node
33   * @param { ts.Diagnostic } diagnostic
34   */
35  static getPosOfNode(node: ts.Node, diagnostic: ts.Diagnostic): string {
36    const posOfNode: LineAndCharacter = ts.getLineAndCharacterOfPosition(
37      node.getSourceFile(),
38      diagnostic.start as number
39    );
40    const location: string =
41      (diagnostic.file?.fileName as string) + `(line: ${posOfNode.line + 1}, col: ${posOfNode.character + 1})`;
42    return location;
43  }
44}
45
46export class CompolerOptions {
47  /**
48   * tsconfig配置项设置
49   */
50  static getCompolerOptions(): ts.CompilerOptions {
51    const compilerOptions: ts.CompilerOptions = ts.readConfigFile(
52      path.resolve(FileUtils.getBaseDirName(), './tsconfig.json'),
53      ts.sys.readFile
54    ).config.compilerOptions;
55    Object.assign(compilerOptions, {
56      target: 'es2020',
57      jsx: 'preserve',
58      incremental: undefined,
59      declaration: undefined,
60      declarationMap: undefined,
61      emitDeclarationOnly: undefined,
62      outFile: undefined,
63      composite: undefined,
64      tsBuildInfoFile: undefined,
65      noEmit: undefined,
66      isolatedModules: true,
67      paths: undefined,
68      rootDirs: undefined,
69      types: undefined,
70      out: undefined,
71      noLib: undefined,
72      noResolve: true,
73      noEmitOnError: undefined,
74      declarationDir: undefined,
75      suppressOutputPathCheck: true,
76      allowNonTsExtensions: true,
77    });
78    return compilerOptions;
79  }
80}
81
82export class GenerateFile {
83  /**
84   * 将错误信息输出为txt文件
85   * @param { ApiResultSimpleInfo[] } resultData
86   * @param { string } outputPath
87   * @param { string } option
88   */
89  static writeFile(resultData: ApiResultMessage[], outputPath: string, option: object): void {
90    const STANDARD_INDENT: number = 2;
91    fs.writeFile(
92      path.resolve(outputPath),
93      JSON.stringify(resultData, null, STANDARD_INDENT),
94      option,
95      (err) => {
96        if (err) {
97          console.error(`ERROR FOR CREATE FILE:${err}`);
98        } else {
99          console.log('API CHECK FINISH!');
100        }
101      }
102    );
103  }
104
105  /**
106   * 将错误信息输出为excel文件
107   * @param { ApiResultInfo[] } apiCheckArr
108   */
109  static async writeExcelFile(apiCheckArr: ApiResultMessage[]): Promise<void> {
110    const workbook: Workbook = new Workbook();
111    const sheet: Worksheet = workbook.addWorksheet('Js Api', { views: [{ xSplit: 1 }] });
112    sheet.getRow(1).values = [
113      'order',
114      'analyzerName',
115      'buggyFilePath',
116      'codeContextStaerLine',
117      'defectLevel',
118      'defectType',
119      'description',
120      'language',
121      'mainBuggyCode',
122      'apiName',
123      'apiType',
124      'hierarchicalRelations',
125      'parentModuleName'
126    ];
127    for (let i = 1; i <= apiCheckArr.length; i++) {
128      const apiData: ApiResultMessage = apiCheckArr[i - 1];
129      sheet.getRow(i + 1).values = [
130        i,
131        apiData.analyzerName,
132        apiData.getFilePath(),
133        apiData.getLocation(),
134        apiData.getLevel(),
135        apiData.getType(),
136        apiData.getMessage(),
137        apiData.language,
138        apiData.getMainBuggyCode(),
139        apiData.getExtendInfo().getApiName(),
140        apiData.getExtendInfo().getApiType(),
141        apiData.getExtendInfo().getHierarchicalRelations(),
142        apiData.getExtendInfo().getParentModuleName()
143      ];
144    }
145    workbook.xlsx.writeBuffer().then((buffer) => {
146      fs.writeFile(path.resolve(FileUtils.getBaseDirName(), './Js_Api.xlsx'), buffer, function (err) {
147        if (err) {
148          console.error(err);
149          return;
150        }
151      });
152    });
153  }
154}
155
156export class ObtainFullPath {
157  /**
158   * 获取仓库中api文件夹下的所有d.tsd.ets路径
159   * @param { string } dir -api路径
160   * @param { string[] } utFiles -存放具体路径的数组
161   */
162  static getFullFiles(dir: string, utFiles: string[]): void {
163    try {
164      const files: string[] = fs.readdirSync(dir);
165      files.forEach((element) => {
166        const filePath: string = path.join(dir, element);
167        const status: Stats = fs.statSync(filePath);
168        if (status.isDirectory()) {
169          ObtainFullPath.getFullFiles(filePath, utFiles);
170        } else if (/\.d\.ts/.test(filePath) || /\.d\.ets/.test(filePath) || /\.ts/.test(filePath)) {
171          utFiles.push(filePath);
172        }
173      });
174    } catch (e) {
175      console.error('ETS ERROR: ' + e);
176    }
177  }
178}
179
180export class CommonFunctions {
181  static getSinceVersion(sinceValue: string): string {
182    return sinceValue.indexOf(PunctuationMark.LEFT_PARENTHESES) !== -1 ?
183      sinceValue.substring(sinceValue.indexOf(PunctuationMark.LEFT_PARENTHESES) + 1,
184        sinceValue.indexOf(PunctuationMark.RIGHT_PARENTHESES)) : sinceValue;
185  }
186  /**
187   * 判断标签是否为官方标签
188   */
189  static isOfficialTag(tagName: string): boolean {
190    return tagsArrayOfOrder.indexOf(tagName) === -1;
191  }
192
193  /**
194   * Replaces the set placeholder with the target
195   * @param { string } errorInfo
196   * @param { string[] } params
197   * @returns { string }
198   */
199  static createErrorInfo(errorInfo: string, params: string[]): string {
200    params.forEach((param) => {
201      errorInfo = errorInfo.replace('$$', param);
202    });
203    return errorInfo;
204  }
205
206  /**
207   * Obtain the current version to be checked
208   * @returns { number }
209   */
210  static getCheckApiVersion(): string {
211    let checkApiVersion: string = '-1';
212    try {
213      checkApiVersion = JSON.stringify(ApiCheckVersion);
214    } catch (error) {
215      throw `Failed to read package.json or parse JSON content: ${error}`;
216    }
217    if (!checkApiVersion) {
218      throw 'Please configure the correct API version to be verified';
219    }
220    return checkApiVersion;
221  }
222
223  static judgeSpecialCase(type: ts.SyntaxKind): string[] {
224    let specialCaseType: string[] = [];
225    if (type === ts.SyntaxKind.TypeLiteral) {
226      specialCaseType = ['object'];
227    } else if (type === ts.SyntaxKind.FunctionType) {
228      specialCaseType = ['function'];
229    }
230    return specialCaseType;
231  }
232  static getExtendsApiValue(singleApi: ApiInfo): string {
233    let extendsApiValue: string = '';
234    const extendsApiValueArr: string[] = [];
235    const extendsApiArr: ParentClass[] = (singleApi as ClassInfo).getParentClasses();
236    if (extendsApiArr.length === 0) {
237      return extendsApiValue;
238    }
239    extendsApiArr.forEach(extendsApi => {
240      if (extendsApi.getExtendClass().length !== 0) {
241        extendsApiValueArr.push(extendsApi.getExtendClass());
242      }
243    });
244    extendsApiValue = extendsApiValueArr.join(',');
245    return extendsApiValue;
246  }
247
248  static getImplementsApiValue(singleApi: ApiInfo): string {
249    let implementsApiValue: string = '';
250    const implementsApiArr: ParentClass[] = (singleApi as ClassInfo).getParentClasses();
251    if (implementsApiArr.length === 0) {
252      return implementsApiValue;
253    }
254    implementsApiArr.forEach(implementsApi => {
255      if (implementsApi.getImplementClass().length !== 0) {
256        implementsApiValue = implementsApi.getImplementClass();
257      }
258    });
259    return implementsApiValue;
260  }
261
262
263  static getErrorInfo(singleApi: BasicApiInfo | undefined, apiJsdoc: Comment.JsDocInfo | undefined, filePath: string,
264    errorBaseInfo: ErrorBaseInfo): ApiCheckInfo {
265    let apiInfo: ApiCheckInfo = new ApiCheckInfo();
266    if (singleApi === undefined) {
267      return apiInfo;
268    }
269    const sinceVersion: number = apiJsdoc === undefined ? -1 : toNumber(apiJsdoc.since);
270    apiInfo
271      .setErrorID(errorBaseInfo.errorID)
272      .setErrorLevel(errorBaseInfo.errorLevel)
273      .setFilePath(filePath)
274      .setApiPostion(singleApi.getPos())
275      .setErrorType(errorBaseInfo.errorType)
276      .setLogType(errorBaseInfo.logType)
277      .setSinceNumber(sinceVersion)
278      .setApiName(singleApi.getApiName())
279      .setApiType(singleApi.getApiType())
280      .setApiText(singleApi.getDefinedText())
281      .setErrorInfo(errorBaseInfo.errorInfo)
282      .setHierarchicalRelations(singleApi.getHierarchicalRelations().join('|'))
283      .setParentModuleName(singleApi.getParentApi()?.getApiName());
284
285    return apiInfo;
286  }
287
288  static getMdFiles(url: string): string[] {
289    const mdFiles: string[] = [];
290    const content: string = fs.readFileSync(url, 'utf-8');
291    const filePathArr: string[] = content.split(/[(\r\n)\r\n]+/);
292    filePathArr.forEach((filePath: string) => {
293      const pathElements: Set<string> = new Set();
294      CommonFunctions.splitPath(filePath, pathElements);
295      if (!pathElements.has('build-tools')) {
296        mdFiles.push(filePath);
297      }
298    });
299    return mdFiles;
300  }
301
302  static splitPath(filePath: string, pathElements: Set<string>): void {
303    let spliteResult: ParsedPath = path.parse(filePath);
304    if (spliteResult.base !== '') {
305      pathElements.add(spliteResult.base);
306      CommonFunctions.splitPath(spliteResult.dir, pathElements);
307    }
308  }
309
310  static isAscending(arr: number[]): boolean {
311    for (let i = 1; i < arr.length; i++) {
312      if (arr[i] < arr[i - 1]) {
313        return false;
314      }
315    }
316    return true;
317  }
318}
319
320/**
321 * The order between labels
322 */
323export const tagsArrayOfOrder: string[] = [
324  'namespace', 'struct', 'typedef', 'interface', 'extends', 'implements', 'permission', 'enum', 'constant', 'type',
325  'param', 'default', 'returns', 'readonly', 'throws', 'static', 'fires', 'syscap', 'systemapi', 'famodelonly',
326  'FAModelOnly', 'stagemodelonly', 'StageModelOnly', 'crossplatform', 'form', 'atomicservice', 'since', 'deprecated',
327  'useinstead', 'test', 'example'
328];
329
330/**
331 * Official label
332 */
333export const officialTagArr: string[] = [
334  'abstract', 'access', 'alias', 'async', 'augments', 'author', 'borrows', 'class', 'classdesc', 'constructs',
335  'copyright', 'event', 'exports', 'external', 'file', 'function', 'generator', 'global', 'hideconstructor', 'ignore',
336  'inheritdoc', 'inner', 'instance', 'lends', 'license', 'listens', 'member', 'memberof', 'mixes',
337  'mixin', 'modifies', 'module', 'package', 'private', 'property', 'protected', 'public', 'requires', 'see', 'summary',
338  'this', 'todo', 'tutorial', 'variation', 'version', 'yields', 'also', 'description', 'kind', 'name', 'undocumented'
339];
340
341/**
342 * Inherit tag
343 */
344export const inheritTagArr: string[] = ['test', 'famodelonly', 'FAModelOnly', 'stagemodelonly', 'StageModelOnly',
345  'deprecated', 'systemapi'];
346
347export const followTagArr: string[] = ['atomicservice', 'form'];
348
349/**
350 * Optional tag
351 */
352export const optionalTags: string[] = [
353  'static', 'fires', 'systemapi', 'famodelonly', 'FAModelOnly', 'stagemodelonly',
354  'StageModelOnly', 'crossplatform', 'deprecated', 'test', 'form', 'example', 'atomicservice'
355];
356/**
357 * conditional optional tag
358 */
359export const conditionalOptionalTags: string[] = ['default', 'permission', 'throws'];
360
361/**
362 * All api types that can use the permission tag.
363 */
364export const permissionOptionalTags: ts.SyntaxKind[] = [
365  ts.SyntaxKind.FunctionDeclaration,
366  ts.SyntaxKind.MethodSignature,
367  ts.SyntaxKind.MethodDeclaration,
368  ts.SyntaxKind.CallSignature,
369  ts.SyntaxKind.Constructor,
370  ts.SyntaxKind.PropertyDeclaration,
371  ts.SyntaxKind.PropertySignature,
372  ts.SyntaxKind.VariableStatement,
373];
374
375/**
376 * Each api type corresponds to a set of available tags.
377 */
378export const apiLegalityCheckTypeMap: Map<ts.SyntaxKind, string[]> = new Map([
379  [ts.SyntaxKind.CallSignature, ['param', 'returns', 'permission', 'throws', 'syscap', 'since']],
380  [ts.SyntaxKind.ClassDeclaration, ['extends', 'implements', 'syscap', 'since']],
381  [ts.SyntaxKind.Constructor, ['param', 'syscap', 'permission', 'throws', 'syscap', 'since']],
382  [ts.SyntaxKind.EnumDeclaration, ['enum', 'syscap', 'since']],
383  [ts.SyntaxKind.FunctionDeclaration, ['param', 'returns', 'permission', 'throws', 'syscap', 'since']],
384  [ts.SyntaxKind.InterfaceDeclaration, ['typedef', 'extends', 'syscap', 'since']],
385  [ts.SyntaxKind.MethodDeclaration, ['param', 'returns', 'permission', 'throws', 'syscap', 'since']],
386  [ts.SyntaxKind.MethodSignature, ['param', 'returns', 'permission', 'throws', 'syscap', 'since']],
387  [ts.SyntaxKind.ModuleDeclaration, ['namespace', 'syscap', 'since']],
388  [ts.SyntaxKind.PropertyDeclaration, ['type', 'default', 'permission', 'throws', 'readonly', 'syscap', 'since']],
389  [ts.SyntaxKind.PropertySignature, ['type', 'default', 'permission', 'throws', 'readonly', 'syscap', 'since']],
390  [ts.SyntaxKind.VariableStatement, ['constant', 'default', 'permission', 'throws', 'syscap', 'since']],
391  [ts.SyntaxKind.TypeAliasDeclaration, ['syscap', 'since', 'typedef', 'param', 'returns', 'throws']],
392  [ts.SyntaxKind.EnumMember, ['syscap', 'since']],
393  [ts.SyntaxKind.NamespaceExportDeclaration, ['syscap', 'since']],
394  [ts.SyntaxKind.TypeLiteral, ['syscap', 'since']],
395  [ts.SyntaxKind.LabeledStatement, ['syscap', 'since']],
396  [ts.SyntaxKind.StructDeclaration, ['struct', 'syscap', 'since']],
397]);
398
399/**
400 * An array of online error messages
401 */
402export let compositiveResult: ApiResultSimpleInfo[] = [];
403
404/**
405 * An array of local error messages
406 */
407export const compositiveLocalResult: ApiResultInfo[] = [];
408
409export let apiCheckResult: ApiResultMessage[] = [];
410
411export let hierarchicalRelationsSet: Set<string> = new Set();
412
413export function cleanApiCheckResult() {
414  apiCheckResult = [];
415  compositiveResult = [];
416  hierarchicalRelationsSet = new Set();
417}
418
419export const punctuationMarkSet: Set<string> = new Set(['\\{', '\\}', '\\(', '\\)', '\\[', '\\]', '\\@', '\\.', '\\:',
420  '\\,', '\\;', '\\(', '\\)', '\\"', '\\/', '\\_', '\\-', '\\=', '\\?', '\\<', '\\>', '\\,', '\\!', '\\#', '\:', '\,',
421  '\\:', '\\|', '\\%', '\\&', '\\¡', '\\¢', '\\+', '\\`', '\\\\', '\\\'']);
422
423export const throwsTagDescriptionArr: string[] = [
424  'Parameter error. Possible causes:',
425  'Mandatory parameters are left unspecified',
426  'Incorrect parameter types',
427  'Parameter verification failed'
428];