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