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