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