• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2023-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
16namespace ts {
17export namespace ArkTSLinter_1_1 {
18
19import DiagnosticChecker = DiagnosticCheckerNamespace.DiagnosticChecker
20// Current approach relates on error code and error message matching and it is quite fragile,
21// so this place should be checked thoroughly in the case of typescript upgrade
22export namespace LibraryTypeCallDiagnosticCheckerNamespace {
23export const TYPE_0_IS_NOT_ASSIGNABLE_TO_TYPE_1_ERROR_CODE = 2322;
24export const TYPE_UNKNOWN_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE = /^Type '(.*)\bunknown\b(.*)' is not assignable to type '.*'\.$/;
25export const TYPE_NULL_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE = /^Type '(.*)\bnull\b(.*)' is not assignable to type '.*'\.$/;
26export const TYPE_UNDEFINED_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE = /^Type '(.*)\bundefined\b(.*)' is not assignable to type '.*'\.$/;
27
28export const ARGUMENT_OF_TYPE_0_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_ERROR_CODE = 2345;
29export const ARGUMENT_OF_TYPE_NULL_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_RE = /^Argument of type '(.*)\bnull\b(.*)' is not assignable to parameter of type '.*'\.$/;
30export const ARGUMENT_OF_TYPE_UNDEFINED_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_RE = /^Argument of type '(.*)\bundefined\b(.*)' is not assignable to parameter of type '.*'\.$/;
31
32export const NO_OVERLOAD_MATCHES_THIS_CALL_ERROR_CODE = 2769;
33export const TYPE = 'Type';
34export const IS_NOT_ASSIGNABLE_TO_TYPE = 'is not assignable to type';
35export const ARGUMENT_OF_TYPE = 'Argument of type';
36export const IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE = 'is not assignable to parameter of type';
37
38export enum ErrorType {
39  NO_ERROR,
40  UNKNOW,
41  NULL,
42  UNDEFINED
43}
44
45export class LibraryTypeCallDiagnosticChecker implements DiagnosticChecker {
46  inLibCall: boolean = false;
47  diagnosticMessages: Array<ts.DiagnosticMessageChain> | undefined;
48  filteredDiagnosticMessages: DiagnosticMessageChain[] = [];
49
50  constructor(filteredDiagnosticMessages: DiagnosticMessageChain[]) {
51    this.filteredDiagnosticMessages = filteredDiagnosticMessages;
52  }
53
54  configure(inLibCall: boolean, diagnosticMessages: Array<ts.DiagnosticMessageChain>) {
55    this.inLibCall = inLibCall;
56    this.diagnosticMessages = diagnosticMessages;
57  }
58
59  checkMessageText(msg: string): boolean {
60    if (this.inLibCall) {
61      const match = msg.match(ARGUMENT_OF_TYPE_NULL_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_RE) ||
62        msg.match(ARGUMENT_OF_TYPE_UNDEFINED_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_RE) ||
63        msg.match(TYPE_UNDEFINED_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE) ||
64        msg.match(TYPE_NULL_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE);
65      return !match;
66    }
67    return true;
68  }
69
70  static checkMessageChain(chain: ts.DiagnosticMessageChain, inLibCall: boolean): ErrorType {
71    if (chain.code === TYPE_0_IS_NOT_ASSIGNABLE_TO_TYPE_1_ERROR_CODE) {
72      if (chain.messageText.match(TYPE_UNKNOWN_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE)) {
73        return ErrorType.UNKNOW;
74      }
75      if (inLibCall && chain.messageText.match(TYPE_UNDEFINED_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE)) {
76        return ErrorType.UNDEFINED;
77      }
78      if (inLibCall && chain.messageText.match(TYPE_NULL_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE)) {
79        return ErrorType.NULL;
80      }
81    }
82    if (chain.code === ARGUMENT_OF_TYPE_0_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_ERROR_CODE) {
83      if (inLibCall && chain.messageText.match(ARGUMENT_OF_TYPE_UNDEFINED_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_RE)) {
84        return ErrorType.UNDEFINED;
85      }
86      if (inLibCall && chain.messageText.match(ARGUMENT_OF_TYPE_NULL_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_RE)) {
87        return ErrorType.NULL;
88      }
89    }
90
91    if (!chain.next?.length) {
92      return ErrorType.NO_ERROR;
93    }
94    // 'No_overrload...' error need to check each sub-error message, others only check the first one
95    if (chain.code !== NO_OVERLOAD_MATCHES_THIS_CALL_ERROR_CODE) {
96      return LibraryTypeCallDiagnosticChecker.checkMessageChain(chain.next[0], inLibCall);
97    }
98    let result = ErrorType.NO_ERROR;
99    for (const child of chain.next) {
100      result = LibraryTypeCallDiagnosticChecker.checkMessageChain(child, inLibCall);
101      if (result !== ErrorType.NO_ERROR) {
102        break;
103      }
104    }
105    return result;
106  };
107
108  checkFilteredDiagnosticMessages(msgText: ts.DiagnosticMessageChain | string) {
109    if (this.filteredDiagnosticMessages.length == 0) {
110      return true;
111    }
112
113    if (typeof msgText !== 'string' && this.filteredDiagnosticMessages.includes(msgText)) {
114      return false;
115    }
116
117    for (const msgChain of this.filteredDiagnosticMessages) {
118      if (typeof msgText == 'string') {
119        if (msgText == msgChain.messageText) {
120          return false;
121        }
122        continue;
123      }
124
125      let curMsg: ts.DiagnosticMessageChain | undefined = msgText;
126      let curFilteredMsg: ts.DiagnosticMessageChain | undefined = msgChain;
127      while (curMsg) {
128        if (!curFilteredMsg) {
129          return true;
130        }
131
132        if (curMsg.code != curFilteredMsg.code) {
133          return true;
134        }
135
136        if (curMsg.messageText != curFilteredMsg.messageText) {
137          return true;
138        }
139
140        curMsg = curMsg.next ? curMsg.next[0]: undefined;
141        curFilteredMsg = curFilteredMsg.next ? curFilteredMsg.next[0]: undefined;
142      }
143
144      return false;
145    }
146    return true;
147  }
148
149  checkDiagnosticMessage(msgText: string | ts.DiagnosticMessageChain): boolean {
150    if (!this.diagnosticMessages) {
151      return false;
152    }
153
154    if (this.inLibCall && !this.checkFilteredDiagnosticMessages(msgText)) {
155      return false;
156    }
157
158    if (typeof msgText == 'string') {
159      return this.checkMessageText(msgText);
160    }
161
162    if (LibraryTypeCallDiagnosticChecker.checkMessageChain(msgText, this.inLibCall) !== ErrorType.NO_ERROR) {
163      this.diagnosticMessages.push(msgText);
164      return false;
165    }
166    return true;
167  }
168
169  static rebuildTscDiagnostics(tscStrictDiagnostics: Map<Diagnostic[]>): void {
170    if (tscStrictDiagnostics.size === 0) {
171      return;
172    }
173
174    let diagnosticMessageChainArr: Diagnostic[] = [];
175    let strictArr: Diagnostic[] = [];
176    tscStrictDiagnostics.forEach((strict) => {
177      if (strict.length === 0) {
178        return;
179      }
180
181      for (let i = 0; i < strict.length; i++) {
182        if (typeof strict[i].messageText === 'string') {
183          strictArr.push(strict[i]);
184        } else {
185          diagnosticMessageChainArr.push(strict[i]);
186        }
187      }
188    });
189
190    if (diagnosticMessageChainArr.length === 0 || strictArr.length === 0) {
191      return;
192    }
193
194    /**
195     * DiagnosticMessageChain in strict mode errors are reported across files and need to be traversed for DiagnosticMessageChain errors on all files
196     * DiagnosticMessageChain type errors require that both type and argument error text be constructed to match those with string errors
197     * TypeScriptLinter. StrictDiagnosticCache can only keep by white list DiagnosticMessageChain type error and its matching success string type errors
198     */
199    const textSet: Set<string> = new Set<string>();
200    const unknownSet: Set<string> = new Set<string>();
201    diagnosticMessageChainArr.forEach((item) => {
202      const diagnosticMessageChain = item.messageText as DiagnosticMessageChain;
203      const isAllowFilter: ErrorType = LibraryTypeCallDiagnosticChecker.checkMessageChain(diagnosticMessageChain, true);
204      if (isAllowFilter === ErrorType.UNKNOW) {
205        LibraryTypeCallDiagnosticChecker.collectDiagnosticMessage(diagnosticMessageChain, unknownSet);
206        TypeScriptLinter.unknowDiagnosticCache.add(item);
207        return;
208      }
209      if (isAllowFilter !== ErrorType.NO_ERROR) {
210        LibraryTypeCallDiagnosticChecker.collectDiagnosticMessage(diagnosticMessageChain, textSet);
211        TypeScriptLinter.strictDiagnosticCache.add(item);
212      }
213    });
214    strictArr.forEach((item) => {
215      const messageText = item.messageText as string;
216      if (unknownSet.has(messageText)) {
217        TypeScriptLinter.unknowDiagnosticCache.add(item);
218      } else if (textSet.has(messageText)) {
219        TypeScriptLinter.strictDiagnosticCache.add(item);
220      }
221    });
222  }
223
224  static collectDiagnosticMessage(diagnosticMessageChain: DiagnosticMessageChain, textSet: Set<string>): void {
225    const isTypeError = diagnosticMessageChain.code === TYPE_0_IS_NOT_ASSIGNABLE_TO_TYPE_1_ERROR_CODE;
226    const typeText = isTypeError ?
227      diagnosticMessageChain.messageText :
228      diagnosticMessageChain.messageText.replace(ARGUMENT_OF_TYPE, TYPE)
229        .replace(IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE, IS_NOT_ASSIGNABLE_TO_TYPE);
230    const argumentText = isTypeError ?
231      diagnosticMessageChain.messageText.replace(TYPE, ARGUMENT_OF_TYPE)
232        .replace(IS_NOT_ASSIGNABLE_TO_TYPE, IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE) :
233      diagnosticMessageChain.messageText;
234    textSet.add(typeText);
235    textSet.add(argumentText);
236  }
237}
238}
239}
240}