1/* 2 * Copyright (c) 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 16import ts from 'typescript'; 17import { EventConstant } from '../../../utils/Constant'; 18import { EventMethodData, CollectParamStatus } from '../../../typedef/checker/event_method_check_interface'; 19import { ErrorID, ErrorLevel, ErrorMessage, ErrorType, LogType } from '../../../typedef/checker/result_type'; 20import { ApiType, BasicApiInfo, MethodInfo, ParamInfo } from '../../../typedef/parser/ApiInfoDefination'; 21import { CommonFunctions } from '../../../utils/checkUtils'; 22import { FilesMap, Parser } from '../../parser/parser'; 23import { AddErrorLogs } from './compile_info'; 24import { compositiveResult, compositiveLocalResult } from '../../../utils/checkUtils'; 25import { CheckHump } from './check_hump'; 26 27export class EventMethodChecker { 28 private apiData: FilesMap; 29 constructor(apiData: FilesMap) { 30 this.apiData = apiData; 31 } 32 33 public getAllEventMethod(): Map<string, EventMethodData> { 34 const allBasicApi: BasicApiInfo[] = Parser.getAllBasicApi(this.apiData); 35 const eventMethodInfo: BasicApiInfo[] = []; 36 allBasicApi.forEach((basicApi: BasicApiInfo) => { 37 if (basicApi.apiType === ApiType.METHOD && this.isEventMethod(basicApi.apiName)) { 38 eventMethodInfo.push(basicApi); 39 } 40 }); 41 const eventMethodDataMap: Map<string, EventMethodData> = this.getEventMethodDataMap(eventMethodInfo); 42 return eventMethodDataMap; 43 } 44 45 public checkEventMethod(eventMethodData: Map<string, EventMethodData>): void { 46 eventMethodData.forEach((eventMethod: EventMethodData) => { 47 // check on&off event pair 48 if ((eventMethod.onEvents.length === 0 && eventMethod.offEvents.length !== 0) || 49 (eventMethod.onEvents.length !== 0 && eventMethod.offEvents.length === 0)) { 50 const firstEvent: BasicApiInfo = eventMethod.onEvents.concat(eventMethod.offEvents)[0]; 51 const errorMessage: string = CommonFunctions.createErrorInfo(ErrorMessage.ERROR_EVENT_ON_AND_OFF_PAIR, []); 52 AddErrorLogs.addAPICheckErrorLogs( 53 ErrorID.API_PAIR_ERRORS_ID, 54 ErrorLevel.MIDDLE, 55 firstEvent.getFilePath(), 56 firstEvent.getPos(), 57 ErrorType.API_PAIR_ERRORS, 58 LogType.LOG_API, 59 parseInt(firstEvent.getCurrentVersion()), 60 firstEvent.getApiName(), 61 firstEvent.getDefinedText(), 62 errorMessage, 63 compositiveResult, 64 compositiveLocalResult 65 ); 66 } 67 68 // check off event 69 let offEvnetCallbackNumber: number = 0; 70 let offCallbackRequiredNumber: number = 0; 71 for (let i = 0; i < eventMethod.offEvents.length; i++) { 72 const offEvent: MethodInfo = eventMethod.offEvents[i] as MethodInfo; 73 if (offEvent.getParams().length < 2) { 74 continue; 75 } 76 const eventCallbackStatus: CollectParamStatus = this.collectEventCallback(offEvent, offEvnetCallbackNumber, 77 offCallbackRequiredNumber); 78 offEvnetCallbackNumber = eventCallbackStatus.callbackNumber; 79 offCallbackRequiredNumber = eventCallbackStatus.requiredCallbackNumber; 80 } 81 if (eventMethod.offEvents.length > 0) { 82 if ((offEvnetCallbackNumber !== 0 && offEvnetCallbackNumber === eventMethod.offEvents.length && 83 offEvnetCallbackNumber === offCallbackRequiredNumber) || 84 (offEvnetCallbackNumber === 0 && eventMethod.offEvents.length !== 0)) { 85 const firstEvent: BasicApiInfo = eventMethod.offEvents[0]; 86 const errorMessage: string = CommonFunctions.createErrorInfo(ErrorMessage.ERROR_EVENT_CALLBACK_OPTIONAL, []); 87 AddErrorLogs.addAPICheckErrorLogs( 88 ErrorID.PARAMETER_ERRORS_ID, 89 ErrorLevel.MIDDLE, 90 firstEvent.getFilePath(), 91 firstEvent.getPos(), 92 ErrorType.PARAMETER_ERRORS, 93 LogType.LOG_API, 94 parseInt(firstEvent.getCurrentVersion()), 95 firstEvent.getApiName(), 96 firstEvent.getDefinedText(), 97 errorMessage, 98 compositiveResult, 99 compositiveLocalResult 100 ); 101 } 102 } 103 104 // check event first param 105 const allEvnets: BasicApiInfo[] = eventMethod.onEvents.concat(eventMethod.offEvents) 106 .concat(eventMethod.emitEvents).concat(eventMethod.onceEvents); 107 for (let i = 0; i < allEvnets.length; i++) { 108 const event: BasicApiInfo = allEvnets[i]; 109 if (!this.checkVersionNeedCheck(event)) { 110 continue; 111 } 112 const eventParams: ParamInfo[] = (event as MethodInfo).getParams(); 113 if (eventParams.length < 1) { 114 const errorMessage: string = CommonFunctions.createErrorInfo(ErrorMessage.ERROR_EVENT_WITHOUT_PARAMETER, []); 115 AddErrorLogs.addAPICheckErrorLogs( 116 ErrorID.PARAMETER_ERRORS_ID, 117 ErrorLevel.MIDDLE, 118 event.getFilePath(), 119 event.getPos(), 120 ErrorType.PARAMETER_ERRORS, 121 LogType.LOG_API, 122 parseInt(event.getCurrentVersion()), 123 event.getApiName(), 124 event.getDefinedText(), 125 errorMessage, 126 compositiveResult, 127 compositiveLocalResult 128 ); 129 continue; 130 } 131 const firstParam: ParamInfo = eventParams[0]; 132 if (firstParam.getParamType() === ts.SyntaxKind.LiteralType) { 133 const paramTypeName: string = firstParam.getType()[0].replace(/\'/g, ''); 134 if (paramTypeName === '') { 135 const errorMessage: string = CommonFunctions.createErrorInfo(ErrorMessage.ERROR_EVENT_NAME_NULL, 136 [firstParam.getApiName()]); 137 AddErrorLogs.addAPICheckErrorLogs( 138 ErrorID.PARAMETER_ERRORS_ID, 139 ErrorLevel.MIDDLE, 140 event.getFilePath(), 141 event.getPos(), 142 ErrorType.PARAMETER_ERRORS, 143 LogType.LOG_API, 144 parseInt(event.getCurrentVersion()), 145 event.getApiName(), 146 event.getDefinedText(), 147 errorMessage, 148 compositiveResult, 149 compositiveLocalResult 150 ); 151 } else if (!CheckHump.checkSmallHump(paramTypeName)) { 152 const errorMessage: string = CommonFunctions.createErrorInfo(ErrorMessage.ERROR_EVENT_NAME_SMALL_HUMP, 153 [paramTypeName]); 154 AddErrorLogs.addAPICheckErrorLogs( 155 ErrorID.PARAMETER_ERRORS_ID, 156 ErrorLevel.MIDDLE, 157 event.getFilePath(), 158 event.getPos(), 159 ErrorType.PARAMETER_ERRORS, 160 LogType.LOG_API, 161 parseInt(event.getCurrentVersion()), 162 event.getApiName(), 163 event.getDefinedText(), 164 errorMessage, 165 compositiveResult, 166 compositiveLocalResult 167 ); 168 } 169 } else if (firstParam.getParamType() !== ts.SyntaxKind.StringKeyword) { 170 const errorMessage: string = CommonFunctions.createErrorInfo(ErrorMessage.ERROR_EVENT_NAME_STRING, 171 [firstParam.getApiName()]); 172 AddErrorLogs.addAPICheckErrorLogs( 173 ErrorID.PARAMETER_ERRORS_ID, 174 ErrorLevel.MIDDLE, 175 event.getFilePath(), 176 event.getPos(), 177 ErrorType.PARAMETER_ERRORS, 178 LogType.LOG_API, 179 parseInt(event.getCurrentVersion()), 180 event.getApiName(), 181 event.getDefinedText(), 182 errorMessage, 183 compositiveResult, 184 compositiveLocalResult 185 ); 186 } 187 } 188 }); 189 } 190 191 private checkVersionNeedCheck(eventInfo: BasicApiInfo): boolean { 192 return parseInt(eventInfo.getCurrentVersion()) >= EventConstant.eventMethodCheckVersion; 193 } 194 195 private collectEventCallback(offEvent: MethodInfo, 196 callbackNumber: number, requiredCallbackNumber: number): CollectParamStatus { 197 const lastParam: ParamInfo = offEvent.getParams().slice(-1)[0]; 198 if (lastParam.paramType) { 199 const basicTypes = new Set([ts.SyntaxKind.NumberKeyword, ts.SyntaxKind.StringKeyword, 200 ts.SyntaxKind.BooleanKeyword, ts.SyntaxKind.UndefinedKeyword, ts.SyntaxKind.LiteralType]); 201 if (!basicTypes.has(lastParam.paramType)) { 202 callbackNumber++; 203 if (lastParam.getIsRequired()) { 204 requiredCallbackNumber++; 205 } 206 } 207 } 208 return { 209 callbackNumber: callbackNumber, 210 requiredCallbackNumber: requiredCallbackNumber 211 }; 212 } 213 214 private getEventMethodDataMap(eventInfos: BasicApiInfo[]): Map<string, EventMethodData> { 215 let eventMethodDataMap: Map<string, EventMethodData> = new Map(); 216 eventInfos.forEach((eventInfo: BasicApiInfo) => { 217 const directorRelations: string[] = [...eventInfo.hierarchicalRelations]; 218 directorRelations.pop(); 219 const apiCompletePath: string = [...directorRelations, this.getEventName(eventInfo.apiName)].join('/'); 220 let eventMethodData: EventMethodData = { 221 onEvents: [], 222 offEvents: [], 223 emitEvents: [], 224 onceEvents: [] 225 }; 226 if (eventMethodDataMap.get(apiCompletePath)) { 227 eventMethodData = eventMethodDataMap.get(apiCompletePath) as EventMethodData; 228 } 229 eventMethodDataMap.set(apiCompletePath, this.collectEventMethod(eventMethodData, eventInfo)); 230 }); 231 return eventMethodDataMap; 232 } 233 234 private collectEventMethod(eventMethodData: EventMethodData, eventInfo: BasicApiInfo): EventMethodData { 235 const eventType: string = this.getEventType(eventInfo.apiName); 236 switch (eventType) { 237 case 'on': 238 eventMethodData.onEvents.push(eventInfo); 239 break; 240 case 'off': 241 eventMethodData.offEvents.push(eventInfo); 242 break; 243 case 'emit': 244 eventMethodData.emitEvents.push(eventInfo); 245 break; 246 case 'once': 247 eventMethodData.onceEvents.push(eventInfo); 248 break; 249 } 250 return eventMethodData; 251 } 252 253 private getEventName(apiName: string): string { 254 return apiName.split(/\_/)[1]; 255 } 256 257 private getEventType(apiName: string): string { 258 return apiName.split(/\_/)[0]; 259 } 260 261 private isEventMethod(apiName: string): boolean { 262 const eventNameReg: RegExp = new RegExp(`^(${EventConstant.eventNameList.join('|')})\_`); 263 return eventNameReg.test(apiName); 264 } 265} 266