• 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
16/* eslint prefer-named-capture-group: 0 */
17
18import type * as ts from 'typescript';
19import type { DiagnosticChecker } from './DiagnosticChecker';
20
21/*
22 * Current approach relates on error code and error message matching and it is quite fragile,
23 * so this place should be checked thoroughly in the case of typescript upgrade
24 */
25export const TYPE_0_IS_NOT_ASSIGNABLE_TO_TYPE_1_ERROR_CODE = 2322;
26export const TYPE_UNKNOWN_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE =
27  /^Type '(.*)\bunknown\b(.*)' is not assignable to type '.*'\.$/;
28export const TYPE_NULL_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE = /^Type '(.*)\bnull\b(.*)' is not assignable to type '.*'\.$/;
29export const TYPE_UNDEFINED_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE =
30  /^Type '(.*)\bundefined\b(.*)' is not assignable to type '.*'\.$/;
31
32export const ARGUMENT_OF_TYPE_0_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_ERROR_CODE = 2345;
33export const OBJECT_IS_POSSIBLY_UNDEFINED_ERROR_CODE = 2532;
34export const NO_OVERLOAD_MATCHES_THIS_CALL_ERROR_CODE = 2769;
35export const ARGUMENT_OF_TYPE_NULL_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_RE =
36  /^Argument of type '(.*)\bnull\b(.*)' is not assignable to parameter of type '.*'\.$/;
37export const ARGUMENT_OF_TYPE_UNDEFINED_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_RE =
38  /^Argument of type '(.*)\bundefined\b(.*)' is not assignable to parameter of type '.*'\.$/;
39
40export class LibraryTypeCallDiagnosticChecker implements DiagnosticChecker {
41  inLibCall: boolean = false;
42  diagnosticMessages: Array<ts.DiagnosticMessageChain> | undefined;
43  filteredDiagnosticMessages: Set<ts.DiagnosticMessageChain>;
44
45  constructor(filteredDiagnosticMessages: Set<ts.DiagnosticMessageChain>) {
46    this.filteredDiagnosticMessages = filteredDiagnosticMessages;
47  }
48
49  configure(inLibCall: boolean, diagnosticMessages: Array<ts.DiagnosticMessageChain>): void {
50    this.inLibCall = inLibCall;
51    this.diagnosticMessages = diagnosticMessages;
52  }
53
54  checkMessageText(msg: string): boolean {
55    if (this.inLibCall) {
56      const match =
57        msg.match(ARGUMENT_OF_TYPE_NULL_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_RE) ||
58        msg.match(ARGUMENT_OF_TYPE_UNDEFINED_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_RE) ||
59        msg.match(TYPE_UNDEFINED_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE) ||
60        msg.match(TYPE_NULL_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE);
61      return !match;
62    }
63    return true;
64  }
65
66  checkMessageChain(chain: ts.DiagnosticMessageChain): boolean {
67    if (chain.code === TYPE_0_IS_NOT_ASSIGNABLE_TO_TYPE_1_ERROR_CODE) {
68      if (chain.messageText.match(TYPE_UNKNOWN_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE)) {
69        return false;
70      }
71      if (this.inLibCall && chain.messageText.match(TYPE_UNDEFINED_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE)) {
72        return false;
73      }
74      if (this.inLibCall && chain.messageText.match(TYPE_NULL_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE)) {
75        return false;
76      }
77    }
78    if (chain.code === ARGUMENT_OF_TYPE_0_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_ERROR_CODE) {
79      if (
80        this.inLibCall &&
81        chain.messageText.match(ARGUMENT_OF_TYPE_UNDEFINED_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_RE)
82      ) {
83        return false;
84      }
85      if (
86        this.inLibCall &&
87        chain.messageText.match(ARGUMENT_OF_TYPE_NULL_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_RE)
88      ) {
89        return false;
90      }
91    }
92    return chain.next === undefined ? true : this.checkMessageChain(chain.next[0]);
93  }
94
95  checkFilteredDiagnosticMessages(msgText: ts.DiagnosticMessageChain | string): boolean {
96    if (this.filteredDiagnosticMessages.size === 0) {
97      return true;
98    }
99
100    if (typeof msgText !== 'string' && this.filteredDiagnosticMessages.has(msgText)) {
101      return false;
102    }
103
104    for (const msgChain of this.filteredDiagnosticMessages) {
105      if (typeof msgText === 'string') {
106        if (msgText === msgChain.messageText) {
107          return false;
108        }
109        continue;
110      }
111
112      let curMsg: ts.DiagnosticMessageChain | undefined = msgText;
113      let curFilteredMsg: ts.DiagnosticMessageChain | undefined = msgChain;
114      while (curMsg) {
115        if (!curFilteredMsg) {
116          return true;
117        }
118
119        if (curMsg.code !== curFilteredMsg.code) {
120          return true;
121        }
122
123        if (curMsg.messageText !== curFilteredMsg.messageText) {
124          return true;
125        }
126
127        curMsg = curMsg.next ? curMsg.next[0] : undefined;
128        curFilteredMsg = curFilteredMsg.next ? curFilteredMsg.next[0] : undefined;
129      }
130
131      return false;
132    }
133    return true;
134  }
135
136  checkDiagnosticMessage(msgText: string | ts.DiagnosticMessageChain): boolean {
137    if (!this.diagnosticMessages) {
138      return false;
139    }
140
141    if (this.inLibCall && !this.checkFilteredDiagnosticMessages(msgText)) {
142      return false;
143    }
144
145    if (typeof msgText === 'string') {
146      return this.checkMessageText(msgText);
147    }
148
149    if (!this.checkMessageChain(msgText)) {
150      this.diagnosticMessages.push(msgText);
151      return false;
152    }
153    return true;
154  }
155}
156