• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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