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