• 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*/
15
16const whiteLists = require('../config/jsdocCheckWhiteList.json');
17const { parseJsDoc, commentNodeWhiteList, requireTypescriptModule, ErrorType, ErrorLevel, FileType, ErrorValueInfo,
18  createErrorInfo, isWhiteListFile } = require('./utils');
19const { checkApiOrder, checkAPITagName, checkInheritTag } = require('./check_jsdoc_value/check_order');
20const { addAPICheckErrorLogs } = require('./compile_info');
21const ts = requireTypescriptModule();
22
23// 标签合法性校验
24function checkJsDocLegality(node, comments, checkInfoMap) {
25  // since
26  legalityCheck(node, comments, commentNodeWhiteList, ['since'], true, checkInfoMap);
27  // syscap
28  legalityCheck(node, comments, getIllegalKinds([ts.SyntaxKind.ModuleDeclaration, ts.SyntaxKind.ClassDeclaration]),
29    ['syscap'], true, checkInfoMap);
30  // const定义语句必填
31  legalityCheck(node, comments, [ts.SyntaxKind.VariableStatement], ['constant'], true, checkInfoMap,
32    (currentNode, checkResult) => {
33      return (checkResult && (currentNode.kind !== ts.SyntaxKind.VariableStatement || !/^const\s/.test(currentNode.getText()))) ||
34        (!checkResult && currentNode.kind === ts.SyntaxKind.VariableStatement && /^const\s/.test(currentNode.getText()));
35    });
36  // 'enum'
37  legalityCheck(node, comments, [ts.SyntaxKind.EnumDeclaration], ['enum'], true, checkInfoMap);
38  // 'extends'
39  legalityCheck(node, comments, [ts.SyntaxKind.ClassDeclaration], ['extends'], true, checkInfoMap,
40    (currentNode, checkResult) => {
41      let tagCheckResult = false;
42      if (ts.isClassDeclaration(currentNode) && currentNode.heritageClauses) {
43        const clauses = currentNode.heritageClauses;
44        clauses.forEach(claus => {
45          if (/^extends\s/.test(claus.getText())) {
46            tagCheckResult = true;
47          }
48        });
49      }
50      return (checkResult && !tagCheckResult) || (!checkResult && tagCheckResult);
51    }
52  );
53  // 'namespace'
54  legalityCheck(node, comments, [ts.SyntaxKind.ModuleDeclaration], ['namespace'], true, checkInfoMap);
55  // 'param'
56  legalityCheck(node, comments, [ts.SyntaxKind.FunctionDeclaration, ts.SyntaxKind.MethodSignature,
57    ts.SyntaxKind.MethodDeclaration, ts.SyntaxKind.CallSignature, ts.SyntaxKind.Constructor], ['param'], true, checkInfoMap,
58  (currentNode, checkResult) => {
59    if (!new Set([ts.SyntaxKind.FunctionDeclaration, ts.SyntaxKind.MethodSignature,
60      ts.SyntaxKind.MethodDeclaration, ts.SyntaxKind.Constructor]).has(currentNode.kind)) {
61      return true;
62    }
63    return currentNode.parameters;
64  }
65  );
66  // 'returns'
67  legalityCheck(node, comments, [ts.SyntaxKind.FunctionDeclaration, ts.SyntaxKind.MethodSignature,
68    ts.SyntaxKind.MethodDeclaration, ts.SyntaxKind.CallSignature], ['returns'], true, checkInfoMap,
69  (currentNode, checkResult) => {
70    if (!checkResult && !new Set([ts.SyntaxKind.FunctionDeclaration, ts.SyntaxKind.MethodSignature,
71      ts.SyntaxKind.MethodDeclaration, ts.SyntaxKind.CallSignature]).has(currentNode.kind)) {
72      return false;
73    }
74    return !(!checkResult && !new Set([ts.SyntaxKind.FunctionDeclaration, ts.SyntaxKind.MethodSignature,
75      ts.SyntaxKind.MethodDeclaration, ts.SyntaxKind.CallSignature]).has(currentNode.kind)) && (currentNode.type &&
76        currentNode.type.kind !== ts.SyntaxKind.VoidKeyword);
77  }
78  );
79  // 'useinstead'
80  legalityCheck(node, comments, commentNodeWhiteList, ['useinstead'], true, checkInfoMap,
81    (currentNode, checkResult) => {
82      return new Set(commentNodeWhiteList).has(currentNode.kind);
83    }
84  );
85  // typedef/interface
86  legalityCheck(node, comments, [ts.SyntaxKind.InterfaceDeclaration], ['interface', 'typedef'], true, checkInfoMap);
87  // 'type', 'readonly'
88  legalityCheck(node, comments, [ts.SyntaxKind.PropertyDeclaration, ts.SyntaxKind.PropertySignature],
89    ['type', 'readonly'], false, checkInfoMap);
90  // 'default'
91  legalityCheck(node, comments, [ts.SyntaxKind.PropertyDeclaration, ts.SyntaxKind.PropertySignature,
92    ts.SyntaxKind.VariableStatement], ['default'], false, checkInfoMap);
93  return checkInfoMap;
94}
95exports.checkJsDocLegality = checkJsDocLegality;
96
97function getIllegalKinds(legalKinds) {
98  const illegalKinds = [];
99  const legalKindSet = new Set(legalKinds);
100  commentNodeWhiteList.forEach(kind => {
101    if (!legalKindSet.has(kind)) {
102      illegalKinds.push(kind);
103    }
104  });
105  return illegalKinds;
106}
107
108function dealSpecialTag(comment, tagName) {
109  let checkResult = false;
110  const useinsteadResultObj = {
111    hasUseinstead: false,
112    hasDeprecated: false,
113  };
114  let paramTagNum = 0;
115  comment.tags.forEach(tag => {
116    if (tagName === 'useinstead') {
117      if (tag.tag === tagName) {
118        useinsteadResultObj.hasUseinstead = true;
119      } else if (tag.tag === 'deprecated') {
120        useinsteadResultObj.hasDeprecated = true;
121      }
122    } else if (((tagName === 'interface' || tagName === 'typedef') && (tag.tag === 'interface' ||
123      tag.tag === 'typedef')) || tag.tag === tagName) {
124      checkResult = true;
125    }
126    if (tag.tag === 'param') {
127      paramTagNum++;
128    }
129  });
130  return {
131    useinsteadResultObj: useinsteadResultObj,
132    checkResult: checkResult,
133    paramTagNum: paramTagNum,
134  };
135}
136
137function legalityCheck(node, comments, legalKinds, tagsName, isRequire, checkInfoMap, extraCheckCallback) {
138  const illegalKinds = getIllegalKinds(legalKinds);
139  let illegalKindSet = new Set(illegalKinds);
140  const legalKindSet = new Set(legalKinds);
141  tagsName.forEach(tagName => {
142    if (tagName === 'extends') {
143      illegalKindSet = new Set(commentNodeWhiteList);
144    } else if (tagName === 'syscap') {
145      illegalKindSet = new Set([]);
146    }
147    comments.forEach((comment, index) => {
148      if (!checkInfoMap[index]) {
149        checkInfoMap[index] = {
150          missingTags: [],
151          illegalTags: [],
152        };
153      }
154      const dealSpecialTagResult = dealSpecialTag(comment, tagName);
155      let parameterNum = 0;
156      if (tagName === 'since') {
157      }
158      if (tagName === 'param' && (ts.isMethodDeclaration(node) || ts.isMethodSignature(node) ||
159        ts.isFunctionDeclaration(node) || ts.isCallSignatureDeclaration(node) || ts.isConstructorDeclaration(node))) {
160        parameterNum = node.parameters.length;
161        checkResult = parameterNum !== dealSpecialTagResult.paramTagNum;
162      }
163      let extraCheckResult = false;
164      if (!extraCheckCallback) {
165        extraCheckResult = true;
166      } else {
167        extraCheckResult = extraCheckCallback(node, dealSpecialTagResult.checkResult);
168      }
169      // useinstead特殊处理
170      if (isRequire && tagName !== 'useinstead' && ((tagName !== 'useinstead' && tagName !== 'param' &&
171        !dealSpecialTagResult.checkResult && legalKindSet.has(node.kind)) || (tagName === 'param' &&
172          dealSpecialTagResult.paramTagNum < parameterNum)) && extraCheckResult) {
173        // 报错
174        checkInfoMap[index].missingTags.push(tagName);
175      } else if (((tagName !== 'useinstead' && tagName !== 'param' && dealSpecialTagResult.checkResult &&
176        illegalKindSet.has(node.kind)) || (tagName === 'useinstead' &&
177          !dealSpecialTagResult.useinsteadResultObj.hasDeprecated &&
178          dealSpecialTagResult.useinsteadResultObj.hasUseinstead) ||
179        (tagName === 'param' && dealSpecialTagResult.paramTagNum > parameterNum)) && extraCheckResult) {
180        // 报错
181        let errorInfo = createErrorInfo(ErrorValueInfo.ERROR_USE, [tagName]);
182        if (tagName === 'param') {
183          errorInfo = createErrorInfo(ErrorValueInfo.ERROR_MORELABEL, [parameterNum + 1, tagName]);
184        }
185        checkInfoMap[index].illegalTags.push({
186          checkResult: false,
187          errorInfo,
188          index,
189        });
190      }
191    });
192  });
193  return checkInfoMap;
194}
195
196// 标签重复性检查
197function checkTagsQuantity(comment, index, errorLogs) {
198  const multipleTags = ['throws', 'param'];
199  const tagCountObj = {};
200  comment.tags.forEach(tag => {
201    if (!tagCountObj[tag.tag]) {
202      tagCountObj[tag.tag] = 0;
203    }
204    tagCountObj[tag.tag] = tagCountObj[tag.tag] + 1;
205  });
206  for (const tagName in tagCountObj) {
207    if (tagCountObj[tagName] > 1 && multipleTags.indexOf(tagName) < 0) {
208      errorLogs.push({
209        checkResult: false,
210        errorInfo: createErrorInfo(ErrorValueInfo.ERROR_REPEATLABEL, [tagName]),
211        index,
212      });
213    }
214  }
215  // interface/typedef互斥校验
216  if (tagCountObj.interface > 0 & tagCountObj.typedef > 0) {
217    errorLogs.push({
218      checkResult: false,
219      errorInfo: ErrorValueInfo.ERROR_USE_INTERFACE,
220      index,
221    });
222  }
223}
224
225let paramIndex = 0;
226let throwsIndex = 0;
227
228function checkTagValue(tag, index, node, fileName, errorLogs) {
229  const { JsDocValueChecker } = require('./check_jsdoc_value/check_rest_value');
230  const checker = JsDocValueChecker[tag.tag];
231
232  if (checker) {
233    let valueCheckResult;
234    if (tag.tag === 'param' && [ts.SyntaxKind.FunctionDeclaration, ts.SyntaxKind.MethodSignature,
235      ts.SyntaxKind.MethodDeclaration, ts.SyntaxKind.CallSignature, ts.SyntaxKind.Constructor].indexOf(node.kind) >= 0) {
236      valueCheckResult = checker(tag, node, fileName, paramIndex++);
237    } else if (tag.tag === 'throws') {
238      valueCheckResult = checker(tag, node, fileName, throwsIndex++);
239    } else {
240      valueCheckResult = checker(tag, node, fileName);
241    }
242    if (!valueCheckResult.checkResult) {
243      valueCheckResult.index = index;
244      // 输出告警
245      errorLogs.push(valueCheckResult);
246    }
247  }
248}
249
250function checkJsDocOfCurrentNode(node, sourcefile, fileName, isGuard) {
251  const checkInfoArray = [];
252  const lastComment = parseJsDoc(node).length > 0 ? [parseJsDoc(node).pop()] : [];
253  const comments = isGuard ? lastComment : parseJsDoc(node);
254  const checkInfoMap = checkJsDocLegality(node, comments, {});
255  const checkOrderResult = checkApiOrder(comments);
256  checkOrderResult.forEach((result, index) => {
257    checkInfoMap[index.toString()].orderResult = result;
258  });
259  comments.forEach((comment, index) => {
260    const errorLogs = [];
261    // 继承校验
262    checkInheritTag(comment, node, sourcefile, fileName, index);
263    // 值检验
264    comment.tags.forEach(tag => {
265      const checkAPIDecorator = checkAPITagName(tag, node, sourcefile, fileName, index);
266      if (!checkAPIDecorator.checkResult) {
267        errorLogs.push(checkAPIDecorator);
268      }
269      checkTagValue(tag, index, node, fileName, errorLogs);
270    });
271    paramIndex = 0;
272    throwsIndex = 0;
273    // 标签数量校验
274    checkTagsQuantity(comment, index, errorLogs);
275    checkInfoMap[index.toString()].illegalTags = checkInfoMap[index.toString()].illegalTags.concat(errorLogs);
276  });
277  for (const key in checkInfoMap) {
278    checkInfoArray.push(checkInfoMap[key]);
279  }
280  return checkInfoArray;
281}
282exports.checkJsDocOfCurrentNode = checkJsDocOfCurrentNode;
283
284function checkJSDoc(node, sourcefile, fileName, isGuard) {
285  const verificationResult = checkJsDocOfCurrentNode(node, sourcefile, fileName, isGuard);
286
287  verificationResult.forEach(item => {
288    let errorInfo = '';
289    if (item.missingTags.length > 0) {
290      item.missingTags.forEach(lostLabel => {
291        errorInfo = createErrorInfo(ErrorValueInfo.ERROR_LOST_LABEL, [lostLabel]);
292        addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.WRONG_SCENE, errorInfo, FileType.JSDOC,
293          ErrorLevel.MIDDLE);
294      });
295    }
296    if (item.illegalTags.length > 0) {
297      item.illegalTags.forEach(wrongValueLabel => {
298        errorInfo = wrongValueLabel.errorInfo;
299        addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.WRONG_VALUE, errorInfo, FileType.JSDOC,
300          ErrorLevel.MIDDLE);
301      });
302    }
303    if (!item.orderResult.checkResult) {
304      errorInfo = item.orderResult.errorInfo;
305      addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.WRONG_ORDER, errorInfo, FileType.JSDOC,
306        ErrorLevel.MIDDLE);
307    }
308  });
309}
310exports.checkJSDoc = checkJSDoc;
311