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 */
15
16const { ErrorType, ErrorLevel, LogType, requireTypescriptModule, checkVersionNeedCheck, createErrorInfo, ErrorValueInfo } = require('./utils');
17const { addAPICheckErrorLogs } = require('./compile_info');
18const { checkSmallHump } = require('./check_hump');
19const ts = requireTypescriptModule();
20
21// check if on and off functions show in pair
22function checkOnAndOffAppearInPair(node, sourcefile, fileName, onEventAllNames, onEventCheckNames, offEventAllNames, offEventCheckNames) {
23  for (const value of onEventCheckNames) {
24    if (!offEventAllNames.has(value)) {
25      const checkErrorResult = createErrorInfo(ErrorValueInfo.ERROR_EVENT_ON_AND_OFF_PAIR, []);
26      addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.API_PAIR_ERRORS, checkErrorResult, LogType.LOG_API,
27        ErrorLevel.MIDDLE);
28    }
29  }
30  for (const value of offEventCheckNames) {
31    if (!onEventAllNames.has(value)) {
32      const checkErrorResult = createErrorInfo(ErrorValueInfo.ERROR_EVENT_ON_AND_OFF_PAIR, []);
33      addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.API_PAIR_ERRORS, checkErrorResult, LogType.LOG_API,
34        ErrorLevel.MIDDLE);
35    }
36  }
37}
38
39function checkTheFirstParameter(node, sourcefile, fileName) {
40  // check the version
41  if (!checkVersionNeedCheck(node)) {
42    return;
43  }
44  if (node.parameters && node.parameters.length > 0 && node.parameters[0].type) {
45    const firstParameterType = node.parameters[0].type;
46    // check the type of first parameter
47    if ((firstParameterType.kind === ts.SyntaxKind.LiteralType && firstParameterType.literal.kind ===
48      ts.SyntaxKind.StringLiteral)) {
49      // if the first parameter is string
50      const parameterName = firstParameterType.literal.text;
51      if (!checkSmallHump(parameterName)) {
52        const checkErrorResult = createErrorInfo(ErrorValueInfo.ERROR_EVENT_NAME_SMALL_HUMP, [parameterName]);
53        addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.PARAMETER_ERRORS, checkErrorResult, LogType.LOG_API,
54          ErrorLevel.MIDDLE);
55        return;
56      }
57    } else if (firstParameterType.kind === ts.SyntaxKind.StringKeyword) {
58      // if the first parameter is 'string'
59      return;
60    } else {
61      let checkErrorResult = createErrorInfo(ErrorValueInfo.ERROR_EVENT_NAME_STRING, []);
62      if (firstParameterType.typeName && firstParameterType.typeName.escapedText === '') {
63        checkErrorResult = createErrorInfo(ErrorValueInfo.ERROR_EVENT_NAME_NULL, []);
64      }
65      addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.PARAMETER_ERRORS, checkErrorResult, LogType.LOG_API,
66        ErrorLevel.MIDDLE);
67    }
68  }
69}
70
71function isBasicType(node) {
72  if (node.type !== undefined) {
73    const nodeKind = node.type.kind;
74    const basicTypes = new Set([ts.SyntaxKind.NumberKeyword, ts.SyntaxKind.StringKeyword, ts.SyntaxKind.BooleanKeyword, ts.SyntaxKind.UndefinedKeyword,
75      ts.SyntaxKind.LiteralType]);
76    if (basicTypes.has(nodeKind)) {
77      return true;
78    }
79  }
80  return false;
81}
82
83// check if the callback parameter of off function is optional
84function checkOffFunctions(nodes, sourcefile, fileName) {
85  let isAllCallbackMandatory = true;
86  let someoneMissingCallback = false;
87  let someoneHasCallback = false;
88  for (let node of nodes) {
89    if (node.parameters.length === 0) {
90      continue;
91    }
92    const lastParameter = node.parameters[node.parameters.length - 1];
93    if (isBasicType(lastParameter)) {
94      someoneMissingCallback = true;
95    } else {
96      someoneHasCallback = true;
97      if (lastParameter.questionToken !== undefined) {
98        isAllCallbackMandatory = false;
99      }
100    }
101  }
102  // has off fucntion with callback parameter which is not optional, and doesn't have off function without callback parameter
103  if (isAllCallbackMandatory && !someoneMissingCallback) {
104    const checkErrorResult = createErrorInfo(ErrorValueInfo.ERROR_EVENT_CALLBACK_OPTIONAL, []);
105    addAPICheckErrorLogs(nodes[0], sourcefile, fileName, ErrorType.PARAMETER_ERRORS, checkErrorResult, LogType.LOG_API,
106      ErrorLevel.MIDDLE);
107  }
108}
109
110function extendEventNames(node, allNames, checkNames) {
111  const nodeType = node.parameters[0].type;
112  let eventName = '';
113  if (nodeType.kind === ts.SyntaxKind.LiteralType) {
114    eventName = nodeType.literal.text;
115    allNames.add(eventName);
116  } else if (nodeType.kind === ts.SyntaxKind.StringKeyword) {
117    eventName = 'string';
118    allNames.add(eventName);
119  }
120  if (checkVersionNeedCheck(node) && eventName !== '') {
121    checkNames.add(eventName);
122  }
123  return eventName;
124}
125
126function extendEventNodes(node, eventName, nodesSet) {
127// store the off function node based on their names
128  if (!nodesSet.get(eventName)) {
129    nodesSet.set(eventName, [node]);
130  } else {
131    let curNodes = nodesSet.get(eventName);
132    curNodes.push(node);
133    nodesSet.set(eventName, curNodes);
134  }
135}
136
137// handle event subscription node
138function handleVariousEventSubscriptionAPI(childNode, childNodeName, sourcefile, fileName, onEventAllNames, onEventCheckNames,
139  offEventAllNames, offEventCheckNames, offEventNodes) {
140  if (childNode.parameters && childNode.parameters.length > 0 && childNode.parameters[0].type) {
141    // judge the event subscription api type
142    if (childNodeName === 'on') {
143      extendEventNames(childNode, onEventAllNames, onEventCheckNames);
144      checkTheFirstParameter(childNode, sourcefile, fileName, childNodeName);
145    } else if (childNodeName === 'off') {
146      let eventName = extendEventNames(childNode, offEventAllNames, offEventCheckNames);
147      extendEventNodes(childNode, eventName, offEventNodes);
148      checkTheFirstParameter(childNode, sourcefile, fileName, childNodeName);
149    } else if (childNodeName === 'once' || childNodeName === 'emit') {
150      checkTheFirstParameter(childNode, sourcefile, fileName, childNodeName);
151    }
152  }
153}
154
155function checkEventSubscription(node, sourcefile, fileName) {
156  // if the node is namespace or interface
157  if ((ts.isInterfaceDeclaration(node)) || ts.isModuleBlock(node) || ts.isModuleDeclaration(node) ||
158    ts.isClassDeclaration(node) || node === sourcefile) {
159    const onEventAllNames = new Set();
160    const onEventCheckNames = new Set();
161    const offEventAllNames = new Set();
162    const offEventCheckNames = new Set();
163    const offEventNodes = new Map();
164    let childNodes = node.members;
165    if (ts.isModuleDeclaration(node)) {
166      childNodes = node.body.statements;
167    }
168    if (childNodes === undefined) {
169      return;
170    }
171    childNodes.forEach((childNode) => {
172      // if the node is method or function
173      if (ts.isFunctionDeclaration(childNode) || ts.isMethodDeclaration(childNode) || ts.isMethodSignature(childNode)) {
174        // if the version needed to be check
175        let childNodeName = (childNode.name && ts.isIdentifier(childNode.name)) ?
176          childNode.name.getText() :
177          '';
178        handleVariousEventSubscriptionAPI(childNode, childNodeName, sourcefile, fileName, onEventAllNames,
179          onEventCheckNames, offEventAllNames, offEventCheckNames, offEventNodes);
180      }
181    });
182    // check the callback parameter of off function is optional
183    for (const event of offEventCheckNames) {
184      checkOffFunctions(offEventNodes.get(event), sourcefile, fileName);
185    }
186    // check if the on and off functions of one event shows in pair
187    checkOnAndOffAppearInPair(node, sourcefile, fileName, onEventAllNames, onEventCheckNames, offEventAllNames, offEventCheckNames);
188  }
189}
190exports.checkEventSubscription = checkEventSubscription;
191