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