• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2021-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 */
15const fs = require('fs');
16const rules = require('../../code_style_rule.json');
17const { commentNodeWhiteList, requireTypescriptModule, systemPermissionFile, checkOption, ErrorValueInfo,
18  createErrorInfo, OptionalSymbols, parseJsDoc, getDeclareValue } = require('../../src/utils');
19const ts = requireTypescriptModule();
20
21function checkExtendsValue(tag, node, fileName) {
22  const extendsResult = {
23    checkResult: true,
24    errorInfo: '',
25  };
26  const tagValue = tag.name;
27  // 获取api中的extends信息,校验标签合法性及值规范
28  if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) {
29    const apiValue = node.heritageClauses ? node.heritageClauses[0].types[0].getText() : '';
30    if (tagValue !== apiValue) {
31      extendsResult.checkResult = false;
32      extendsResult.errorInfo = ErrorValueInfo.ERROR_INFO_VALUE_EXTENDS;
33    }
34  }
35  return extendsResult;
36}
37exports.checkExtendsValue = checkExtendsValue;
38
39function checkEnumValue(tag, node, fileName) {
40  const enumResult = {
41    checkResult: true,
42    errorInfo: '',
43  };
44  const enumValues = ['string', 'number'];
45  const tagValue = tag.type;
46  const tagProblems = tag.problems.length;
47
48  // 获取api中的enum信息,校验标签合法性及值规范
49  if (tagProblems > 0 || enumValues.indexOf(tagValue) === -1) {
50    enumResult.checkResult = false;
51    enumResult.errorInfo = ErrorValueInfo.ERROR_INFO_VALUE_ENUM;
52  }
53  return enumResult;
54}
55exports.checkEnumValue = checkEnumValue;
56
57function checkSinceValue(tag, node, fileName) {
58  const sinceResult = {
59    checkResult: true,
60    errorInfo: '',
61  };
62  const tagValue = tag.name;
63  const checkNumber = /^\d+$/.test(tagValue);
64  if (!checkNumber && commentNodeWhiteList.includes(node.kind)) {
65    sinceResult.checkResult = false;
66    sinceResult.errorInfo = ErrorValueInfo.ERROR_INFO_VALUE_SINCE;
67  }
68  return sinceResult;
69}
70exports.checkSinceValue = checkSinceValue;
71
72function checkReturnsValue(tag, node, fileName) {
73  const returnsResult = {
74    checkResult: true,
75    errorInfo: '',
76  };
77  const voidArr = ['void'];
78  const tagValue = tag.type.replace(/\n|\r|\s/g, '');
79  let apiReturnsValue = '';
80  if (!commentNodeWhiteList.includes(node.kind)) {
81    return returnsResult;
82  }
83  if (node.kind === ts.SyntaxKind.TypeAliasDeclaration) {
84    apiReturnsValue = ts.SyntaxKind.VoidKeyword === node.type?.type ? 'void' : node.type?.type?.getText().replace(/\n|\r|\s/g, '');
85  } else {
86    apiReturnsValue = getDeclareValue(node.type);
87  }
88  if (voidArr.indexOf(apiReturnsValue) !== -1 || apiReturnsValue === undefined) {
89    returnsResult.checkResult = false;
90    returnsResult.errorInfo = ErrorValueInfo.ERROR_INFO_RETURNS;
91    return returnsResult;
92  }
93  if (tagValue === apiReturnsValue) {
94    return returnsResult;
95  }
96  if (apiReturnsValue === 'Function' && tagValue === 'function') {
97    return returnsResult;
98  }
99  returnsResult.checkResult = false;
100  returnsResult.errorInfo = ErrorValueInfo.ERROR_INFO_VALUE_RETURNS;
101  return returnsResult;
102}
103exports.checkReturnsValue = checkReturnsValue;
104
105function checkParamValue(tag, node, fileName, tagIndex) {
106  const tagNameValue = tag.name;
107  const tagTypeValue = tag.type.replace(/\n|\r|\s/g, '');
108  let paramResult = {
109    checkResult: true,
110    errorInfo: '',
111  };
112  if (!node.parameters) {
113    return paramResult;
114  }
115  const apiParamInfos = node.parameters;
116  if (!apiParamInfos[tagIndex]) {
117    return paramResult;
118  }
119  const apiName = apiParamInfos[tagIndex].name.escapedText;
120  let apiType = getDeclareValue(apiParamInfos[tagIndex].type);
121  let errorInfo = '';
122  if (apiType === tagTypeValue) {
123    return paramResult;
124  }
125  if (apiType === 'Function' && tagTypeValue === 'function') {
126    return paramResult;
127  }
128  if (apiName !== tagNameValue) {
129    paramResult.checkResult = false;
130    if (errorInfo !== '') {
131      errorInfo += '\n';
132    }
133    errorInfo += createErrorInfo(ErrorValueInfo.ERROR_INFO_VALUE_PARAM, [tagIndex + 1, tagIndex + 1]);
134  }
135  paramResult.checkResult = false;
136  errorInfo += createErrorInfo(ErrorValueInfo.ERROR_INFO_TYPE_PARAM, [tagIndex + 1, tagIndex + 1]);
137  if (!paramResult.checkResult) {
138    paramResult.errorInfo = errorInfo;
139  }
140  return paramResult;
141}
142exports.checkParamValue = checkParamValue;
143
144function checkThrowsValue(tag, node, fileName, tagIndex) {
145  const throwsResult = {
146    checkResult: true,
147    errorInfo: '',
148  };
149  const tagNameValue = tag.name;
150  const tagTypeValue = tag.type;
151  let errorInfo = '';
152  let hasDeprecated = false;
153  const comments = parseJsDoc(node).length > 0 ? [parseJsDoc(node).pop()] : [];
154  comments.forEach(comment => {
155    comment.tags.forEach(tag => {
156      if (tag.tag === 'deprecated') {
157        hasDeprecated = true;
158      }
159    });
160  });
161  if (tagTypeValue !== 'BusinessError' && !hasDeprecated) {
162    throwsResult.checkResult = false;
163    errorInfo += createErrorInfo(ErrorValueInfo.ERROR_INFO_VALUE1_THROWS, [tagIndex + 1]);
164  }
165
166  if (isNaN(tagNameValue) && !hasDeprecated) {
167    if (errorInfo !== '') {
168      errorInfo += '\n';
169    }
170    throwsResult.checkResult = false;
171    errorInfo += createErrorInfo(ErrorValueInfo.ERROR_INFO_VALUE2_THROWS, [tagIndex + 1]);
172  }
173  if (!throwsResult.checkResult) {
174    throwsResult.errorInfo = errorInfo;
175  }
176  return throwsResult;
177}
178exports.checkThrowsValue = checkThrowsValue;
179
180/**
181 *
182 * 1.引用不同文件的api接口
183 * xxx.xxx#xxx
184 *
185 * 2.引用不同文件的模块接口
186 * xxx.xxx
187 *
188 * 3.引用不同文件的api事件接口
189 * xxx.xxx#event:xxx
190 */
191function checkModule(moduleValue) {
192  return /^[A-Za-z0-9_]+\b(\.[A-Za-z0-9_]+\b)*$/.test(moduleValue) ||
193    /^[A-Za-z0-9_]+\b(\.[A-Za-z0-9_]+\b)*\#[A-Za-z0-9_]+\b$/.test(moduleValue) ||
194    /^[A-Za-z0-9_]+\b(\.[A-Za-z0-9_]+\b)*\#event:[A-Za-z0-9_]+\b$/.test(moduleValue);
195}
196
197function splitUseinsteadValue(useinsteadValue) {
198  if (!useinsteadValue || useinsteadValue === '') {
199    return undefined;
200  }
201  const splitResult = {
202    checkResult: true,
203    errorInfo: '',
204  };
205  // 拆分字符串
206  const splitArray = useinsteadValue.split(/\//g);
207  const MODEL_COUNT = 1;
208  const MODEL_COUNTS = 2;
209  const FILENAME_MODEL_COUNT = 1;
210  if (splitArray.length === MODEL_COUNT) {
211    if (splitArray[0].indexOf(OptionalSymbols.LEFT_BRACKET) === -1 &&
212      splitArray[0].indexOf(OptionalSymbols.RIGHT_BRACKET) === -1) {
213      // 同一文件
214      splitResult.checkResult = checkModule(splitArray[0]);
215    }
216
217  } else if (splitArray.length === MODEL_COUNTS) {
218    // 不同文件
219    const fileNameArray = splitArray[0].split('.');
220    if (fileNameArray.length === FILENAME_MODEL_COUNT) {
221      // arkui
222      if (!/^[A-Za-z0-9_]+\b$/.test(fileNameArray[0]) || !checkModule(splitArray[1])) {
223        splitResult.checkResult = false;
224      }
225    } else {
226      // 非arkui
227      let checkFileName = true;
228      for (let i = 0; i < fileNameArray.length; i++) {
229        if (fileNameArray[0] !== 'ohos' || !/^[A-Za-z0-9_]+\b$/.test(fileNameArray[i])) {
230          checkFileName = false;
231        }
232      }
233      if (!checkFileName || (!checkModule(splitArray[1]) && splitArray[1].indexOf(OptionalSymbols.LEFT_BRACKET) === -1 &&
234        splitArray[1].indexOf(OptionalSymbols.RIGHT_BRACKET) === -1)) {
235        splitResult.checkResult = false;
236      }
237    }
238  } else {
239    // 格式错误
240    splitResult.checkResult = false;
241  }
242  if (!splitResult.checkResult) {
243    splitResult.errorInfo = ErrorValueInfo.ERROR_INFO_VALUE_USEINSTEAD;
244  }
245  return splitResult;
246}
247
248// 精确校验功能待补全
249function checkUseinsteadValue(tag, node, fileName) {
250  const tagNameValue = tag.name;
251  let useinsteadResult = {
252    checkResult: true,
253    errorInfo: '',
254  };
255  if (tagNameValue === '') {
256    useinsteadResult.checkResult = false;
257    useinsteadResult.errorInfo = ErrorValueInfo.ERROR_INFO_VALUE_USEINSTEAD;
258  } else {
259    const result = splitUseinsteadValue(tagNameValue, fileName);
260    if (result && !result.checkResult) {
261      useinsteadResult = result;
262    }
263  }
264  return useinsteadResult;
265}
266exports.checkUseinsteadValue = checkUseinsteadValue;
267
268function checkTypeValue(tag, node, fileName) {
269  const typeResult = {
270    checkResult: true,
271    errorInfo: '',
272  };
273  const tagTypeValue = tag.type.replace(/\n|\r|\s/g, '');
274  if (!commentNodeWhiteList.includes(node.kind)) {
275    return typeResult;
276  }
277  let apiTypeValue = getDeclareValue(node.type);
278  if (node.questionToken) {
279    apiTypeValue = ts.isUnionTypeNode(node.type) ? OptionalSymbols.LEFT_PARENTHESES + apiTypeValue +
280      OptionalSymbols.RIGHT_PARENTHESES : apiTypeValue;
281    apiTypeValue = OptionalSymbols.QUERY.concat(apiTypeValue);
282  }
283  if (apiTypeValue === tagTypeValue) {
284    return typeResult;
285  }
286  if ((apiTypeValue === 'Function' && tagTypeValue === 'function') || (apiTypeValue === '?Function' && tagTypeValue === '?function')) {
287    return typeResult;
288  }
289  typeResult.checkResult = false;
290  typeResult.errorInfo = ErrorValueInfo.ERROR_INFO_VALUE_TYPE;
291  return typeResult;
292}
293exports.checkTypeValue = checkTypeValue;
294
295function checkDefaultValue(tag, node, fileName) {
296  const defaultResult = {
297    checkResult: true,
298    errorInfo: '',
299  };
300  if (commentNodeWhiteList.includes(node.kind) && tag.name.length === 0 && tag.type.length === 0) {
301    defaultResult.checkResult = false;
302    defaultResult.errorInfo = ErrorValueInfo.ERROR_INFO_VALUE_DEFAULT;
303  }
304  return defaultResult;
305}
306exports.checkDefaultValue = checkDefaultValue;
307
308/**
309 * 门禁环境优先使用systemPermissionFile
310 * 本地环境从指定分支上下载
311 * 下载失败则使用默认配置
312 *
313 * @returns Set<string>
314 */
315function getPermissionList() {
316  const permissionTags = ['ohos.permission.HEALTH_DATA', 'ohos.permission.HEART_RATE', 'ohos.permission.ACCELERATION'];
317  let permissionFileContent;
318  if (fs.existsSync(systemPermissionFile)) {
319    permissionFileContent = require(systemPermissionFile);
320  } else if (checkOption.permissionContent) {
321    permissionFileContent = JSON.parse(checkOption.permissionContent);
322  } else {
323    permissionFileContent = require('../../config/config.json');
324  }
325  const permissionTagsObj = permissionFileContent.module.definePermissions;
326  permissionTagsObj.forEach((item) => {
327    permissionTags.push(item.name);
328  });
329  const permissionRuleSets = new Set(permissionTags);
330  return permissionRuleSets;
331}
332
333function checkPermissionTag(tag, node, fileName) {
334  const permissionRuleSet = getPermissionList();
335  let hasPermissionError = false;
336  let errorInfo = '';
337  const permissionResult = {
338    checkResult: true,
339    errorInfo: '',
340  };
341  const tagValue = tag.name + tag.description;
342  const permissionArr = tagValue.replace(/\s|\(|\)/g, '').replace(/(or|and)/g, '$').split('$');
343  permissionArr.forEach(permissionStr => {
344    if ((permissionStr !== '' && !permissionRuleSet.has(permissionStr)) ||
345      permissionStr === '') {
346      hasPermissionError = true;
347      errorInfo = ErrorValueInfo.ERROR_INFO_VALUE_PERMISSION;
348    }
349  });
350  if (hasPermissionError) {
351    permissionResult.checkResult = false;
352    permissionResult.errorInfo = errorInfo;
353  }
354  return permissionResult;
355}
356exports.checkPermissionTag = checkPermissionTag;
357
358function checkDeprecatedTag(tag, node, fileName) {
359  const deprecatedResult = {
360    checkResult: true,
361    errorInfo: '',
362  };
363  const tagValue1 = tag.name;
364  const tagValue2 = tag.description;
365  const checkNumber = /^\d+$/.test(tagValue2);
366  if ((tagValue1 !== 'since' || !checkNumber) && commentNodeWhiteList.includes(node.kind)) {
367    deprecatedResult.checkResult = false;
368    deprecatedResult.errorInfo = ErrorValueInfo.ERROR_INFO_VALUE_DEPRECATED;
369  }
370  return deprecatedResult;
371}
372exports.checkDeprecatedTag = checkDeprecatedTag;
373
374function checkSyscapTag(tag, node, fileName) {
375  const syscapResult = {
376    checkResult: true,
377    errorInfo: '',
378  };
379  const tagValue = tag.name;
380  const syscapRuleSet = new Set(rules.syscap.SystemCapability);
381  if (!syscapRuleSet.has(tagValue)) {
382    syscapResult.checkResult = false;
383    syscapResult.errorInfo = ErrorValueInfo.ERROR_INFO_VALUE_SYSCAP;
384  }
385  return syscapResult;
386}
387exports.checkSyscapTag = checkSyscapTag;
388
389function checkNamespaceTag(tag, node, fileName) {
390  const namespaceResult = {
391    checkResult: true,
392    errorInfo: '',
393  };
394  const tagValue = tag.name;
395  if (commentNodeWhiteList.includes(node.kind)) {
396    const apiValue = node.name?.escapedText;
397    if (apiValue !== undefined && tagValue !== apiValue) {
398      namespaceResult.checkResult = false;
399      namespaceResult.errorInfo = ErrorValueInfo.ERROR_INFO_VALUE_NAMESPACE;
400    }
401  }
402  return namespaceResult;
403}
404exports.checkNamespaceTag = checkNamespaceTag;
405
406function checkInterfaceTypedefTag(tag, node, fileName) {
407  const interfaceResult = {
408    checkResult: true,
409    errorInfo: '',
410  };
411  const tagValue = tag.name;
412  const tagType = tag.type.replace(/\n|\r|\s/g, '');
413
414  if (commentNodeWhiteList.includes(node.kind)) {
415    const apiValue = node.name?.escapedText;
416    if (node.kind === ts.SyntaxKind.TypeAliasDeclaration) {
417      const isFunctionType = ts.SyntaxKind.FunctionType === node.type?.kind;
418      const isObjectType = ts.SyntaxKind.TypeLiteral === node.type?.kind;
419      let apiType = isFunctionType ? 'function' :
420        isObjectType ? 'object' : node.type.getText().replace(/\n|\r|\s/g, '');
421      if (tagType !== apiType) {
422        interfaceResult.checkResult = false;
423        interfaceResult.errorInfo = ErrorValueInfo.ERROR_INFO_VALUE_TYPEDEF;
424      }
425    } else {
426      if (apiValue !== undefined && tagValue !== apiValue) {
427        interfaceResult.checkResult = false;
428        if (tag.tag === 'interface') {
429          interfaceResult.errorInfo = ErrorValueInfo.ERROR_INFO_VALUE_INTERFACE;
430        } else if (tag.tag === 'typedef') {
431          interfaceResult.errorInfo = ErrorValueInfo.ERROR_INFO_VALUE_TYPEDEF;
432        }
433        interfaceResult.errorInfo = tag.tag === 'interface' ? ErrorValueInfo.ERROR_INFO_VALUE_INTERFACE :
434          tag.tag === 'typedef' ? ErrorValueInfo.ERROR_INFO_VALUE_TYPEDEF : '';
435      }
436    }
437  }
438  return interfaceResult;
439}
440exports.checkInterfaceTypedefTag = checkInterfaceTypedefTag;
441
442const JsDocValueChecker = {
443  extends: checkExtendsValue,
444  enum: checkEnumValue,
445  since: checkSinceValue,
446  returns: checkReturnsValue,
447  param: checkParamValue,
448  throws: checkThrowsValue,
449  useinstead: checkUseinsteadValue,
450  type: checkTypeValue,
451  default: checkDefaultValue,
452  permission: checkPermissionTag,
453  deprecated: checkDeprecatedTag,
454  syscap: checkSyscapTag,
455  namespace: checkNamespaceTag,
456  interface: checkInterfaceTypedefTag,
457  typedef: checkInterfaceTypedefTag,
458};
459exports.JsDocValueChecker = JsDocValueChecker;
460