161847f8eSopenharmony_ci/*
261847f8eSopenharmony_ci * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
361847f8eSopenharmony_ci * Licensed under the Apache License, Version 2.0 (the "License");
461847f8eSopenharmony_ci * you may not use this file except in compliance with the License.
561847f8eSopenharmony_ci * You may obtain a copy of the License at
661847f8eSopenharmony_ci *
761847f8eSopenharmony_ci *     http://www.apache.org/licenses/LICENSE-2.0
861847f8eSopenharmony_ci *
961847f8eSopenharmony_ci * Unless required by applicable law or agreed to in writing, software
1061847f8eSopenharmony_ci * distributed under the License is distributed on an "AS IS" BASIS,
1161847f8eSopenharmony_ci * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1261847f8eSopenharmony_ci * See the License for the specific language governing permissions and
1361847f8eSopenharmony_ci * limitations under the License.
1461847f8eSopenharmony_ci */
1561847f8eSopenharmony_ci
1661847f8eSopenharmony_ciconst { ErrorType, ErrorLevel, LogType, requireTypescriptModule, checkVersionNeedCheck, createErrorInfo, ErrorValueInfo } = require('./utils');
1761847f8eSopenharmony_ciconst { addAPICheckErrorLogs } = require('./compile_info');
1861847f8eSopenharmony_ciconst { checkSmallHump } = require('./check_hump');
1961847f8eSopenharmony_ciconst ts = requireTypescriptModule();
2061847f8eSopenharmony_ci
2161847f8eSopenharmony_ci// check if on and off functions show in pair
2261847f8eSopenharmony_cifunction checkOnAndOffAppearInPair(node, sourcefile, fileName, onEventAllNames, onEventCheckNames, offEventAllNames, offEventCheckNames) {
2361847f8eSopenharmony_ci  for (const value of onEventCheckNames) {
2461847f8eSopenharmony_ci    if (!offEventAllNames.has(value)) {
2561847f8eSopenharmony_ci      const checkErrorResult = createErrorInfo(ErrorValueInfo.ERROR_EVENT_ON_AND_OFF_PAIR, []);
2661847f8eSopenharmony_ci      addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.API_PAIR_ERRORS, checkErrorResult, LogType.LOG_API,
2761847f8eSopenharmony_ci        ErrorLevel.MIDDLE);
2861847f8eSopenharmony_ci    }
2961847f8eSopenharmony_ci  }
3061847f8eSopenharmony_ci  for (const value of offEventCheckNames) {
3161847f8eSopenharmony_ci    if (!onEventAllNames.has(value)) {
3261847f8eSopenharmony_ci      const checkErrorResult = createErrorInfo(ErrorValueInfo.ERROR_EVENT_ON_AND_OFF_PAIR, []);
3361847f8eSopenharmony_ci      addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.API_PAIR_ERRORS, checkErrorResult, LogType.LOG_API,
3461847f8eSopenharmony_ci        ErrorLevel.MIDDLE);
3561847f8eSopenharmony_ci    }
3661847f8eSopenharmony_ci  }
3761847f8eSopenharmony_ci}
3861847f8eSopenharmony_ci
3961847f8eSopenharmony_cifunction checkTheFirstParameter(node, sourcefile, fileName) {
4061847f8eSopenharmony_ci  // check the version
4161847f8eSopenharmony_ci  if (!checkVersionNeedCheck(node)) {
4261847f8eSopenharmony_ci    return;
4361847f8eSopenharmony_ci  }
4461847f8eSopenharmony_ci  if (node.parameters && node.parameters.length > 0 && node.parameters[0].type) {
4561847f8eSopenharmony_ci    const firstParameterType = node.parameters[0].type;
4661847f8eSopenharmony_ci    // check the type of first parameter
4761847f8eSopenharmony_ci    if ((firstParameterType.kind === ts.SyntaxKind.LiteralType && firstParameterType.literal.kind ===
4861847f8eSopenharmony_ci      ts.SyntaxKind.StringLiteral)) {
4961847f8eSopenharmony_ci      // if the first parameter is string
5061847f8eSopenharmony_ci      const parameterName = firstParameterType.literal.text;
5161847f8eSopenharmony_ci      if (!checkSmallHump(parameterName)) {
5261847f8eSopenharmony_ci        const checkErrorResult = createErrorInfo(ErrorValueInfo.ERROR_EVENT_NAME_SMALL_HUMP, [parameterName]);
5361847f8eSopenharmony_ci        addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.PARAMETER_ERRORS, checkErrorResult, LogType.LOG_API,
5461847f8eSopenharmony_ci          ErrorLevel.MIDDLE);
5561847f8eSopenharmony_ci        return;
5661847f8eSopenharmony_ci      }
5761847f8eSopenharmony_ci    } else if (firstParameterType.kind === ts.SyntaxKind.StringKeyword) {
5861847f8eSopenharmony_ci      // if the first parameter is 'string'
5961847f8eSopenharmony_ci      return;
6061847f8eSopenharmony_ci    } else {
6161847f8eSopenharmony_ci      let checkErrorResult = createErrorInfo(ErrorValueInfo.ERROR_EVENT_NAME_STRING, []);
6261847f8eSopenharmony_ci      if (firstParameterType.typeName && firstParameterType.typeName.escapedText === '') {
6361847f8eSopenharmony_ci        checkErrorResult = createErrorInfo(ErrorValueInfo.ERROR_EVENT_NAME_NULL, []);
6461847f8eSopenharmony_ci      }
6561847f8eSopenharmony_ci      addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.PARAMETER_ERRORS, checkErrorResult, LogType.LOG_API,
6661847f8eSopenharmony_ci        ErrorLevel.MIDDLE);
6761847f8eSopenharmony_ci    }
6861847f8eSopenharmony_ci  }
6961847f8eSopenharmony_ci}
7061847f8eSopenharmony_ci
7161847f8eSopenharmony_cifunction isBasicType(node) {
7261847f8eSopenharmony_ci  if (node.type !== undefined) {
7361847f8eSopenharmony_ci    const nodeKind = node.type.kind;
7461847f8eSopenharmony_ci    const basicTypes = new Set([ts.SyntaxKind.NumberKeyword, ts.SyntaxKind.StringKeyword, ts.SyntaxKind.BooleanKeyword, ts.SyntaxKind.UndefinedKeyword,
7561847f8eSopenharmony_ci      ts.SyntaxKind.LiteralType]);
7661847f8eSopenharmony_ci    if (basicTypes.has(nodeKind)) {
7761847f8eSopenharmony_ci      return true;
7861847f8eSopenharmony_ci    }
7961847f8eSopenharmony_ci  }
8061847f8eSopenharmony_ci  return false;
8161847f8eSopenharmony_ci}
8261847f8eSopenharmony_ci
8361847f8eSopenharmony_ci// check if the callback parameter of off function is optional
8461847f8eSopenharmony_cifunction checkOffFunctions(nodes, sourcefile, fileName) {
8561847f8eSopenharmony_ci  let isAllCallbackMandatory = true;
8661847f8eSopenharmony_ci  let someoneMissingCallback = false;
8761847f8eSopenharmony_ci  let someoneHasCallback = false;
8861847f8eSopenharmony_ci  for (let node of nodes) {
8961847f8eSopenharmony_ci    if (node.parameters.length === 0) {
9061847f8eSopenharmony_ci      continue;
9161847f8eSopenharmony_ci    }
9261847f8eSopenharmony_ci    const lastParameter = node.parameters[node.parameters.length - 1];
9361847f8eSopenharmony_ci    if (isBasicType(lastParameter)) {
9461847f8eSopenharmony_ci      someoneMissingCallback = true;
9561847f8eSopenharmony_ci    } else {
9661847f8eSopenharmony_ci      someoneHasCallback = true;
9761847f8eSopenharmony_ci      if (lastParameter.questionToken !== undefined) {
9861847f8eSopenharmony_ci        isAllCallbackMandatory = false;
9961847f8eSopenharmony_ci      }
10061847f8eSopenharmony_ci    }
10161847f8eSopenharmony_ci  }
10261847f8eSopenharmony_ci  // has off fucntion with callback parameter which is not optional, and doesn't have off function without callback parameter
10361847f8eSopenharmony_ci  if (isAllCallbackMandatory && !someoneMissingCallback) {
10461847f8eSopenharmony_ci    const checkErrorResult = createErrorInfo(ErrorValueInfo.ERROR_EVENT_CALLBACK_OPTIONAL, []);
10561847f8eSopenharmony_ci    addAPICheckErrorLogs(nodes[0], sourcefile, fileName, ErrorType.PARAMETER_ERRORS, checkErrorResult, LogType.LOG_API,
10661847f8eSopenharmony_ci      ErrorLevel.MIDDLE);
10761847f8eSopenharmony_ci  }
10861847f8eSopenharmony_ci}
10961847f8eSopenharmony_ci
11061847f8eSopenharmony_cifunction extendEventNames(node, allNames, checkNames) {
11161847f8eSopenharmony_ci  const nodeType = node.parameters[0].type;
11261847f8eSopenharmony_ci  let eventName = '';
11361847f8eSopenharmony_ci  if (nodeType.kind === ts.SyntaxKind.LiteralType) {
11461847f8eSopenharmony_ci    eventName = nodeType.literal.text;
11561847f8eSopenharmony_ci    allNames.add(eventName);
11661847f8eSopenharmony_ci  } else if (nodeType.kind === ts.SyntaxKind.StringKeyword) {
11761847f8eSopenharmony_ci    eventName = 'string';
11861847f8eSopenharmony_ci    allNames.add(eventName);
11961847f8eSopenharmony_ci  }
12061847f8eSopenharmony_ci  if (checkVersionNeedCheck(node) && eventName !== '') {
12161847f8eSopenharmony_ci    checkNames.add(eventName);
12261847f8eSopenharmony_ci  }
12361847f8eSopenharmony_ci  return eventName;
12461847f8eSopenharmony_ci}
12561847f8eSopenharmony_ci
12661847f8eSopenharmony_cifunction extendEventNodes(node, eventName, nodesSet) {
12761847f8eSopenharmony_ci// store the off function node based on their names
12861847f8eSopenharmony_ci  if (!nodesSet.get(eventName)) {
12961847f8eSopenharmony_ci    nodesSet.set(eventName, [node]);
13061847f8eSopenharmony_ci  } else {
13161847f8eSopenharmony_ci    let curNodes = nodesSet.get(eventName);
13261847f8eSopenharmony_ci    curNodes.push(node);
13361847f8eSopenharmony_ci    nodesSet.set(eventName, curNodes);
13461847f8eSopenharmony_ci  }
13561847f8eSopenharmony_ci}
13661847f8eSopenharmony_ci
13761847f8eSopenharmony_ci// handle event subscription node
13861847f8eSopenharmony_cifunction handleVariousEventSubscriptionAPI(childNode, childNodeName, sourcefile, fileName, onEventAllNames, onEventCheckNames,
13961847f8eSopenharmony_ci  offEventAllNames, offEventCheckNames, offEventNodes) {
14061847f8eSopenharmony_ci  if (childNode.parameters && childNode.parameters.length > 0 && childNode.parameters[0].type) {
14161847f8eSopenharmony_ci    // judge the event subscription api type
14261847f8eSopenharmony_ci    if (childNodeName === 'on') {
14361847f8eSopenharmony_ci      extendEventNames(childNode, onEventAllNames, onEventCheckNames);
14461847f8eSopenharmony_ci      checkTheFirstParameter(childNode, sourcefile, fileName, childNodeName);
14561847f8eSopenharmony_ci    } else if (childNodeName === 'off') {
14661847f8eSopenharmony_ci      let eventName = extendEventNames(childNode, offEventAllNames, offEventCheckNames);
14761847f8eSopenharmony_ci      extendEventNodes(childNode, eventName, offEventNodes);
14861847f8eSopenharmony_ci      checkTheFirstParameter(childNode, sourcefile, fileName, childNodeName);
14961847f8eSopenharmony_ci    } else if (childNodeName === 'once' || childNodeName === 'emit') {
15061847f8eSopenharmony_ci      checkTheFirstParameter(childNode, sourcefile, fileName, childNodeName);
15161847f8eSopenharmony_ci    }
15261847f8eSopenharmony_ci  }
15361847f8eSopenharmony_ci}
15461847f8eSopenharmony_ci
15561847f8eSopenharmony_cifunction checkEventSubscription(node, sourcefile, fileName) {
15661847f8eSopenharmony_ci  // if the node is namespace or interface
15761847f8eSopenharmony_ci  if ((ts.isInterfaceDeclaration(node)) || ts.isModuleBlock(node) || ts.isModuleDeclaration(node) ||
15861847f8eSopenharmony_ci    ts.isClassDeclaration(node) || node === sourcefile) {
15961847f8eSopenharmony_ci    const onEventAllNames = new Set();
16061847f8eSopenharmony_ci    const onEventCheckNames = new Set();
16161847f8eSopenharmony_ci    const offEventAllNames = new Set();
16261847f8eSopenharmony_ci    const offEventCheckNames = new Set();
16361847f8eSopenharmony_ci    const offEventNodes = new Map();
16461847f8eSopenharmony_ci    let childNodes = node.members;
16561847f8eSopenharmony_ci    if (ts.isModuleDeclaration(node)) {
16661847f8eSopenharmony_ci      childNodes = node.body.statements;
16761847f8eSopenharmony_ci    }
16861847f8eSopenharmony_ci    if (childNodes === undefined) {
16961847f8eSopenharmony_ci      return;
17061847f8eSopenharmony_ci    }
17161847f8eSopenharmony_ci    childNodes.forEach((childNode) => {
17261847f8eSopenharmony_ci      // if the node is method or function
17361847f8eSopenharmony_ci      if (ts.isFunctionDeclaration(childNode) || ts.isMethodDeclaration(childNode) || ts.isMethodSignature(childNode)) {
17461847f8eSopenharmony_ci        // if the version needed to be check
17561847f8eSopenharmony_ci        let childNodeName = (childNode.name && ts.isIdentifier(childNode.name)) ?
17661847f8eSopenharmony_ci          childNode.name.getText() :
17761847f8eSopenharmony_ci          '';
17861847f8eSopenharmony_ci        handleVariousEventSubscriptionAPI(childNode, childNodeName, sourcefile, fileName, onEventAllNames,
17961847f8eSopenharmony_ci          onEventCheckNames, offEventAllNames, offEventCheckNames, offEventNodes);
18061847f8eSopenharmony_ci      }
18161847f8eSopenharmony_ci    });
18261847f8eSopenharmony_ci    // check the callback parameter of off function is optional
18361847f8eSopenharmony_ci    for (const event of offEventCheckNames) {
18461847f8eSopenharmony_ci      checkOffFunctions(offEventNodes.get(event), sourcefile, fileName);
18561847f8eSopenharmony_ci    }
18661847f8eSopenharmony_ci    // check if the on and off functions of one event shows in pair
18761847f8eSopenharmony_ci    checkOnAndOffAppearInPair(node, sourcefile, fileName, onEventAllNames, onEventCheckNames, offEventAllNames, offEventCheckNames);
18861847f8eSopenharmony_ci  }
18961847f8eSopenharmony_ci}
19061847f8eSopenharmony_ciexports.checkEventSubscription = checkEventSubscription;
191