1/* 2 * Copyright (c) 2023-2024 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 * as ts from 'typescript'; 17import type { TsUtils } from '../TsUtils'; 18 19type CheckStdCallApi = (callExpr: ts.CallExpression) => boolean; 20type StdCallApiEntry = Map<string, CheckStdCallApi>; 21 22export class SupportedStdCallApiChecker { 23 tsUtils: TsUtils; 24 typeChecker: ts.TypeChecker; 25 26 constructor(tsUtils: TsUtils, typeChecker: ts.TypeChecker) { 27 this.tsUtils = tsUtils; 28 this.typeChecker = typeChecker; 29 } 30 31 stdObjectEntry = new Map<string, CheckStdCallApi>([['assign', this.checkObjectAssignCall]]); 32 33 StdCallApi = new Map<string | undefined, StdCallApiEntry>([ 34 ['Object', this.stdObjectEntry], 35 ['ObjectConstructor', this.stdObjectEntry] 36 ]); 37 38 isSupportedStdCallAPI(callExpr: ts.CallExpression, parentSymName: string | undefined, symName: string): boolean { 39 const entry = this.StdCallApi.get(parentSymName); 40 if (entry) { 41 const stdCallApiCheckCb = entry.get(symName); 42 return !!stdCallApiCheckCb && stdCallApiCheckCb.call(this, callExpr); 43 } 44 45 return false; 46 } 47 48 private checkObjectAssignCall(callExpr: ts.CallExpression): boolean { 49 50 /* 51 * 'Object.assign' is allowed only with signature like following: 52 * assign(target: Record<string, V>, ...source: Object[]>): Record<String, V> 53 * 54 * Note: For 'return' type, check the contextual type of call expression, as the 55 * return type of actual call signature will be deduced as an intersection of all 56 * types of the 'target' and 'source' arguments. 57 */ 58 59 if (callExpr.typeArguments || callExpr.arguments.length === 0) { 60 return false; 61 } 62 const targetArgType = this.typeChecker.getTypeAtLocation(callExpr.arguments[0]); 63 if (!this.isValidObjectAssignRecordType(targetArgType)) { 64 return false; 65 } 66 const contextualType = this.typeChecker.getContextualType(callExpr); 67 if (!contextualType || !this.isValidObjectAssignRecordType(contextualType)) { 68 return false; 69 } 70 71 return true; 72 } 73 74 private isValidObjectAssignRecordType(type: ts.Type): boolean { 75 if (this.tsUtils.isStdRecordType(type) && type.aliasTypeArguments?.length) { 76 const typeArg = type.aliasTypeArguments[0]; 77 if (typeArg.getFlags() & (ts.TypeFlags.String | ts.TypeFlags.StringLiteral)) { 78 return true; 79 } 80 } 81 return false; 82 } 83} 84