• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2021-2022 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 */
15const fs = require('fs');
16const path = require('path');
17const ExcelJS = require('exceljs');
18const cm = require('comment-parser');
19function requireTypescriptModule() {
20  const buildOption = require('./build.json');
21  if (buildOption.isBundle) {
22    return require('typescript');
23  }
24  const tsPathArray = [
25    path.resolve(__dirname, '../node_modules/typescript'),
26    path.resolve(__dirname, '../../node_modules/typescript')
27  ];
28  if (fs.existsSync(tsPathArray[0])) {
29    return require(tsPathArray[0]);
30  } else if (fs.existsSync(tsPathArray[1])) {
31    return require(tsPathArray[1]);
32  }
33  return null;
34}
35exports.requireTypescriptModule = requireTypescriptModule;
36const ts = requireTypescriptModule();
37
38const commentNodeWhiteList = [
39  ts.SyntaxKind.PropertySignature, ts.SyntaxKind.CallSignature, ts.SyntaxKind.MethodSignature,
40  ts.SyntaxKind.MethodDeclaration, ts.SyntaxKind.EnumMember, ts.SyntaxKind.VariableStatement,
41  ts.SyntaxKind.PropertyDeclaration, ts.SyntaxKind.Constructor, ts.SyntaxKind.ModuleDeclaration,
42  ts.SyntaxKind.NamespaceExportDeclaration, ts.SyntaxKind.ClassDeclaration, ts.SyntaxKind.InterfaceDeclaration,
43  ts.SyntaxKind.EnumDeclaration, ts.SyntaxKind.Parameter, ts.SyntaxKind.TypeLiteral, ts.SyntaxKind.FunctionDeclaration,
44  ts.SyntaxKind.LabeledStatement, ts.SyntaxKind.TypeAliasDeclaration
45];
46exports.commentNodeWhiteList = commentNodeWhiteList;
47
48const tagsArrayOfOrder = [
49  'namespace', 'extends', 'typedef', 'interface', 'permission', 'enum', 'constant', 'type', 'param', 'default',
50  'returns', 'readonly', 'throws', 'static', 'fires', 'syscap', 'systemapi', 'famodelonly', 'FAModelOnly',
51  'stagemodelonly', 'StageModelOnly', 'crossplatform', 'since', 'deprecated', 'useinstead', 'test', 'form', 'example'
52];
53exports.tagsArrayOfOrder = tagsArrayOfOrder;
54
55function getAPINote(node) {
56  const apiLength = node.getText().length;
57  const apiFullLength = node.getFullText().length;
58  return node.getFullText().substring(0, apiFullLength - apiLength);
59}
60exports.getAPINote = getAPINote;
61
62function hasAPINote(node) {
63  if (!node) {
64    return false;
65  }
66  const apiNote = getAPINote(node).replace(/[\s]/g, '');
67  if (apiNote && apiNote.length !== 0) {
68    return true;
69  }
70  return false;
71}
72exports.hasAPINote = hasAPINote;
73
74function removeDir(url) {
75  const statObj = fs.statSync(url);
76  if (statObj.isDirectory()) {
77    let dirs = fs.readdirSync(url);
78    dirs = dirs.map(dir => path.join(url, dir));
79    for (let i = 0; i < dirs.length; i++) {
80      removeDir(dirs[i]);
81    }
82    fs.rmdirSync(url);
83  } else {
84    fs.unlinkSync(url);
85  }
86}
87exports.removeDir = removeDir;
88
89function writeResultFile(resultData, outputPath, option) {
90  const STANDARD_INDENT = 2;
91  fs.writeFile(path.resolve(__dirname, outputPath), JSON.stringify(resultData, null, STANDARD_INDENT), option, err => {
92    if (err) {
93      console.error(`ERROR FOR CREATE FILE:${err}`);
94    } else {
95      console.log('API CHECK FINISH!');
96    }
97  });
98}
99exports.writeResultFile = writeResultFile;
100
101function overwriteIndexOf(item, array) {
102  const indexArr = [];
103  for (let i = 0; i < array.length; i++) {
104    if (array[i] === item) {
105      indexArr.push(i);
106    }
107  }
108  return indexArr;
109}
110exports.overwriteIndexOf = overwriteIndexOf;
111
112const ErrorType = {
113  UNKNOW_DECORATOR: {
114    id: 0,
115    description: 'unknow decorator',
116  },
117  MISSPELL_WORDS: {
118    id: 1,
119    description: 'misspell words',
120  },
121  NAMING_ERRORS: {
122    id: 2,
123    description: 'naming errors',
124  },
125  UNKNOW_PERMISSION: {
126    id: 3,
127    description: 'unknow permission',
128  },
129  UNKNOW_SYSCAP: {
130    id: 4,
131    description: 'unknow syscap',
132  },
133  UNKNOW_DEPRECATED: {
134    id: 5,
135    description: 'unknow deprecated',
136  },
137  WRONG_ORDER: {
138    id: 6,
139    description: 'wrong order',
140  },
141  WRONG_VALUE: {
142    id: 7,
143    description: 'wrong value',
144  },
145  WRONG_SCENE: {
146    id: 8,
147    description: 'wrong scene',
148  },
149  PARAMETER_ERRORS: {
150    id: 9,
151    description: 'wrong parameter',
152  },
153  API_PAIR_ERRORS: {
154    id: 10,
155    description: 'limited api pair errors',
156  },
157  ILLEGAL_ANY: {
158    id: 11,
159    description: 'illegal any',
160  },
161  API_CHANGE_ERRORS: {
162    id: 12,
163    description: 'api change errors',
164  },
165};
166exports.ErrorType = ErrorType;
167
168const LogType = {
169  LOG_API: 'Api',
170  LOG_JSDOC: 'JsDoc',
171  LOG_FILE: 'File',
172};
173exports.LogType = LogType;
174
175const ErrorLevel = {
176  HIGH: 3,
177  MIDDLE: 2,
178  LOW: 1,
179};
180exports.ErrorLevel = ErrorLevel;
181
182const FileType = {
183  API: 'Api',
184  JSDOC: 'JsDoc',
185};
186exports.FileType = FileType;
187
188const apiCheckArr = [];
189exports.apiCheckArr = apiCheckArr;
190
191const apiCheckInfoArr = [];
192exports.apiCheckInfoArr = apiCheckInfoArr;
193
194class ApiCheckResultClass {
195  formatCheckResult = true;
196}
197exports.ApiCheckResult = new ApiCheckResultClass();
198
199async function excelApiCheckResult(apiCheckArr) {
200  const workbook = new ExcelJS.Workbook();
201  const sheet = workbook.addWorksheet('Js Api', { views: [{ xSplit: 1 }] });
202  sheet.getRow(1).values = ['order', 'errorType', 'fileName', 'apiName', 'apiContent', 'type', 'errorInfo', 'version', 'model'];
203  for (let i = 1; i <= apiCheckArr.length; i++) {
204    const apiData = apiCheckArr[i - 1];
205    sheet.getRow(i + 1).values = [i, apiData.errorType, apiData.fileName, apiData.apiName, apiData.apiFullText,
206      apiData.type, apiData.errorInfo, apiData.version, apiData.basename];
207  }
208  const buffer = await workbook.xlsx.writeBuffer();
209  fs.writeFile('Js_Api.xlsx', buffer, function (err) {
210    if (err) {
211      console.error(err);
212      return;
213    }
214  });
215  return buffer;
216}
217exports.excelApiCheckResult = excelApiCheckResult;
218
219function getApiInfo(node) {
220  const notesStr = getAPINote(node);
221  const apiInfo = {};
222  const versionArr = [];
223  if (notesStr !== '') {
224    if (/\@systemapi/g.test(notesStr)) {
225      apiInfo.isSystemApi = 'system api';
226    }
227    if (/\@constant/g.test(notesStr)) {
228      apiInfo.isConstant = true;
229    }
230    if (/\@since\s*(\d+)/g.test(notesStr)) {
231      notesStr.replace(/\@since\s*(\d+)/g, (versionInfo) => {
232        versionArr.push(versionInfo);
233        apiInfo.version = versionInfo.replace(/\@since/g, '').trim();
234      });
235      apiInfo.humpVersion = versionArr[0].replace(/\@since/g, '').trim();
236    }
237    if (/\@deprecated.*since\s*(\d+)/g.test(notesStr)) {
238      notesStr.replace(/\@deprecated.*since\s*(\d+)/g,
239        versionInfo => {
240          apiInfo.deprecated = versionInfo.replace(
241            /\@deprecated.*since\s*/g, '').trim();
242        });
243    }
244    if (/\@famodelonly/g.test(notesStr)) {
245      notesStr.replace(/\@famodelonly/g, modelInfo => {
246        apiInfo.model = modelInfo;
247      });
248    } else if (/\@stagemodelonly/g.test(notesStr)) {
249      notesStr.replace(/\@stagemodelonly/g, modelInfo => {
250        apiInfo.model = modelInfo;
251      });
252    }
253    if (/\@syscap\s*((\w|\.|\/|\{|\@|\}|\s)+)/g.test(notesStr)) {
254      notesStr.replace(/\@syscap\s*((\w|\.|\/|\{|\@|\}|\s)+)/g, sysCapInfo => {
255        apiInfo.sysCap = sysCapInfo.replace(/\@syscap/g, '').trim();
256      });
257    }
258    if (/\@permission\s*((\w|\.|\/|\{|\@|\}|\s)+)/g.test(notesStr)) {
259      notesStr.replace(/\@permission\s*((\w|\.|\/|\{|\@|\}|\s)+)/g,
260        permissionInfo => {
261          apiInfo.permission =
262            permissionInfo.replace(/\@permission/g, '').trim();
263        });
264    }
265  }
266  return apiInfo;
267}
268exports.getApiInfo = getApiInfo;
269
270function getApiVersion(node) {
271  if (getApiInfo(node).humpVersion) {
272    return getApiInfo(node).humpVersion;
273  } else if (node.parent) {
274    return getApiVersion(node.parent);
275  } else {
276    return 'NA';
277  }
278}
279exports.getApiVersion = getApiVersion;
280
281function parseJsDoc(node) {
282  if (!hasAPINote(node)) {
283    return [];
284  } else {
285    return cm.parse(getAPINote(node));
286  }
287}
288exports.parseJsDoc = parseJsDoc;
289
290function getDeclareValue(declareValue) {
291  let apiDeclareValue = '';
292  if (!declareValue) {
293    return apiDeclareValue;
294  }
295  if (ts.isFunctionTypeNode(declareValue)) {
296    apiDeclareValue = 'Function';
297  } else if (ts.isTypeLiteralNode(declareValue)) {
298    apiDeclareValue = 'object';
299  } else {
300    apiDeclareValue = declareValue.getText().replace(/\n|\r|\s/g, '');
301  }
302  return apiDeclareValue;
303}
304exports.getDeclareValue = getDeclareValue;
305
306const systemPermissionFile = path.resolve(__dirname, '../../../../../',
307  'base/global/system_resources/systemres/main/config.json');
308
309exports.systemPermissionFile = systemPermissionFile;
310
311exports.checkOption = {
312  permissionContent: undefined,
313};
314
315const inheritArr = ['test', 'famodelonly', 'FAModelOnly', 'stagemodelonly', 'StageModelOnly', 'deprecated',
316  'systemapi'];
317exports.inheritArr = inheritArr;
318
319const ErrorValueInfo = {
320  ERROR_INFO_VALUE_EXTENDS: 'the [extends] tag value is incorrect. Please check if the tag value matches the inherited class name.',
321  ERROR_INFO_VALUE_ENUM: 'the [enum] tag type is incorrect. Please check if the tag type is { string } or { number }',
322  ERROR_INFO_VALUE_SINCE: 'the [since] tag value is incorrect. Please check if the tag value is a numerical value',
323  ERROR_INFO_RETURNS: 'the [returns] tag was used incorrectly. The returns tag should not be used when the return type is void',
324  ERROR_INFO_VALUE_RETURNS: 'the [returns] tag type is incorrect. Please check if the tag type is consistent with the return type',
325  ERROR_INFO_VALUE_USEINSTEAD: 'the [useinstead] tag value is incorrect. Please check the usage method',
326  ERROR_INFO_VALUE_TYPE: 'the [type] tag type is incorrect. Please check if the type matches the attribute type',
327  ERROR_INFO_VALUE_DEFAULT: 'the [default] tag value is incorrect. Please supplement the default value',
328  ERROR_INFO_VALUE_PERMISSION: 'the [permission] tag value is incorrect. Please check if the permission field has been configured or update the configuration file',
329  ERROR_INFO_VALUE_DEPRECATED: 'the [deprecated] tag value is incorrect. Please check the usage method',
330  ERROR_INFO_VALUE_SYSCAP: 'the [syscap] tag value is incorrect. Please check if the syscap field is configured',
331  ERROR_INFO_VALUE_NAMESPACE: 'the [namespace] tag value is incorrect. Please check if it matches the namespace name',
332  ERROR_INFO_VALUE_INTERFACE: 'the [interface] label value is incorrect. Please check if it matches the interface name',
333  ERROR_INFO_VALUE_TYPEDEF: 'the [typedef] tag value is incorrect. Please check if it matches the interface name',
334  ERROR_INFO_TYPE_PARAM: 'the type of the [$$] [param] tag is incorrect. Please check if it matches the type of the [$$] parameter',
335  ERROR_INFO_VALUE_PARAM: 'the value of the [$$] [param] tag is incorrect. Please check if it matches the [$$] parameter name',
336  ERROR_INFO_VALUE1_THROWS: 'the type of the [$$] [throws] tag is incorrect. Please fill in [BusinessError]',
337  ERROR_INFO_VALUE2_THROWS: 'the type of the [$$] [throws] tag is incorrect. Please check if the tag value is a numerical value',
338  ERROR_INFO_INHERIT: 'it was detected that there is an inheritable label [$$] in the current file, but there are child nodes without this label',
339  ERROR_ORDER: 'JSDoc label order error, please make adjustments',
340  ERROR_LABELNAME: 'the [$$] tag does not exist. Please use a valid JSDoc tag',
341  ERROR_LOST_LABEL: 'JSDoc tag validity verification failed. Please confirm if the [$$] tag is missing',
342  ERROR_USE: 'JSDoc label validity verification failed. The [$$] label is not allowed. Please check the label usage method.',
343  ERROR_MORELABEL: 'JSDoc tag validity verification failed. The [$$] [$$] tag is redundant. Please check if the tag should be deleted.',
344  ERROR_REPEATLABEL: 'the validity verification of the JSDoc tag failed. The [$$] tag is not allowed to be reused, please delete the extra tags',
345  ERROR_USE_INTERFACE: 'the validity verification of the JSDoc tag failed. The [interface] tag and [typedef] tag are not allowed to be used simultaneously. Please confirm the interface class.',
346  ERROR_EVENT_NAME_STRING: 'The event name should be string.',
347  ERROR_EVENT_NAME_NULL: 'The event name cannot be Null value.',
348  ERROR_EVENT_NAME_SMALL_HUMP: 'The event name should be named by small hump. (Received [\'$$\'])',
349  ERROR_EVENT_CALLBACK_OPTIONAL: 'The callback parameter of off function should be optional.',
350  ERROR_EVENT_CALLBACK_MISSING: 'The off functions of one single event should have at least one callback parameter, and the callback parameter should be the last parameter.',
351  ERROR_EVENT_ON_AND_OFF_PAIR: 'The on and off event subscription methods do not appear in pair.',
352  ILLEGAL_USE_ANY: 'Illegal [any] keyword used in the API',
353  ERROR_CHANGES_VERSION: 'Please check if the changed API version number is 10.',
354  ERROR_CHANGES_API_HISTORY_PARAM_REQUIRED_CHANGE: 'Forbid changes: Optional parameters cannot be changed to required parameters.',
355  ERROR_CHANGES_API_HISTORY_PARAM_RANGE_CHANGE: 'Forbid changes: Parameters type range cannot be reduced.',
356  ERROR_CHANGES_API_HISTORY_PARAM_WITHOUT_TYPE_CHANGE: 'Forbid changes: Parameters Parameter must be defined by type.',
357  ERROR_CHANGES_API_HISTORY_PARAM_TYPE_CHANGE: 'Forbid changes: Parameters type cannot be modified.',
358  ERROR_CHANGES_API_HISTORY_PARAM_POSITION_CHANGE: 'Forbid changes: Parameters position not be allowed to be modified.',
359  ERROR_CHANGES_API_NEW_REQUIRED_PARAM: 'Forbid changes: Required parameters cannot be created.',
360  ERROR_CHANGES_API_DELETE_PARAM: 'Forbid changes: Parameters cannot be deleted.',
361  ERROR_CHANGES_DEPRECATED: 'Forbid changes: The api has deprecated tag.',
362  ERROR_CHANGES_JSDOC_NUMBER: 'Forbid changes: API changes must add a new section of JSDoc.',
363  ERROR_CHANGES_JSDOC_CHANGE: 'Forbid changes: Previous JSDoc cannot be changed.',
364  ERROR_CHANGES_JSDOC_TRROWS: 'Forbid changes: Throws tag cannot be created.',
365  ERROR_CHANGES_JSDOC_PERMISSION: 'Forbid changes: Permission tag cannot be created or modified.',
366};
367exports.ErrorValueInfo = ErrorValueInfo;
368
369const DIFF_INFO  = {
370  NEW_JSDOCS_LENGTH:1,
371  NEW_JSDOC_INDEX:2,
372};
373exports.DIFF_INFO  = DIFF_INFO;
374
375/**
376 * link error message
377 */
378function createErrorInfo(errorInfo, params) {
379  params.forEach((param) => {
380    errorInfo = errorInfo.replace('$$', param);
381  });
382  return errorInfo;
383}
384exports.createErrorInfo = createErrorInfo;
385
386/**
387 * judge if it is an API file for Arkui
388 */
389function isArkUIApiFile(fileName) {
390  if (fileName.indexOf('component\\ets\\') >= 0 || fileName.indexOf('component/ets/') >= 0) {
391    return true;
392  }
393  return false;
394}
395exports.isArkUIApiFile = isArkUIApiFile;
396
397function isWhiteListFile(fileName, whiteList) {
398  for (let i = 0; i < whiteList.length; i++) {
399    if (path.normalize(fileName).indexOf(path.normalize(whiteList[i])) !== -1) {
400      return true;
401    }
402  }
403  return false;
404}
405exports.isWhiteListFile = isWhiteListFile;
406
407function getCheckApiVersion() {
408  const packageJsonPath = path.join(__dirname, '../package.json');
409  let packageJson;
410  let checkApiVersion;
411  try {
412    const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8');
413    packageJson = JSON.parse(packageJsonContent);
414    checkApiVersion = packageJson.checkApiVersion;
415  } catch (error) {
416    throw `Failed to read package.json or parse JSON content: ${error}`;
417  }
418  if (!checkApiVersion) {
419    throw 'Please configure the correct API version to be verified';
420  }
421  return checkApiVersion;
422}
423exports.getCheckApiVersion = getCheckApiVersion;
424
425const OptionalSymbols = {
426  QUERY: '?',
427  LEFT_BRACKET: '[',
428  RIGHT_BRACKET: ']',
429  LEFT_BRACE: '{',
430  RIGHT_BRACE: '}',
431  LEFT_PARENTHESES: '(',
432  RIGHT_PARENTHESES: ')'
433};
434exports.OptionalSymbols = OptionalSymbols;
435
436function removeDuplicateObj(array) {
437  const newArr = [];
438  const errorInfoSet = new Set();
439  for (const errorInfo of array) {
440    if (!errorInfoSet.has(JSON.stringify(errorInfo))) {
441      errorInfoSet.add(JSON.stringify(errorInfo));
442      newArr.push(errorInfo);
443    }
444  }
445  return newArr;
446};
447exports.removeDuplicateObj = removeDuplicateObj;
448
449// check the api version
450function checkVersionNeedCheck(node) {
451  const apiVersion = getApiVersion(node);
452  const apiCheckVersion = getCheckApiVersion();
453  if (parseInt(apiVersion) >= parseInt(apiCheckVersion)) {
454    return true;
455  }
456  return false;
457}
458exports.checkVersionNeedCheck = checkVersionNeedCheck;
459
460const FUNCTION_TYPES = [ts.SyntaxKind.FunctionDeclaration, ts.SyntaxKind.MethodSignature,
461  ts.SyntaxKind.MethodDeclaration, ts.SyntaxKind.CallSignature, ts.SyntaxKind.Constructor];
462exports.FUNCTION_TYPES = FUNCTION_TYPES;
463
464function splitPath(filePath, pathElements) {
465  let spliteResult = path.parse(filePath);
466  if (spliteResult.base !== '') {
467    pathElements.add(spliteResult.base);
468    splitPath(spliteResult.dir, pathElements);
469  }
470}
471exports.splitPath = splitPath;